From 1d656769ce5e170151ec6a0fd20003bd4d7f5654 Mon Sep 17 00:00:00 2001 From: jaytbarimbao-collab <300663773+jaytbarimbao-collab@users.noreply.github.com> Date: Sun, 12 Jul 2026 10:35:21 -0400 Subject: [PATCH] feat(miner): add a systemd bare-host service example for the miner loop Add systemd/gittensory-miner.service.example so an operator can run the miner continuously on a plain Linux host without Docker -- the non-Docker counterpart to DEPLOYMENT.md's fleet-mode docker-compose invocation. Because `gittensory-miner loop` (#5135) is a long-running daemon that schedules its own cycles, this is a persistent Type=simple service with Restart=on-failure, not a oneshot unit + .timer (contrast the periodic gittensory-docker-prune.*.example). Adds a "Bare-host (systemd, no Docker)" section to DEPLOYMENT.md pointing at it. Template/docs only; no runtime logic touched. Closes #5197 --- packages/gittensory-miner/DEPLOYMENT.md | 15 ++++++++ systemd/gittensory-miner.service.example | 49 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 systemd/gittensory-miner.service.example diff --git a/packages/gittensory-miner/DEPLOYMENT.md b/packages/gittensory-miner/DEPLOYMENT.md index 4e38dffa5c..6fba10145e 100644 --- a/packages/gittensory-miner/DEPLOYMENT.md +++ b/packages/gittensory-miner/DEPLOYMENT.md @@ -80,6 +80,21 @@ docker compose -f docker-compose.miner.yml up -d --build **Scaling to N parallel workers.** `docker compose -f docker-compose.miner.yml up -d --scale miner=N` gives every replica the **same** `miner-data` volume — and the miner's SQLite ledgers are **not** safe for concurrent access, so N replicas on one volume will contend/corrupt. To run N **isolated** workers, give each its own state: run N separate compose projects (`docker compose -p miner-1 …`, `-p miner-2 …` — `-p` namespaces the volume) or point each at a distinct `GITTENSORY_MINER_CONFIG_DIR` on its own mount. For built-in isolated horizontal scaling, use the Kubernetes StatefulSet in [`k8s/`](../../k8s/) (per-pod volumes). +## Bare-host (systemd, no Docker) + +To run the miner continuously on a plain Linux host without Docker, supervise `gittensory-miner loop` — the autonomous discover → attempt → manage daemon (#5135) — with systemd. [`systemd/gittensory-miner.service.example`](../../systemd/gittensory-miner.service.example) is a ready-to-adapt persistent unit; its header carries the full install steps: + +```sh +npm install -g @jsonbored/gittensory-miner +gittensory-miner init +sudo cp systemd/gittensory-miner.service.example /etc/systemd/system/gittensory-miner.service +sudo $EDITOR /etc/systemd/system/gittensory-miner.service # set User / WorkingDirectory / ExecStart / secrets +sudo systemctl daemon-reload +sudo systemctl enable --now gittensory-miner.service +``` + +Because `loop` is a **long-running daemon that schedules its own cycles**, it is a persistent `Type=simple` service (with `Restart=on-failure`) — **not** a oneshot unit driven by a `.timer`, unlike the periodic `gittensory-docker-prune.*.example` hygiene job in [`systemd/`](../../systemd/). Keep `GITHUB_TOKEN` (and any coding-agent credentials) in a root-owned `0600` `EnvironmentFile`, never in the unit file. Follow the loop with `journalctl -u gittensory-miner -f`; `systemctl stop` sends SIGTERM, which the loop handles cleanly at its next kill-switch check. + ## Invariants - Core miner bookkeeping (claims, plans, queues, ledgers) works offline after install. diff --git a/systemd/gittensory-miner.service.example b/systemd/gittensory-miner.service.example new file mode 100644 index 0000000000..2250ff1ef7 --- /dev/null +++ b/systemd/gittensory-miner.service.example @@ -0,0 +1,49 @@ +# Runs `gittensory-miner loop` -- the autonomous discover -> attempt -> manage supervising daemon (#5135) -- +# continuously on a bare host, no Docker required. This is the non-Docker counterpart to DEPLOYMENT.md's +# fleet-mode docker-compose invocation. +# +# WHY A PERSISTENT SERVICE, NOT A .timer: `gittensory-miner loop` is a long-running daemon that schedules its +# own cycles internally (kill-switch check -> run-loop boundary gate -> attempt -> loop-closure -> reentry), so +# it is a Type=simple service supervised by systemd, NOT a oneshot unit fired by a .timer. (Contrast +# gittensory-docker-prune.service.example, which IS a genuine periodic batch and therefore does pair with a +# .timer.) See DEPLOYMENT.md's "Bare-host (systemd, no Docker)" section. +# +# Install (adjust the placeholders below to your host): +# npm install -g @jsonbored/gittensory-miner # puts the `gittensory-miner` bin on PATH +# gittensory-miner init # one-time local state-dir setup +# gittensory-miner doctor # confirm Node/state-dir/SQLite readiness +# sudo cp systemd/gittensory-miner.service.example /etc/systemd/system/gittensory-miner.service +# sudo $EDITOR /etc/systemd/system/gittensory-miner.service # fix User/WorkingDirectory/ExecStart/env +# sudo systemctl daemon-reload +# sudo systemctl enable --now gittensory-miner.service +# journalctl -u gittensory-miner -f # follow the loop's output + +[Unit] +Description=Gittensory miner autonomous loop (discover -> attempt -> manage) +# The loop makes GitHub/API calls, so wait for the network to be routable before starting. +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +# REQUIRED: run as a dedicated non-root user that owns the state directory below. +User=gittensory +# REQUIRED: point this at a writable working directory for the miner (repo clones, worktrees, local state). +WorkingDirectory=/var/lib/gittensory-miner +# REQUIRED: absolute path to the globally-installed bin (see `which gittensory-miner`; typically +# /usr/bin or /usr/local/bin, or an nvm/asdf shim path). +ExecStart=/usr/bin/gittensory-miner loop +# A daemon should come back after a transient crash; the loop is idempotent across restarts. +Restart=on-failure +RestartSec=30 +# REQUIRED: GITHUB_TOKEN (and any coding-agent credentials) must be present in the service's environment. +# Keep secrets out of this unit file -- use a root-owned 0600 EnvironmentFile instead: +# EnvironmentFile=/etc/gittensory-miner.env +# Optional overrides (see the package README for the full list): +# Environment=GITTENSORY_MINER_CONFIG_DIR=/var/lib/gittensory-miner/config +# Environment=MINER_CODING_AGENT_PROVIDER=claude-code +# Kill switch: `systemctl stop` sends SIGTERM; the loop checks its kill switch each cycle and exits cleanly. +TimeoutStopSec=120 + +[Install] +WantedBy=multi-user.target