Skip to content

Commit de962bc

Browse files
committed
Add commands
1 parent 1645c18 commit de962bc

3 files changed

Lines changed: 365 additions & 0 deletions

File tree

‎assets/commands/dsync.md‎

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# TAGLINE
2+
3+
push, pull, and diff a local folder against Google Drive
4+
5+
# TLDR
6+
7+
**Initialize** a local folder as a Drive sync root (opens a browser for OAuth)
8+
9+
```dsync init [~/gdrive] --remote-folder [backups/lab] --credentials [~/Downloads/client_secret.json]```
10+
11+
Show **what differs** without transferring anything (exit 1 if anything differs)
12+
13+
```dsync diff```
14+
15+
**Upload** local changes (prints a plan and asks first)
16+
17+
```dsync push```
18+
19+
**Download** remote changes
20+
21+
```dsync pull```
22+
23+
Apply a plan **without prompting** (refused if the plan has conflicts)
24+
25+
```dsync push -y```
26+
27+
Also **remove** destination files that no longer exist on the source (Drive trash on push; permanent delete on Linux pull)
28+
29+
```dsync pull --delete```
30+
31+
Trust equal **size and mtime** instead of hashing (rsync-style quick check)
32+
33+
```dsync diff --fast```
34+
35+
Show **workspace** state (remote folder, depth, cache, token expiry)
36+
37+
```dsync status```
38+
39+
Refresh the **remote index** only
40+
41+
```dsync update-cache```
42+
43+
Print the **version**
44+
45+
```dsync version```
46+
47+
# SYNOPSIS
48+
49+
**dsync** **init** [_DIR_] [**--remote-folder** _P_] [**--credentials** _FILE_] [**--client-id** _ID_] [**--client-secret** _SECRET_] [**--depth** _N_]
50+
51+
**dsync** {**push** | **pull**} [_PATH_] [**-y**] [**--force**] [**-j** _N_] [**--refresh**] [**--fast** | **--verify**] [**--delete**]
52+
53+
**dsync** **diff** [_PATH_] [**-j** _N_] [**--refresh**] [**--fast** | **--verify**]
54+
55+
**dsync** {**status** | **update-cache** [**--refresh**] | **version**}
56+
57+
# COMMANDS
58+
59+
**init** [_DIR_]
60+
> Authorize with Google Drive and turn _DIR_ (default **.**) into a sync folder mirroring **My Drive/**_P_. Writes **.gd/** inside the folder. **--remote-folder** _P_ is created if missing and defaults to the My Drive root. **--depth** _N_ limits how many levels to traverse (**-1**, the default, is unlimited). Supply a Desktop-app **client_secret.json** with **--credentials**, or **--client-id** and **--client-secret** together (also **GOOGLE_CLIENT_ID** / **GOOGLE_CLIENT_SECRET**).
61+
62+
**push** [_PATH_]
63+
> Upload files that are new or newer locally. Shows the plan and asks first. _PATH_ is relative to the current directory and defaults to **.**.
64+
65+
**pull** [_PATH_]
66+
> Download files that are new or newer on Drive. Same plan-and-confirm behaviour as **push**.
67+
68+
**diff** [_PATH_]
69+
> List what differs, with both modification times. Changes nothing. Exit **1** if anything differs, **0** if the trees match, **2** on error.
70+
71+
**status**
72+
> Print the local folder, remote folder, depth, cache state, ignore file, filesystem kind, and token expiry. Does not take the workspace lock.
73+
74+
**update-cache**
75+
> Refresh the local index of the remote tree (incrementally, or fully with **--refresh**).
76+
77+
**version**
78+
> Print `dsync <version> (https://scaleninja.com/drivesync/)`.
79+
80+
# PARAMETERS
81+
82+
**-y**, **--no-prompt**
83+
> Apply the plan without asking. Refused if the plan contains conflicts. **push** / **pull** only.
84+
85+
**--force**
86+
> Also overwrite conflicts where the destination is newer or has different content at the same mtime. Case / Unicode-normalization collisions are never forced. **push** / **pull** only.
87+
88+
**-j** _N_, **--threads** _N_
89+
> Parallel transfer (or hashing) streams. Default **8**, range **1–64**.
90+
91+
**--refresh**
92+
> Ignore the cached remote index and re-list the whole remote tree.
93+
94+
**--fast**
95+
> Trust equal size and mtime without reading the file (rsync-style quick check). Conflicts with **--verify**.
96+
97+
**--verify**
98+
> Re-read every file that needs hashing instead of trusting the local hash cache.
99+
100+
**--delete**
101+
> After transfers succeed, remove from the destination whatever no longer exists on the source. **push** moves Drive entries to the Drive trash. **pull** moves local files to Trash (macOS) or Recycle Bin (Windows) and **deletes them permanently on Linux**. Off by default. **push** / **pull** only.
102+
103+
# DESCRIPTION
104+
105+
**dsync** is the command-line binary for **DriveSync**, a small Rust tool that keeps one local folder and one Google Drive folder in step. It is modelled on **odeke-em/drive**: you **push**, **pull**, or **diff** on demand rather than running a background sync daemon.
106+
107+
Run any command from anywhere inside the sync folder. Each side is compared as a set of paths with size, mtime, and MD5 (Drive returns MD5s in listings). Different sizes mean different content; otherwise the local MD5 is compared against a cache keyed on size and mtime. Equal MD5 wins over mtime. Different MD5 with a newer side (1 s tolerance) is a transfer; the same mtime with different content is a **conflict**.
108+
109+
Push and pull print one line per change (`+` create, `M` overwrite, `!` skipped, `C` conflict, `E` unreadable local file, `D` delete with **--delete**) and ask before doing anything. While conflicts exist the prompt defaults to **no**, **--no-prompt** refuses to run, and end-of-input is never taken as yes. Destinations are re-checked immediately before each write. Uploads over 5 MB use resumable sessions stored in **.gd/**. Google Docs, Sheets, and Slides have no binary content and are always skipped. Symlinks and non-regular files are never synced.
110+
111+
Exit status is **0** on success, **1** when **diff** found differences, and **2** on an error or when any transfer failed. Only one **dsync** command runs in a workspace at a time (even **diff** writes the index); a second one waits. **PATH** is taken in its on-disk spelling, so on a case-insensitive filesystem `push Docs` and `push docs` mean the same folder.
112+
113+
# CONFIGURATION
114+
115+
**.gd/**
116+
> Per-workspace state inside the sync root (mode **0600** on Unix, never uploaded). Must be a real directory, not a symlink.
117+
118+
**.gd/config.json**
119+
> Client id/secret, remote folder path and id, and traversal depth.
120+
121+
**.gd/credentials.json**
122+
> OAuth tokens from **init**. Google expires refresh tokens after 7 days for External apps still in Testing; publish the consent-screen app for personal use to keep tokens.
123+
124+
**.gd/cache.db**
125+
> SQLite index of the remote tree plus the local hash cache. Rebuilt automatically if damaged. Unreliable on NFS or SMB; keep the sync folder on a local disk.
126+
127+
**.driveignore**
128+
> gitignore-style patterns at the sync root. **init** creates one with `.DS_Store`, `._*`, `Thumbs.db`, and `desktop.ini`.
129+
130+
**GOOGLE_CLIENT_ID** / **GOOGLE_CLIENT_SECRET**
131+
> Alternative to **--client-id** / **--client-secret** when no **--credentials** file is given.
132+
133+
There is no global config file. Create a Desktop OAuth client in Google Cloud Console (enable the Drive API, External consent screen, add yourself as a test user) and pass the downloaded JSON to **init**. A Web-application client is rejected because the loopback redirect port is chosen at run time.
134+
135+
# CAVEATS
136+
137+
Linux and Windows binaries are built and smoke-tested in CI; the project documents live Drive testing on **macOS** only.
138+
139+
Two machines syncing the same Drive folder are not coordinated. The Drive API has no conditional update, so a write from elsewhere between the pre-write check and the upload can be lost.
140+
141+
**pull --delete** on Linux deletes local files permanently (no trash). An empty source is refused so **--delete** cannot wipe the destination. Deletions run last and only if every transfer succeeded.
142+
143+
Do not overwrite conflicts without **--force**. Pull keeps no local backup of files it overwrites. The hash cache trusts unchanged size and mtime; use **--verify** after restoring from backup or when a tool rewrites files with mtimes preserved.
144+
145+
The binary name is **dsync**; the project, Homebrew formula, and GitHub repo are named **drivesync**. Other unrelated tools also ship a `dsync` command.
146+
147+
# HISTORY
148+
149+
**DriveSync** is an MIT-licensed Rust CLI by **ScaleNinja**. The public repository was created on **7 September 2026**. It is an explicit successor in spirit to **odeke-em/drive** (2015): Git-style push/pull against Google Drive, with MD5 comparison, a Drive Changes-API index, and no background daemon. Version **0.4.1** is the crate version at documentation time.
150+
151+
# SEE ALSO
152+
153+
[rclone](/man/rclone)(1), [drive](/man/drive)(1), [gdrive](/man/gdrive)(1), [rsync](/man/rsync)(1)
154+
155+
# RESOURCES
156+
157+
```[Source code](https://github.com/scaleninja/drivesync)```
158+
159+
```[Homepage](https://scaleninja.com/drivesync/)```
160+
161+
```[Documentation](https://github.com/scaleninja/drivesync/blob/main/docs/SETUP.md)```
162+
163+
<!-- verified: 2026-09-08 -->

‎assets/commands/homebutler.md‎

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# TAGLINE
2+
3+
single-binary homelab CLI that reports what changed on a server
4+
5+
# TLDR
6+
7+
**Interactive setup** (writes `~/.config/homebutler/config.yaml`)
8+
9+
```homebutler init```
10+
11+
Current **CPU, memory, disk, and uptime**
12+
13+
```homebutler status```
14+
15+
Butler-style **health report** plus what changed since the last snapshot
16+
17+
```homebutler report```
18+
19+
Read-only **doctor** check (resources, stopped containers, public ports, backups, Proxmox)
20+
21+
```homebutler doctor --strict```
22+
23+
Map **containers, ports, and topology**
24+
25+
```homebutler inventory scan```
26+
27+
Show only **ports bound on all interfaces**
28+
29+
```homebutler inventory scan --filter exposed```
30+
31+
**List** Docker containers
32+
33+
```homebutler docker list```
34+
35+
Deploy a **self-hosted app** via generated Compose
36+
37+
```homebutler install [uptime-kuma]```
38+
39+
Verify a **backup actually boots** in an isolated container
40+
41+
```homebutler backup drill [uptime-kuma]```
42+
43+
Terminal **dashboard** for every configured server
44+
45+
```homebutler watch tui```
46+
47+
Embedded **web dashboard** (default `127.0.0.1:8080`)
48+
49+
```homebutler serve --token [secret]```
50+
51+
MCP server on **stdio** for AI agents
52+
53+
```homebutler mcp```
54+
55+
Same commands as **JSON** for scripts
56+
57+
```homebutler report --json```
58+
59+
# SYNOPSIS
60+
61+
**homebutler** [**--json**] [**-v**] [**--config** _path_] [**--server** _name_ | **--all**] _command_ [_options_]
62+
63+
# PARAMETERS
64+
65+
**--json**
66+
> Force machine-readable JSON instead of terminal text. Persistent; accepted on most commands. Colour is dropped automatically when output is piped, redirected, or run from cron.
67+
68+
**-v**, **--verbose**
69+
> Show detailed error information.
70+
71+
**--config** _path_
72+
> Config file. Resolution order is this flag, then **$HOMEBUTLER_CONFIG**, then **~/.config/homebutler/config.yaml**, then **./homebutler.yaml**. If none exist, built-in defaults are used. A **--config** path that does not exist also falls back to defaults rather than failing (except **config validate**).
73+
74+
**--server** _name_
75+
> Run the command on a named remote server from the config over SSH (skipped when that server is marked **local**). Not supported by **proxmox** (use **--endpoint** instead). **deploy** and **upgrade** handle remotes themselves.
76+
77+
**--all**
78+
> Run the command on every configured server in parallel. Same **proxmox** restriction as **--server**.
79+
80+
# COMMANDS
81+
82+
**init**
83+
> Interactive wizard that creates or updates the configuration file.
84+
85+
**status**
86+
> CPU, memory, disk, and uptime.
87+
88+
**report**
89+
> Snapshot current system, container, and port state, compare it to the previous snapshot, and print a butler-style summary. Snapshots live under **~/.homebutler/reports/snapshots/** and are pruned to **--keep** entries (default **30**). **--no-save** prints without writing a snapshot. Change kinds in the report (and in **--json**) are **gone**, **new**, **replaced**, **image**, **state**, **port**, **disk**, and **skipped**.
90+
91+
**doctor**
92+
> Read-only preflight: resource pressure, stopped containers, public bind ports, backup hygiene, notification and watch readiness, report baseline, config-file permissions, Docker-socket mounts, Proxmox TLS/reachability, and incident-history limits. **--strict** exits non-zero on warnings or failures. **--backup-max-age** (default **168h**) warns when the latest backup is older than that. Unlike other commands, **doctor** will still run against a config that **Load** refuses for world-readable secrets, so it can name that failure.
93+
94+
**config validate**
95+
> Check the config without starting anything. Reports which file was used, which resolution rule picked it, what each section became, and keys that do not match the schema (otherwise dropped silently). **--strict** also fails on warnings. No remote routing.
96+
97+
**inventory scan** / **inventory show**
98+
> Tree of system health, Docker containers, and ports. **--filter exposed** keeps only ports listening on all interfaces (`0.0.0.0`, `::`, `*`). **--filter** cannot be combined with **--json**.
99+
100+
**inventory export**
101+
> Export the inventory. **--format mermaid** (default).
102+
103+
**docker** **list** | **restart** _c_ | **stop** _c_ | **logs** _c_ [_lines_] | **stats** | **top** _c_ | **inspect** _c_
104+
> Container operations. **list** aliases **ls**. **logs** defaults to **50** lines. **inspect** never prints environment-variable values. **top** is read-only (`docker top`).
105+
106+
**install** _app_
107+
> Generate Compose and deploy a catalogued self-hosted app (Uptime Kuma, Jellyfin, Pi-hole, Gitea, Portainer, and others). **--port**, **--media** (Jellyfin/Plex), **--dry-run**. Subcommands: **list**, **status** _app_, **uninstall** _app_ (keep data), **purge** _app_ (delete data).
108+
109+
**backup**
110+
> Tar Docker service volumes. **--service** _name_, **--to** _dir_. **backup list** lists archives. **backup drill** _service_ boots the latest (or **--archive**) backup in isolation; **--all** drills every supported app in the archive.
111+
112+
**restore** _archive_
113+
> Restore volumes from an archive. **--service** _name_. Bind-mount host paths in the archive are refused unless named with repeatable **--allow-bind** _path_.
114+
115+
**watch**
116+
> Restart tracker. Records under **~/.homebutler/watch/**. Subcommands: **tui**, **add** [_name_] [**--kind** docker|systemd|pm2], **list**, **remove** _name_, **check**, **start** [**--interval**] (minimum **5s**), **history** (alias **incidents**), **show**, **install** / **uninstall** (supervisor service), **status**. Docker targets use `docker events`; systemd and PM2 poll. Watch notifications are **off** until **watch.notify.enabled** is set.
117+
118+
**serve**
119+
> Embedded web dashboard. **--host** (default **127.0.0.1**), **--port** (default **8080**), **--token** (bearer auth), **--demo** (no real system calls). Release binaries and **install.sh** embed the frontend; `go install` / a plain `go build` do not.
120+
121+
**mcp**
122+
> Model Context Protocol server on **stdio**. **--demo** uses fake data.
123+
124+
**ports** / **processes** / **network**
125+
> Open ports with owning processes (try **sudo** if names are missing), process list, and network scan.
126+
127+
**wake** _mac-or-name_ [_broadcast_]
128+
> Wake-on-LAN magic packet. Names resolve from the **wake** config section. Default broadcast **255.255.255.255**.
129+
130+
**notify test**
131+
> Send a test message through configured Telegram, Slack, Discord, or webhook providers. Legacy alias: **alerts test-notify**.
132+
133+
**alerts**
134+
> One-shot CPU/memory/disk threshold check. **--watch** loops (prefer **watch start** unless you want thresholds only). **--interval** (default **30s**). **alerts init**, **alerts history**.
135+
136+
**proxmox**
137+
> Inspect configured Proxmox VE endpoints (**status**, **guests**, **node**, **tasks**, **task**, **guest** start/shutdown/reboot, **script**). Use **--endpoint**, not **--server**. Guest power actions need a separate action token and **--confirm** with explicit **--node**, **--type**, and **--vmid**.
138+
139+
**deploy** / **upgrade** / **trust** / **version**
140+
> Copy the binary to remotes, upgrade it, manage host-key trust, and print the version.
141+
142+
# DESCRIPTION
143+
144+
**homebutler** is a single Go binary for homelab operations. It remembers what a server looked like last time and reports **what changed**, rather than another graph of the current moment. There is no required daemon, database, or always-on web service: the same binary is the CLI, an optional dashboard, and an MCP server.
145+
146+
The design is CLI-first and JSON-friendly so the same commands work from a terminal, cron, SSH, CI, or an agent that should not be given a full shell. **report** compares full container, process, and port lists (not just counts), so a container recreated under the same name shows as **replaced** rather than "no change". When a comparison cannot be made (for example Docker was down), the kind is **skipped** instead of an all-clear.
147+
148+
Remote servers in the config are reached over SSH. Prefer key auth. Colour in terminal reports is dropped when stdout is not a TTY.
149+
150+
# CONFIGURATION
151+
152+
**~/.config/homebutler/config.yaml**
153+
> Preferred config (XDG). Created by **init**. Example sections: **servers**, **proxmox**, **wake**, **alerts**, **notify**, **watch**, **backup_dir**. Output format is a flag (**--json**), not a config key.
154+
155+
**$HOMEBUTLER_CONFIG**
156+
> Overrides the default path when **--config** is omitted.
157+
158+
**./homebutler.yaml**
159+
> Last-resort path in the current directory.
160+
161+
**~/.homebutler/reports/snapshots/**
162+
> **report** baselines. Pruned with **--keep**.
163+
164+
**~/.homebutler/watch/**
165+
> Watch list, incident history, and watch-local config. A running **watch** service reads the list at startup; **watch add** / **remove** print the restart command if a supervisor is installed.
166+
167+
**~/.homebutler/alerts.yaml**
168+
> Deprecated fallback for notify/rules. Move them into **config.yaml**.
169+
170+
A config that holds plaintext secrets must not be group/world-readable; **Load** refuses it (mode check on **perm & 0o077**). Unrecognised keys are ignored unless you run **config validate**. Check a file with **homebutler config validate**.
171+
172+
# CAVEATS
173+
174+
`go install github.com/Higangssh/homebutler@latest` builds without the embedded dashboard: **serve** then cannot serve `/`. Use a release binary, **install.sh**, or **make build-all** from a checkout.
175+
176+
**watch.notify.enabled** defaults to false, and **notify_on: flapping** means a single restart is not sent. Configuring Telegram and passing **notify test** is not enough for incidents to be delivered.
177+
178+
**doctor --strict** makes outbound calls to each configured Proxmox endpoint. An unreachable or rebooting host fails the run.
179+
180+
Proxmox guest start/reboot/shutdown do not fall back to the read token. They fail until **action_token_id** and **action_token** (or **action_token_file**) are set, and they require **--confirm**.
181+
182+
**install portainer** mounts the Docker socket (host root from inside the container). **doctor** reports that class of mount.
183+
184+
# HISTORY
185+
186+
**HomeButler** is an MIT-licensed Go project by **Higangssh**. The public repository was created on **23 February 2026** with **v0.1.0** the same day (core commands, network scan, alerts, Wake-on-LAN). **v0.2.0** added SSH multi-server support, XDG config discovery, and **deploy**. Later 2026 releases added the MCP server, **report** identity-aware diffs (**replaced** vs count comparison, **0.26.0**), Proxmox, backup drills, and the embedded dashboard. Current mainline at documentation time is **0.29.0**.
187+
188+
# SEE ALSO
189+
190+
[docker](/man/docker)(1), [docker-compose](/man/docker-compose)(1), [lazydocker](/man/lazydocker)(1), [ctop](/man/ctop)(1), [glances](/man/glances)(1), [wakeonlan](/man/wakeonlan)(1)
191+
192+
# RESOURCES
193+
194+
```[Source code](https://github.com/Higangssh/homebutler)```
195+
196+
```[Homepage](https://homebutler.dev)```
197+
198+
```[Documentation](https://github.com/Higangssh/homebutler#readme)```
199+
200+
<!-- verified: 2026-09-08 -->

‎assets/commands/index.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,7 @@ dsniff.md
19951995
dss.md
19961996
dstask.md
19971997
dstat.md
1998+
dsync.md
19981999
dtach.md
19992000
dtc.md
20002001
dte.md
@@ -3362,6 +3363,7 @@ holehe.md
33623363
hollywood.md
33633364
holos.md
33643365
home-manager.md
3366+
homebutler.md
33653367
homectl.md
33663368
homeshick.md
33673369
host.md

0 commit comments

Comments
 (0)