vault-sync-install
Install vault-sync on the current host. OS-detecting, idempotent installer that deploys scripts, installs scheduler units, and registers with skillwiki config.
When to use
- First-time setup of vault-sync on a new host
- Upgrading vault-sync scripts after a plugin update
- Switching a host from leaf to snapshotter role (requires
--override-snapshotter) - Installing only the Linux rclone FUSE refresh timer on an LXC/S3-mounted wiki consumer
Steps
- Detect OS — run
platform_detect_os. Fail onunsupported. - Parse flags:
--mode=full|fuse-only(default:full).--role=leaf|snapshotter(default:leaf). Ifsnapshotter:- Read
fleet.yamlfrom vault viafleet_load. - Call
fleet_validate_install $(hostname) snapshotter [--override-snapshotter]. - On override: print warning, note that fleet.yaml update is deferred to user.
- Read
--service-scope=auto|user|systemfor Linux snapshotter or FUSE-only installs.autousessystemwhen run as root anduserotherwise. Full leaf installs stay on user units.--vault-path=<path>for the FUSE-only mount guard. Defaults to~/wiki.--max-dir-cache=<duration>for the FUSE freshness envelope. Defaults to15m.--package-version=<ver>/--package-commit=<sha>(optional) — set deploy provenance forruntime-manifest.json. Equivalent env:VS_PACKAGE_VERSION,VS_PACKAGE_COMMIT. Metadata only; does not change copied scripts. Required for honest manifests when the package root is rsynced without monorepopackage.json/ git.
- Check prerequisites:
command -v rclone— required. Warn-only if not found, but install proceeds (rclone can be installed later).command -v git— required for--mode=full. Fail if missing.- macOS:
command -v launchctl— required. - Linux full mode:
systemctl --usermust be available. Fail with hint if not. - Linux FUSE-only mode:
systemctlmust be available. User scope requiressystemctl --user; system scope writes root units under/etc/systemd/system.
- Deploy scripts:
For full installs, also create or repairmkdir -p $(platform_share_dir)/bin cp packages/vault-sync/scripts/*.sh $(platform_share_dir)/bin/ cp -r packages/vault-sync/scripts/lib $(platform_share_dir)/bin/ cp packages/vault-sync/skills/vault-presync/wiki-sync.sh $(platform_share_dir)/bin/wiki-sync.sh chmod +x $(platform_share_dir)/bin/*.sh~/bin/wiki-sync.shas a symlink to$(platform_share_dir)/bin/wiki-sync.shwhen safe. Do not clobber a real non-symlink user file at that path. - Deploy filter file:
mkdir -p $(platform_rclone_config_dir) cp packages/vault-sync/filters/wiki-push-filters.txt $(platform_rclone_config_dir)/ - Install scheduler units:
- macOS: render
.plist.tmplfiles with@SCRIPT_DIR@→$(platform_share_dir)/bin,@LOG_DIR@→$(platform_log_dir). Validate plist syntax plusLabel/ProgramArguments[0], write to~/Library/LaunchAgents/, then runlaunchctl bootstrap gui/$UID <plist>. - Linux leaf: render
.service+.timerwith@SCRIPT_DIR@→$(platform_share_dir)/bin. Write to~/.config/systemd/user/. Runsystemctl --user daemon-reload && systemctl --user enable --now wiki-push.timer wiki-fetch.timer wiki-fuse-refresh.timer. - Linux snapshotter: render
wiki-snapshot.service+wiki-snapshot.timerpluswiki-fuse-refresh.service+wiki-fuse-refresh.timer. Write to/etc/systemd/system/for--service-scope systemor~/.config/systemd/user/for--service-scope user. Enablewiki-snapshot.timeron a 30-minute cadence (*:02and*:32) plus the 5-minute FUSE refresh timer. - Linux post-check: run
wiki-fuse-refresh.sh --check-only --max-dir-cache 15mand surface a warning if the active mount exceeds the freshness envelope. - Linux only:
loginctl enable-linger $USER. If this fails, surface as a hard error — without it, headless LXC will silently not sync.
- macOS: render
- Register in skillwiki config for full mode:
Snapshotter installs also recordskillwiki config set vault_sync.installed true skillwiki config set vault_sync.role <role> skillwiki config set vault_sync.scheduler <launchd|systemd> skillwiki config set vault_sync.service_scope <user|system> # Linux only skillwiki config set vault_sync.fuse_refresh_enabled <true|false> skillwiki config set vault_sync.fuse_refresh_interval 300s # Linux only skillwiki config set vault_sync.fuse_max_dir_cache 15m # Linux onlyvault_sync.snapshot_scriptand the conventional profile pathvault_sync.snapshot_profile=/etc/vault-sync/profiles/<host>-snapshotter.env. The snapshotter profile is the operational authority for its host-local rclone alias and should containCLOUD_REMOTE=<remote:path>. Managed leaf hosts should setWIKI_REMOTE=<remote:path>in~/.skillwiki/.env. Rclone remote names are local aliases and may legitimately differ between hosts. Status treats a missing remote as unconfigured/unknown; it must not probe a guessed alias and report a false outage. - Write runtime inventory for successful non-dry-run full installs:
$(platform_share_dir)/runtime-manifest.json(package/installer version, host role, SHA-256 hashes of installed scripts and LaunchAgents plists). --dry-runmode: print the entire plan (paths, commands, fleet.yaml diff) but execute nothing.
FUSE-Only Mode
Use --mode=fuse-only for Linux hosts where the wiki path is an rclone S3 FUSE mount and not a git-backed vault. This mode is intended for pvelxc/LXC consumers like /root/wiki -> wiki-s3:cloud/wiki.
FUSE-only mode:
- Requires Linux and verifies
findmnt -T <vault-path>reportsfuse.rclone. - In execute mode, requires an active
rclone mountprocess and validates the helper dry-run before completing. - Copies only
wiki-fuse-refresh.shandscripts/lib/. - Installs only
wiki-fuse-refresh.serviceandwiki-fuse-refresh.timer. - Supports user systemd units under
~/.config/systemd/userand root/system units under/etc/systemd/system. - Sets
HOMEin the systemd service so logs land under$(platform_log_dir). - Sets
vault_sync.fuse_refresh_enabled=true,vault_sync.fuse_refresh_interval=300s, andvault_sync.fuse_max_dir_cache=<duration>. - Does not install or enable
wiki-pushorwiki-fetch. - Does not mark
vault_sync.installed=true; that key is reserved for the full role workflow.
Do not use FUSE-only mode on a normal git-backed wiki vault.
Idempotency
Re-running the install upgrades scripts in-place. Existing scheduler units are reloaded, not duplicated.
On macOS, launchd install is an observed-state transaction:
- Domain probe (
launchctl print) - Bootout until the label is absent
- Enable
- Bootstrap
EIO from launchctl is not special — it is reconciled only when launchctl print shows the label present (exit status only; no field parsing of launchctl output). Candidate plists are staged and moved into place only after registration is proven absent.
Rollback copies live under $(platform_cache_dir)/install-rollback/<timestamp>/ and are retained after success until a later status/live-verify step clears them. Do not delete rollback dirs merely because bootstrap returned success. The installer records whether each previous macOS plist passed integrity validation; only a valid previous plist is eligible for automatic restoration after a failed bootstrap. Invalid prior files remain diagnostic evidence and are never reinstalled as rollback.
After a successful non-dry-run install, $(platform_share_dir)/runtime-manifest.json records package/installer version, host role, and SHA-256 hashes of installed scripts and LaunchAgents plists.
Typical share / cache / log roots:
| OS | platform_share_dir |
platform_cache_dir |
platform_log_dir |
|---|---|---|---|
| macOS | ~/Library/Application Support/vault-sync |
~/Library/Caches/vault-sync |
~/Library/Logs |
| Linux | ~/.local/share/vault-sync |
~/.cache/vault-sync |
~/.local/state/vault-sync/log |
Pull log path for live verification: $(platform_log_dir)/wiki-pull.log (macOS: ~/Library/Logs/wiki-pull.log).
Attended verification checklist (rollout proof)
Repository tests green ≠ install work complete. After deploying to a real host, run this attended checklist before treating the install as done:
- From package source (not only a packaged tarball), reinstall leaf role:
bash packages/vault-sync/skills/vault-sync-install/install.sh --role leaf --execute - Prove runtime hashes match package sources:
Expectbash packages/vault-sync/skills/vault-sync-status/status.sh --read-onlyvault_sync_jobs_enabled=passandvault_sync_runtime_match=pass(valid on-disk LaunchAgent plists plus a parseableruntime-manifest.json). - Wait for a scheduled
wiki-fetchcycle, or kickstart it / run the pull helper once so a live pull executes on the installed scripts. - Confirm the pull log shows helper-owned journal lines (
op=…) and no legacywiki-pull auto-stashmessages:# macOS grep -E 'op=|auto-stash' "$HOME/Library/Logs/wiki-pull.log" # Linux # grep -E 'op=|auto-stash' "$HOME/.local/state/vault-sync/log/wiki-pull.log" - Touch the live-verify marker only after step 4 succeeds:
Status reports# Exact path: $(platform_share_dir)/live-verify.ok # macOS: ~/Library/Application Support/vault-sync/live-verify.ok # Linux: ~/.local/share/vault-sync/live-verify.ok touch "$HOME/Library/Application Support/vault-sync/live-verify.ok" # macOS # touch "$HOME/.local/share/vault-sync/live-verify.ok" # Linuxvault_sync_live_verify=passonly when this marker exists. Status never writes the marker itself.
Completion gate
- Green
npm run test:vault-sync(or CI) is necessary but not sufficient. - Update the vault work item
status: completedonly after live scheduled-cycle evidence is recorded in the work retro (attended checklist above, includinglive-verify.ok). - Do not mark the work item completed from repository tests alone.
Guardrails
- sg01 is production. Never run this skill with
--executeon sg01 from CI. Only hand-migration with human supervision. - Single-writer-git. Only one host per fleet may be snapshotter. Fleet validation enforces this.
- Protected hosts. If the host is marked
protected: truein fleet.yaml, the install proceeds (installing is non-destructive) but prints a warning.
Package root (monorepo vs plugin)
Resolve the vault-sync package root before invoking the companion script:
- Monorepo checkout:
<repo>/packages/vault-sync - Claude / Codex plugin install: directory that contains
skills/vault-sync-install(plugin cache) - Installed share (
~/Library/Application Support/vault-syncor~/.local/share/vault-sync) is runtime only — not package source for drift checks
# monorepo
bash packages/vault-sync/skills/vault-sync-install/install.sh --role leaf --dry-run
# plugin root (cwd = vault-sync plugin package)
bash skills/vault-sync-install/install.sh --role leaf --dry-run
Staged / rsync install (honest runtime-manifest without monorepo git tree):
VS_PACKAGE_VERSION=0.9.60 \
VS_PACKAGE_COMMIT=<source-git-sha> \
bash skills/vault-sync-install/install.sh --role snapshotter --service-scope system --execute
# or flags (same env SSOT):
# bash skills/vault-sync-install/install.sh --package-version 0.9.60 --package-commit <sha> ...
See docs/offline-host-runbook.md for the full staged-deploy matrix and satellite-only host notes.
Execution
# Companion script (interactive)
bash packages/vault-sync/skills/vault-sync-install/install.sh --role leaf --dry-run
bash packages/vault-sync/skills/vault-sync-install/install.sh --role snapshotter --service-scope system --dry-run
bash packages/vault-sync/skills/vault-sync-install/install.sh --mode fuse-only --vault-path /root/wiki --service-scope system --dry-run
# Companion script (headless / CI)
VS_ROLE=leaf VS_DRY_RUN=1 bash packages/vault-sync/skills/vault-sync-install/install.sh
VS_ROLE=snapshotter VS_SERVICE_SCOPE=system VS_DRY_RUN=1 bash packages/vault-sync/skills/vault-sync-install/install.sh
VS_MODE=fuse-only VS_VAULT_PATH=/root/wiki VS_SERVICE_SCOPE=system VS_DRY_RUN=1 bash packages/vault-sync/skills/vault-sync-install/install.sh
VS_PACKAGE_VERSION=0.9.60 VS_PACKAGE_COMMIT=<sha> VS_ROLE=snapshotter VS_SERVICE_SCOPE=system VS_DRY_RUN=1 bash packages/vault-sync/skills/vault-sync-install/install.sh