summaryrefslogtreecommitdiff
path: root/update.sh
diff options
context:
space:
mode:
Diffstat (limited to 'update.sh')
-rwxr-xr-xupdate.sh167
1 files changed, 167 insertions, 0 deletions
diff --git a/update.sh b/update.sh
new file mode 100755
index 0000000..8a7d108
--- /dev/null
+++ b/update.sh
@@ -0,0 +1,167 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+QUICK=no
+DO_COMMIT=no
+INSTALL_HOOK=no
+DRY_RUN=no
+
+usage() {
+ cat <<EOF
+Sync live system state back into the dotfiles repo.
+
+Usage: $0 [options]
+
+ -q, --quick non-interactive, minimal output (used by postsync hook)
+ -c, --commit git add -A && git commit after syncing
+ -h, --hook-install write /etc/portage/postsync.d hook (auto after each emerge)
+ -n, --dry-run show what would change without copying
+ --help show this help
+EOF
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -q|--quick) QUICK=yes ;;
+ -c|--commit) DO_COMMIT=yes ;;
+ -h|--hook-install) INSTALL_HOOK=yes ;;
+ -n|--dry-run) DRY_RUN=yes ;;
+ --help) usage; exit 0 ;;
+ *) echo "unknown option: $1" >&2; usage >&2; exit 2 ;;
+ esac
+ shift
+done
+
+log() { printf '\033[1;32m[sync]\033[0m %s\n' "$*"; }
+warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; }
+
+root_cp() {
+ if [[ "$DRY_RUN" == "yes" ]]; then
+ log " would copy $1 -> $2"
+ return 0
+ fi
+ if [[ $EUID -eq 0 ]]; then
+ cp "$1" "$2"
+ elif cp "$1" "$2" 2>/dev/null; then
+ : # readable as current user — done
+ elif command -v sudo >/dev/null 2>&1; then
+ sudo cp "$1" "$2"
+ else
+ warn "need root to copy $1"
+ return 1
+ fi
+}
+
+rsync_portage() {
+ # -L: dereference symlinks so /etc/portage symlinks back into the repo
+ # are copied as content, never re-created as self-referencing symlinks.
+ # Broken symlinks are excluded (rsync -L aborts on dangling referents).
+ local exfile
+ exfile="$(mktemp)"
+ printf '.gitkeep\nmake.profile\n' > "$exfile"
+ find /etc/portage -type l ! -exec test -e {} \; -printf '%P\n' >> "$exfile"
+
+ local -a opts=(-aL --delete "--exclude-from=$exfile")
+ [[ "$DRY_RUN" == "yes" ]] && opts+=(-n)
+ log "portage /etc/portage -> repo/portage/etc/portage"
+ rsync "${opts[@]}" /etc/portage/ "$REPO_DIR/portage/etc/portage/"
+ rm -f "$exfile"
+}
+
+sync_portage() {
+ rsync_portage
+
+ if [[ -e /etc/portage/make.profile ]]; then
+ log "profile: $(readlink /etc/portage/make.profile)"
+ if [[ "$DRY_RUN" != "yes" ]]; then
+ printf '%s\n' "$(readlink /etc/portage/make.profile)" > "$REPO_DIR/portage/PROFILE"
+ else
+ log " would write: portage/PROFILE"
+ fi
+ fi
+
+ log "world /var/lib/portage/world -> repo"
+ if [[ -L /var/lib/portage/world ]] \
+ && [[ "$(readlink -f /var/lib/portage/world)" == "$REPO_DIR/portage/var/lib/portage/world" ]]; then
+ log " already a symlink into the repo — no copy needed"
+ else
+ root_cp /var/lib/portage/world "$REPO_DIR/portage/var/lib/portage/world"
+ fi
+
+ if [[ -s /var/lib/portage/world_sets ]]; then
+ log "world_sets /var/lib/portage/world_sets -> repo"
+ root_cp /var/lib/portage/world_sets "$REPO_DIR/portage/var/lib/portage/world_sets"
+ fi
+}
+
+sync_system() {
+ log "fstab /etc/fstab -> repo/fstab/etc/fstab"
+ root_cp /etc/fstab "$REPO_DIR/fstab/etc/fstab"
+
+ if [[ -f /etc/greetd/config.toml ]]; then
+ log "greetd /etc/greetd/config.toml -> repo/config.toml"
+ root_cp /etc/greetd/config.toml "$REPO_DIR/config.toml"
+ fi
+}
+
+install_hook() {
+ local hook=/etc/portage/postsync.d/update-dotfiles
+ if [[ "$DRY_RUN" == "yes" ]]; then
+ log " would write: $hook"
+ return 0
+ fi
+ log "writing $hook"
+ if [[ $EUID -eq 0 ]]; then
+ mkdir -p /etc/portage/postsync.d
+ cat > "$hook" <<EOF
+#!/usr/bin/env bash
+# generated by update.sh --hook-install — keeps dotfiles fresh after each emerge
+exec "$REPO_DIR/update.sh" --quick
+EOF
+ chmod +x "$hook"
+ elif command -v sudo >/dev/null 2>&1; then
+ sudo tee "$hook" >/dev/null <<EOF
+#!/usr/bin/env bash
+# generated by update.sh --hook-install — keeps dotfiles fresh after each emerge
+exec "$REPO_DIR/update.sh" --quick
+EOF
+ sudo chmod +x "$hook"
+ else
+ warn "need root to write $hook"
+ fi
+}
+
+commit() {
+ cd "$REPO_DIR"
+ if [[ -n "$(git status --porcelain)" ]]; then
+ git add -A
+ git commit -q -m "sync system state: $(date '+%Y-%m-%d %H:%M')"
+ log "committed."
+ else
+ log "nothing to commit."
+ fi
+}
+
+main() {
+ if [[ "$DRY_RUN" == "yes" ]]; then
+ log "dry-run mode — showing changes, nothing written"
+ fi
+ sync_portage
+ sync_system
+ [[ "$INSTALL_HOOK" == "yes" ]] && install_hook
+
+ if [[ "$DO_COMMIT" == "yes" ]]; then
+ commit
+ elif [[ "$DRY_RUN" != "yes" && "$QUICK" != "yes" ]]; then
+ cd "$REPO_DIR"
+ if [[ -n "$(git status --porcelain)" ]]; then
+ warn "dotfiles changed — review and commit (or rerun with --commit)."
+ git status --short
+ else
+ log "repo is up to date."
+ fi
+ fi
+}
+
+main "$@" \ No newline at end of file