Add ssr update subcommand for self-update
- bin/ssr-update: fast-forward `git pull` on the clone with safety guards (dirty tree, detached HEAD, divergent history all abort). - Re-renders & reloads the launchd plist when launchd/ or bin/ssr* changed AND the schedule is currently active, so the cron picks up any plist template updates automatically. - Reports VERSION delta, commit log, and new config keys introduced in config/ssr.conf.example so users can opt-in via `ssr config edit`. - `--dry-run` shows would-be changes without pulling or touching launchd. - Wired into bin/ssr dispatcher + README updated (Updating section, layout entry, command help line). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -49,6 +49,7 @@ export PATH="$HOME/.local/bin:$PATH"
|
||||
ssr install First-time install: link binary, seed config, schedule sync
|
||||
ssr sync One-shot backup → cloud
|
||||
ssr restore [Brewfile] Restore the machine; Brewfile defaults to the cloud copy
|
||||
ssr update [--dry-run] Pull latest ssr code, refresh launchd if needed
|
||||
ssr schedule on|off Enable/disable the daily launchd job
|
||||
ssr config show|edit Show or edit the active config
|
||||
ssr status Show config, cloud target, last sync, schedule state
|
||||
@@ -146,6 +147,30 @@ log show --predicate 'process == "ssr"' --last 1h
|
||||
|
||||
---
|
||||
|
||||
## Updating
|
||||
|
||||
Updates happen on three independent layers:
|
||||
|
||||
**Tool code (this repo).** Self-update via `ssr update`:
|
||||
|
||||
```bash
|
||||
ssr update # git pull --ff-only on the clone, then refresh side-effects
|
||||
ssr update --dry-run # show what would change, don't pull
|
||||
```
|
||||
|
||||
`ssr update` will:
|
||||
|
||||
- Refuse if the working tree is dirty or HEAD is detached.
|
||||
- Fast-forward only — never rewrites history.
|
||||
- Re-render and reload the launchd plist if `launchd/` or `bin/ssr*` changed *and* the schedule is currently active.
|
||||
- Print added config keys when `config/ssr.conf.example` gained new options.
|
||||
|
||||
**Backup data (cloud drive).** Refreshed automatically by the scheduled `ssr sync`. The previous `Brewfile` is preserved as `Brewfile.bak`; deeper history is provided by the cloud provider's built-in version retention (iCloud Drive, Dropbox, …). Trigger an on-demand sync with `ssr sync`.
|
||||
|
||||
**Dependencies (Homebrew, mackup).** `ssr sync` runs `brew upgrade` and `brew cleanup` when `SSR_BREW_UPGRADE=true` / `SSR_BREW_CLEANUP=true` (both default `true`). Set them to `false` in `ssr.conf` to keep snapshots without auto-upgrading installed packages.
|
||||
|
||||
---
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
@@ -154,6 +179,7 @@ log show --predicate 'process == "ssr"' --last 1h
|
||||
├── bin/
|
||||
│ ├── ssr # CLI dispatcher
|
||||
│ ├── ssr-install # Local install + scheduling
|
||||
│ ├── ssr-update # Self-update (git pull + launchd refresh)
|
||||
│ ├── ssr-sync # Backup to cloud
|
||||
│ ├── ssr-restore # Rebuild a Mac from the backup
|
||||
│ ├── ssr-schedule # launchd on/off/status
|
||||
|
||||
@@ -29,6 +29,7 @@ Commands:
|
||||
install First-time install: links binary, seeds config, schedules sync.
|
||||
sync Run a backup: brew bundle dump + mackup backup → cloud.
|
||||
restore [Brewfile] Restore a machine: brew bundle + mackup restore + macOS defaults.
|
||||
update [--dry-run] Pull latest ssr code, refresh launchd if needed.
|
||||
schedule on|off Enable/disable the scheduled launchd sync job.
|
||||
config show|edit Print or edit the active config file.
|
||||
status Show last sync time, cloud target, and scheduled state.
|
||||
@@ -60,7 +61,7 @@ done
|
||||
|
||||
cmd="$1"; shift
|
||||
case "$cmd" in
|
||||
install|sync|restore|schedule|config|status)
|
||||
install|sync|restore|update|schedule|config|status)
|
||||
target="$SSR_BIN_DIR/ssr-$cmd"
|
||||
[[ -x "$target" ]] || ssr::die "Subcommand not executable: $target"
|
||||
exec "$target" "$@"
|
||||
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# ssr-update — pull the latest ssr code from origin and refresh side-effects.
|
||||
#
|
||||
# Behavior:
|
||||
# 1. Verify the repo is a clean git checkout on a real branch.
|
||||
# 2. git fetch + fast-forward pull. Bail if FF would be impossible.
|
||||
# 3. Report VERSION delta.
|
||||
# 4. If the launchd template or any bin/ file changed and the schedule is
|
||||
# currently active, re-render and reload it.
|
||||
# 5. If config/ssr.conf.example gained new keys, warn the user.
|
||||
|
||||
set -euo pipefail
|
||||
SSR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=../lib/common.sh
|
||||
source "$SSR_ROOT/lib/common.sh"
|
||||
|
||||
DRY_RUN=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-n|--dry-run) DRY_RUN=true; shift ;;
|
||||
-h|--help)
|
||||
cat <<EOF
|
||||
Usage: ssr update [--dry-run]
|
||||
|
||||
Pulls the latest commit on the current branch and re-applies side effects:
|
||||
- Re-renders & reloads the launchd plist if it changed (when schedule is active).
|
||||
- Reports added config keys in config/ssr.conf.example.
|
||||
EOF
|
||||
exit 0 ;;
|
||||
*) ssr::die "Unknown flag: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
ssr::require_cmd git
|
||||
|
||||
# --- 1. preflight ---------------------------------------------------------
|
||||
[[ -d "$SSR_ROOT/.git" ]] \
|
||||
|| ssr::die "Not a git checkout: $SSR_ROOT (re-clone the repo to enable updates)"
|
||||
|
||||
cd "$SSR_ROOT"
|
||||
|
||||
branch="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)"
|
||||
[[ -n "$branch" ]] || ssr::die "Detached HEAD — checkout a branch first"
|
||||
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
ssr::die "Working tree has local changes — commit, stash, or revert before updating"
|
||||
fi
|
||||
|
||||
origin_url="$(git remote get-url origin 2>/dev/null || true)"
|
||||
[[ -n "$origin_url" ]] || ssr::die "No 'origin' remote configured"
|
||||
ssr::log "Updating from $origin_url ($branch)"
|
||||
|
||||
# --- 2. fetch + ff pull ---------------------------------------------------
|
||||
old_sha="$(git rev-parse HEAD)"
|
||||
old_version="$(cat VERSION 2>/dev/null || echo unknown)"
|
||||
|
||||
if $DRY_RUN; then
|
||||
git fetch --quiet origin "$branch"
|
||||
new_sha="$(git rev-parse "origin/$branch")"
|
||||
else
|
||||
git fetch --quiet origin "$branch"
|
||||
new_sha="$(git rev-parse "origin/$branch")"
|
||||
if [[ "$old_sha" == "$new_sha" ]]; then
|
||||
ssr::ok "Already up to date ($(git rev-parse --short HEAD))"
|
||||
exit 0
|
||||
fi
|
||||
if ! git merge-base --is-ancestor "$old_sha" "$new_sha"; then
|
||||
ssr::die "Cannot fast-forward (history diverged). Resolve manually: git pull --rebase"
|
||||
fi
|
||||
git merge --ff-only --quiet "origin/$branch"
|
||||
fi
|
||||
|
||||
new_version="$(cat VERSION 2>/dev/null || echo unknown)"
|
||||
|
||||
if [[ "$old_sha" == "$new_sha" ]]; then
|
||||
ssr::ok "No changes available."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- 3. summarize -----------------------------------------------------
|
||||
ssr::ok "Updated $(git rev-parse --short "$old_sha") → $(git rev-parse --short "$new_sha")"
|
||||
if [[ "$old_version" != "$new_version" ]]; then
|
||||
ssr::log "Version: $old_version → $new_version"
|
||||
fi
|
||||
ssr::log "Changes:"
|
||||
git --no-pager log --oneline "$old_sha..$new_sha" | sed 's/^/ /'
|
||||
|
||||
$DRY_RUN && { ssr::log "(dry-run: no side effects executed)"; exit 0; }
|
||||
|
||||
# --- 4. launchd refresh -----------------------------------------------
|
||||
changed_files="$(git --no-pager diff --name-only "$old_sha" "$new_sha")"
|
||||
plist_changed=false
|
||||
case "$changed_files" in
|
||||
*launchd/*|*bin/ssr*) plist_changed=true ;;
|
||||
esac
|
||||
|
||||
label="${SSR_LAUNCHD_LABEL:-de.surke.ssr.sync}"
|
||||
if $plist_changed && launchctl list 2>/dev/null | grep -q "$label"; then
|
||||
ssr::log "launchd-relevant files changed → reloading schedule"
|
||||
"$SSR_ROOT/bin/ssr-schedule" on
|
||||
fi
|
||||
|
||||
# --- 5. config drift hint -------------------------------------------------
|
||||
example="$SSR_ROOT/config/ssr.conf.example"
|
||||
user_cfg="${SSR_CONFIG:-$HOME/.config/ssr/ssr.conf}"
|
||||
if [[ -f "$user_cfg" && -f "$example" ]]; then
|
||||
new_keys="$(comm -23 \
|
||||
<(grep -E '^[A-Z_][A-Z0-9_]*=' "$example" | cut -d= -f1 | sort -u) \
|
||||
<(grep -E '^[A-Z_][A-Z0-9_]*=' "$user_cfg" | cut -d= -f1 | sort -u))"
|
||||
if [[ -n "$new_keys" ]]; then
|
||||
ssr::warn "New config keys available in $example:"
|
||||
echo "$new_keys" | sed 's/^/ /'
|
||||
echo "Review with: ssr config edit"
|
||||
fi
|
||||
fi
|
||||
|
||||
ssr::ok "Update complete."
|
||||
Reference in New Issue
Block a user