NixDaemon

NixOS pentest workstation as one flake — IceBreaker's successor
git clone https://git.daemon-sec.xyz/NixDaemon.git
Log | Files | Refs | README

commit eaad237c760efe648c241879758088f3e5a46a0b
parent de158dfad01b94e31ed96cdab01748dda83c5fa2
Author: DAEMON <zer0sec.xp@icloud.com>
Date:   Sat, 10 Oct 2026 12:35:58 +0100

feat(git-site): sync git.daemon-sec.xyz automatically

- git-site-sync: auto (sync when a mirrored repo's main moved on GitLab),
  now, status, log. One git ls-remote per repo, so checking is cheap; the
  build and upload run only on a change. flock keeps runs from overlapping;
  a failure raises a desktop notification.
- A systemd user timer runs it every 15 minutes (persistent across sleep).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Diffstat:
Mmodules/home/default.nix | 1+
Amodules/home/git-site.nix | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amodules/home/git-site/git-site-sync.sh | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 160 insertions(+), 0 deletions(-)

diff --git a/modules/home/default.nix b/modules/home/default.nix @@ -22,6 +22,7 @@ prompt # starship prompt and fastfetch card, Rosé Pine, NixOS logo fan # `fan`: status/watch without root, max/auto with a clamp watchdog htb-shell # $TARGET/$BOX in every terminal, and in the prompt + git-site # git-site-sync: keep git.daemon-sec.xyz in step with GitLab (15-min timer) ssh # the ssh key (sops) and ~/.ssh/config gpg # the GPG main key (public in-repo, secret via sops) and gpg.conf git # git identity, signing with the main key, delta diff --git a/modules/home/git-site.nix b/modules/home/git-site.nix @@ -0,0 +1,68 @@ +# modules/home/git-site.nix — keep git.daemon-sec.xyz in step with GitLab, +# automatically. +# +# git-site-sync status last sync, GitLab's heads, the next timer run +# git-site-sync now force a sync (after a theme or build change) +# git-site-sync log what the last runs did +# +# A systemd user timer runs `git-site-sync auto` every 15 minutes while you +# are logged in. It asks GitLab for the head of `main` in each mirrored repo +# (NixDaemon, daemon-sec-cheatsheet, daemon-sec-lotl — the list is +# ~/git/daemon-sec/script/git-site/repos.json) and only when one has moved +# runs `npm run git-site:sync`, which rebuilds the pages and uploads what +# changed to the daemon-sec-git bucket. Failures raise a desktop notification. +# +# It uses what an interactive shell has: the ssh config (the private +# cheatsheet repo is fetched over SSH with the sops key, see ssh.nix) and +# wrangler's own login in ~/.config/.wrangler. If wrangler's login ever +# expires, `npx wrangler login` in ~/git/daemon-sec fixes it. +{ ... }: +{ + flake.homeModules.git-site = + { pkgs, lib, ... }: + let + tool = pkgs.writeShellApplication { + name = "git-site-sync"; + runtimeInputs = with pkgs; [ + git + openssh + jq + nodejs + python3 + util-linux # flock + coreutils + diffutils + gnused + libnotify # notify-send + systemd + ]; + text = builtins.readFile ./git-site/git-site-sync.sh; + }; + in + { + home.packages = [ tool ]; + + systemd.user.services.git-site-sync = { + Unit = { + Description = "Sync git.daemon-sec.xyz from GitLab when a mirrored repo changed"; + After = [ "network-online.target" ]; + }; + Service = { + Type = "oneshot"; + ExecStart = "${lib.getExe tool} auto"; + Nice = 10; + IOSchedulingClass = "idle"; + }; + }; + + systemd.user.timers.git-site-sync = { + Unit.Description = "Check the mirrored repos every 15 minutes"; + Timer = { + OnCalendar = "*:0/15"; + Persistent = true; # catch up after the laptop was asleep + RandomizedDelaySec = "90"; + }; + Install.WantedBy = [ "timers.target" ]; + }; + }; +} diff --git a/modules/home/git-site/git-site-sync.sh b/modules/home/git-site/git-site-sync.sh @@ -0,0 +1,91 @@ +# git-site-sync — keep git.daemon-sec.xyz in step with GitLab. +# +# git-site-sync same as `auto` +# git-site-sync auto sync only if a mirrored repo's main moved (the timer runs this) +# git-site-sync now sync regardless (after editing the theme or build script) +# git-site-sync status last sync, what GitLab has now, the timer +# git-site-sync log the last runs, from the journal +# +# The repos are whatever ~/git/daemon-sec/script/git-site/repos.json lists +# (NixDaemon, daemon-sec-cheatsheet, daemon-sec-lotl). Checking is one +# `git ls-remote` per repo — a few hundred bytes — so the timer can run +# often; the build and upload (`npm run git-site:sync`, which itself only +# sends changed files) happen only when a head actually moved. + +SITE_REPO=${GIT_SITE_REPO:-$HOME/git/daemon-sec} +CONFIG=$SITE_REPO/script/git-site/repos.json +STATE=${XDG_STATE_HOME:-$HOME/.local/state}/git-site +mkdir -p "$STATE" + +# The npm script calls `nix shell` and `npx`: give a systemd user service the +# system and per-user profiles that an interactive shell would have. +export PATH="$PATH:/run/current-system/sw/bin:/etc/profiles/per-user/$USER/bin" + +die() { echo "git-site-sync: $*" >&2; exit 1; } +[ -f "$CONFIG" ] || die "no $CONFIG (set GIT_SITE_REPO if the site repo moved)" + +# "<name> <commit>" per repo, from GitLab. Fails loudly if any repo cannot be +# read, so a network blip never looks like "nothing changed". +heads() { + local name url sha + jq -r '.repos[] | "\(.name) \(.fetch)"' "$CONFIG" | while read -r name url; do + sha=$(git ls-remote "$url" refs/heads/main | cut -f1) || return 1 + [ -n "$sha" ] || { echo "git-site-sync: no main on $url" >&2; return 1; } + printf '%s %s\n' "$name" "$sha" + done +} + +notify() { + command -v notify-send >/dev/null 2>&1 && notify-send -a git-site-sync -u "${2:-normal}" "git.daemon-sec.xyz" "$1" 2>/dev/null || true +} + +sync() { + local new=$1 + echo "git-site-sync: syncing…" + if (cd "$SITE_REPO" && npm run -s git-site:sync); then + printf '%s\n' "$new" > "$STATE/heads" + date -Is > "$STATE/last-sync" + echo "git-site-sync: done" + else + notify "Sync failed — git-site-sync log" critical + die "sync failed (git-site-sync log for the details)" + fi +} + +# One run at a time: the timer and a manual `now` must not interleave uploads. +exec 9>"$STATE/lock" +case "${1:-auto}" in + auto | now) + flock -n 9 || { echo "git-site-sync: another sync is running"; exit 0; } + new=$(heads) || die "could not read GitLab (offline?); nothing changed" + if [ "${1:-auto}" = auto ] && [ "$new" = "$(cat "$STATE/heads" 2>/dev/null)" ]; then + echo "git-site-sync: up to date" + exit 0 + fi + if [ -f "$STATE/heads" ]; then + diff <(cat "$STATE/heads") <(printf '%s\n' "$new") | sed -n 's/^> / moved: /p' || true + fi + sync "$new" + ;; + status) + echo "last sync: $(cat "$STATE/last-sync" 2>/dev/null || echo never)" + echo "synced heads:" + sed 's/^/ /' "$STATE/heads" 2>/dev/null || echo " (none yet)" + if new=$(heads); then + if [ "$new" = "$(cat "$STATE/heads" 2>/dev/null)" ]; then echo "GitLab: no changes since"; else + echo "GitLab now:"; printf '%s\n' "$new" | sed 's/^/ /'; fi + fi + systemctl --user list-timers git-site-sync.timer --no-pager 2>/dev/null | head -2 + ;; + log) + journalctl --user -u git-site-sync.service -n "${2:-60}" --no-pager + ;; + -h | --help | help) + echo "usage: git-site-sync [auto|now|status|log [n]]" + echo " auto sync if a mirrored repo's main moved (what the timer runs)" + echo " now sync regardless" + echo " status last sync, GitLab's heads, the timer" + echo " log the last runs from the journal" + ;; + *) die "unknown command '$1' (auto, now, status, log)" ;; +esac