From 331efe95de1709c639d8ef3529be5640b3805ca0 Mon Sep 17 00:00:00 2001 From: Ulises Chavarria Date: Mon, 22 Jun 2026 02:07:06 -0700 Subject: [PATCH 1/9] docs(forgejo): document Mac mini runner; move server to Tailscale sidecar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Mac mini Forgejo Actions runner had no docs and broke after the server moved off ollie-server:3300 to its own Tailscale node (forgejo:3000) without the runner config being updated. macOS/forgejo-runner/: install/verify/run scripts (binary built from source — Forgejo ships no macOS builds), config + LaunchAgent templates, and a README covering setup and the connection-refused failure mode. linux-server/forgejo/: rewrite docker-compose.yml to the per-service Tailscale sidecar model (own tailnet device, no host ports, :3000, SSH on 22) matching the live deployment; reconcile the now-stale :3300/:2222 references in the homepage widget, post-install.md, and README. Co-Authored-By: Claude Opus 4.8 --- linux-server/README.md | 8 +- linux-server/forgejo/.env.example | 20 ++- linux-server/forgejo/docker-compose.yml | 35 ++++- linux-server/homepage/config/services.yaml | 4 +- linux-server/post-install.md | 20 +-- macOS/README.md | 7 + macOS/forgejo-runner/README.md | 93 +++++++++++++ .../forgejo-runner-config.example.yml | 33 +++++ macOS/forgejo-runner/install.sh | 128 ++++++++++++++++++ macOS/forgejo-runner/lib.sh | 24 ++++ .../net.forgejo.runner.plist.template | 30 ++++ macOS/forgejo-runner/run.sh | 49 +++++++ macOS/forgejo-runner/verify.sh | 95 +++++++++++++ 13 files changed, 523 insertions(+), 23 deletions(-) create mode 100644 macOS/forgejo-runner/README.md create mode 100644 macOS/forgejo-runner/forgejo-runner-config.example.yml create mode 100755 macOS/forgejo-runner/install.sh create mode 100755 macOS/forgejo-runner/lib.sh create mode 100644 macOS/forgejo-runner/net.forgejo.runner.plist.template create mode 100755 macOS/forgejo-runner/run.sh create mode 100755 macOS/forgejo-runner/verify.sh diff --git a/linux-server/README.md b/linux-server/README.md index fb8c347..8cce133 100644 --- a/linux-server/README.md +++ b/linux-server/README.md @@ -270,13 +270,13 @@ In NPM admin (`http://:81`): 2. Deploy: ```sh cd linux-server/forgejo - cp .env.example .env # set FORGEJO_DOMAIN to your Tailscale hostname; optionally set FORGEJO_DATA_PATH for external drive + cp .env.example .env # set FORGEJO_DOMAIN (forgejo..ts.net) and TS_AUTHKEY; optionally FORGEJO_DATA_PATH for external drive docker compose up -d ``` - 3. Web UI at `http://:3300` — complete the setup wizard on first visit, create admin account - 4. Git over SSH on port `2222`: + 3. Runs behind a Tailscale sidecar (its own tailnet device), so it's reachable only on the tailnet — no host port. Web UI at `http://forgejo..ts.net:3000` — complete the setup wizard on first visit, create admin account + 4. Git over SSH on port `22` (the sidecar's own tailnet device, no conflict with the host's sshd): ```sh - git clone ssh://git@:2222//.git + git clone ssh://git@forgejo..ts.net:22//.git ``` 5. Add your SSH public key in **Settings → SSH / GPG Keys** after creating your account 6. To migrate from GitHub: use Forgejo's built-in migration (**+ → New Migration → GitHub**), then optionally configure a push mirror back to GitHub under repo **Settings → Push Mirrors** while validating the setup diff --git a/linux-server/forgejo/.env.example b/linux-server/forgejo/.env.example index 8866bec..0364d8d 100644 --- a/linux-server/forgejo/.env.example +++ b/linux-server/forgejo/.env.example @@ -1,8 +1,22 @@ # Copy this file to .env and update the values. -# Tailscale hostname — shown in clone URLs in the Forgejo web UI -# Run: tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//' -FORGEJO_DOMAIN=..ts.net +# Tailscale hostname for this Forgejo node — used in clone URLs and ROOT_URL. +# With the sidecar, this is the sidecar's `hostname` (forgejo) + your tailnet: +# forgejo..ts.net +# Find your tailnet suffix with: +# tailscale status --json | jq -r '.MagicDNSSuffix' +FORGEJO_DOMAIN=forgejo..ts.net + +# Auth key for the Tailscale sidecar to join the tailnet. +# Create one at: https://tailscale.com/admin/settings/keys +# A reusable, tagged (tag:server) key is convenient; an ephemeral key would +# drop the node when the container stops, so prefer non-ephemeral here. +TS_AUTHKEY=tskey-auth-xxxxxxxxxxxx + +# Where to persist the sidecar's Tailscale state (node identity). Keeping this +# means restarts reuse the same forgejo..ts.net device. +# Default: ./ts-state (same directory as docker-compose.yml) +TS_STATE_PATH=./ts-state # Where to store repositories and Forgejo config. # Default: ./data (same directory as docker-compose.yml) diff --git a/linux-server/forgejo/docker-compose.yml b/linux-server/forgejo/docker-compose.yml index 967ee5a..82b30b1 100644 --- a/linux-server/forgejo/docker-compose.yml +++ b/linux-server/forgejo/docker-compose.yml @@ -1,21 +1,43 @@ services: + # Tailscale sidecar: gives Forgejo its own tailnet device (forgejo..ts.net) + # instead of sharing the host's identity. Forgejo joins this container's network + # namespace below, so it's reachable on the tailnet at this hostname. + tailscale: + image: tailscale/tailscale:latest + container_name: forgejo-tailscale + hostname: forgejo # -> forgejo..ts.net + environment: + - TS_AUTHKEY=${TS_AUTHKEY} # tailscale.com/admin/settings/keys + - TS_STATE_DIR=/var/lib/tailscale # persist node identity across restarts + - TS_EXTRA_ARGS=--advertise-tags=tag:server + volumes: + - ${TS_STATE_PATH:-./ts-state}:/var/lib/tailscale + devices: + - /dev/net/tun:/dev/net/tun + cap_add: + - net_admin + restart: unless-stopped + forgejo: # Pin to a major version tag to avoid surprise upgrades — check latest at: # https://codeberg.org/forgejo/forgejo/releases image: codeberg.org/forgejo/forgejo:15 container_name: forgejo - restart: unless-stopped + # Share the sidecar's netns: no host port mappings, reached only over Tailscale. + network_mode: service:tailscale + depends_on: + - tailscale environment: - USER_UID=1000 - USER_GID=1000 - TZ=${TZ:-America/Los_Angeles} - FORGEJO__database__DB_TYPE=sqlite3 - - FORGEJO__server__ROOT_URL=http://${FORGEJO_DOMAIN}:3300 + # ROOT_URL host:port MUST match where clients reach Forgejo. The Mac mini + # Actions runner connects here (see macOS/forgejo-runner/). Forgejo serves + # on 3000 inside the netns; that's the tailnet port too (no remap). + - FORGEJO__server__ROOT_URL=http://${FORGEJO_DOMAIN}:3000 - FORGEJO__server__SSH_DOMAIN=${FORGEJO_DOMAIN} - - FORGEJO__server__SSH_PORT=2222 - ports: - - "3300:3000" # web UI - - "2222:22" # git over SSH + - FORGEJO__server__SSH_PORT=22 volumes: - ${FORGEJO_DATA_PATH:-./data}:/data healthcheck: @@ -23,3 +45,4 @@ services: interval: 30s timeout: 5s retries: 3 + restart: unless-stopped diff --git a/linux-server/homepage/config/services.yaml b/linux-server/homepage/config/services.yaml index 69b28eb..a424b44 100644 --- a/linux-server/homepage/config/services.yaml +++ b/linux-server/homepage/config/services.yaml @@ -97,13 +97,13 @@ - Storage: - forgejo: icon: si-forgejo - href: http://{{HOMEPAGE_VAR_FORGEJO_DOMAIN}}:3300 + href: http://{{HOMEPAGE_VAR_FORGEJO_DOMAIN}}:3000 description: Self-hosted git server: my-docker container: forgejo widget: type: gitea - url: http://localhost:3300 + url: http://{{HOMEPAGE_VAR_FORGEJO_DOMAIN}}:3000 key: "{{HOMEPAGE_VAR_FORGEJO_TOKEN}}" - syncthing: icon: si-syncthing diff --git a/linux-server/post-install.md b/linux-server/post-install.md index 07ee063..7d55883 100644 --- a/linux-server/post-install.md +++ b/linux-server/post-install.md @@ -127,22 +127,26 @@ The Homepage Tailscale widget uses a local OAuth proxy to avoid 90-day key rotat - Create admin credentials — then add them to `homepage/.env` - [ ] Point your router's DNS to `` for network-wide filtering -### Forgejo — http://\:3300 +### Forgejo — http://\:3000 + +Forgejo runs behind a Tailscale sidecar, so it is reachable only on the tailnet +at `http://forgejo..ts.net:3000` — there is no `` host port. - [ ] Copy and edit the env file: ```sh cd linux-server/forgejo && cp .env.example .env - # Set FORGEJO_DOMAIN to your Tailscale hostname + # Set FORGEJO_DOMAIN to forgejo..ts.net + # Set TS_AUTHKEY (tailscale.com/admin/settings/keys) so the sidecar can join # Optionally set FORGEJO_DATA_PATH to an external drive path ``` - [ ] Start Forgejo: ```sh docker compose up -d ``` -- [ ] Open `http://:3300` and complete the setup wizard: +- [ ] Open `http://forgejo..ts.net:3000` and complete the setup wizard: - Database: SQLite (pre-set) - SSH server domain and port: pre-filled from `.env` — verify they look correct - - Application URL: should match `http://:3300` + - Application URL: should match `http://forgejo..ts.net:3000` - Create the admin account at the bottom of the wizard page - [ ] Generate a personal access token for the Homepage widget: - Top-right avatar → **Settings → Applications → Generate Token** — scope: all (or read-only is enough for the widget) @@ -170,10 +174,10 @@ Everything Forgejo needs to restore from scratch lives in `FORGEJO_DATA_PATH` (d ```sh # SSH clone (use this for all git operations on Mac) -git clone ssh://git@:2222//.git +git clone ssh://git@forgejo..ts.net:22//.git # Set as remote on an existing repo -git remote set-url origin ssh://git@:2222//.git +git remote set-url origin ssh://git@forgejo..ts.net:22//.git ``` #### Migrating an existing repo (e.g. Obsidian vault) from GitHub @@ -181,7 +185,7 @@ git remote set-url origin ssh://git@:2222//. 1. In Forgejo web UI: **+ → New Migration → GitHub** — imports history, branches, and tags 2. On Mac, point the local repo at Forgejo: ```sh - git remote set-url origin ssh://git@:2222//.git + git remote set-url origin ssh://git@forgejo..ts.net:22//.git ``` 3. Add GitHub as a push mirror for validation while you transition: - In Forgejo: repo **Settings → Git Hooks → Push Mirrors → Add Push Mirror** @@ -216,4 +220,4 @@ git remote set-url origin ssh://git@:2222//. | Cockpit | https://\:9090 | | | Tailscale Web UI | http://localhost:8088 | After `tailscale up` | | Tailscale proxy | http://localhost:8089 | Internal — used by Homepage widget | -| Forgejo | http://\:3300 | Git over SSH on port 2222 | +| Forgejo | http://forgejo.\.ts.net:3000 | Tailscale sidecar; Git over SSH on port 22 | diff --git a/macOS/README.md b/macOS/README.md index be9b8f4..5d384c7 100644 --- a/macOS/README.md +++ b/macOS/README.md @@ -110,3 +110,10 @@ Install with `bash setup.sh --work` from the repo root (`macOS/setup.sh` is a th 2. Amazon Photos | [Download](https://www.amazon.com/Amazon-Photos/b?node=13234696011) | [brew](https://formulae.brew.sh/cask/amazon-photos) 1. Unlimited full-resolution photo backup for Amazon Prime members 2. Install: `brew install --cask amazon-photos` + +## Services + +- **[Forgejo Actions runner](forgejo-runner/)** — this Mac mini runs CI jobs for + the home Forgejo server over Tailscale. See + [`forgejo-runner/README.md`](forgejo-runner/README.md) for setup, the + `install`/`verify`/`run` scripts, and troubleshooting. diff --git a/macOS/forgejo-runner/README.md b/macOS/forgejo-runner/README.md new file mode 100644 index 0000000..c57c86e --- /dev/null +++ b/macOS/forgejo-runner/README.md @@ -0,0 +1,93 @@ +# Forgejo Actions runner (Mac mini) + +The Mac mini (`m4-mini`) is a self-hosted [Forgejo Actions](https://forgejo.org/docs/next/user/actions/) +runner. It executes CI jobs **directly on the host** (label `macos-latest:host`, +no Docker) for repos on the home Forgejo server, reaching it over Tailscale. + +``` +┌──────────────────────┐ Tailscale ┌──────────────────────────────┐ +│ Mac mini (m4-mini) │ ───────────────────────▶│ Forgejo server │ +│ forgejo-runner │ http://forgejo │ forgejo..ts.net │ +│ (LaunchAgent) │ ..ts.net:3000 │ :3000 (see linux-server/) │ +└──────────────────────┘ └──────────────────────────────┘ +``` + +The server side lives in [`../../linux-server/forgejo/`](../../linux-server/forgejo/). + +## Files on the Mac mini + +| Path | What | +| --- | --- | +| `~/.local/bin/forgejo-runner` | the runner binary (built from source — see below) | +| `~/forgejo-runner-config.yml` | config; the `server.connections` block holds the instance URL + token | +| `~/Library/LaunchAgents/net.forgejo.runner.plist` | LaunchAgent: `RunAtLoad` + `KeepAlive`, starts at login and restarts on crash | +| `~/Library/Logs/forgejo-runner.log` | combined stdout/stderr | + +## Why there is no prebuilt binary + +Forgejo publishes **Linux-only** runner binaries — there is no macOS build. So +on the Mac mini the runner is compiled from source with Go (hence the +`git describe` version string like `v12.10.2+20-g22ebc7d1`). `install.sh` +handles the build. + +## Reproduce from scratch + +```bash +# 1. Build, register, and load the LaunchAgent (prompts for a registration token). +bash install.sh + +# 2. Confirm it's healthy. +bash verify.sh +``` + +`install.sh` will: +1. Build `forgejo-runner` from source (`brew install go` first if needed) and + install it to `~/.local/bin/`. +2. Generate the base config (`generate-config`) if none exists. +3. Register the runner against the Forgejo instance — get a token from + **Forgejo → Settings → Actions → Runners → Create new runner** (site, org, or + repo scope). +4. Render `net.forgejo.runner.plist` from the template and `launchctl bootstrap` + it so it runs at login. + +Useful overrides: + +```bash +RUNNER_VERSION=v12.12.0 bash install.sh +FORGEJO_INSTANCE_URL=http://forgejo..ts.net:3000 bash install.sh +bash install.sh --skip-build # reuse the existing binary +bash install.sh --skip-register # leave server.connections untouched +``` + +Find your tailnet suffix with `tailscale status --json | jq -r '.MagicDNSSuffix'`. + +## Day-to-day + +```bash +bash run.sh status # launchd state + last log lines +bash run.sh restart # after editing the config +bash run.sh tail # follow the log +bash run.sh stop|start +``` + +## Troubleshooting + +**`connection refused` / `fail to invoke Declare` in the log — the most common +failure.** The runner host is fine; the configured instance URL no longer points +at where Forgejo listens. This is exactly what happened when the server moved +from `ollie-server:3300` to its own Tailscale node `forgejo..ts.net:3000` +— the runner kept dialing the old `:3300` and `KeepAlive` restarted it in a loop. + +1. `bash verify.sh` — the instance-reachable check pinpoints it. +2. Confirm where Forgejo actually answers: + `curl -s http://forgejo..ts.net:3000/api/v1/version` + (must return JSON, not a "Host validation failed" error — the host in the URL + has to match the server's `ROOT_URL`). +3. Fix the `url:` under `server.connections` in `~/forgejo-runner-config.yml`. +4. `bash run.sh restart`. + +**Daemon not running (`LastExitStatus` non-zero, no PID).** Check the log; it +restarts every ~10s via `KeepAlive`, so the tail shows the live error. + +**Reboot didn't bring it back.** It should (`RunAtLoad`). Verify Tailscale came +up (`tailscale status`) and the LaunchAgent is loaded (`bash run.sh status`). diff --git a/macOS/forgejo-runner/forgejo-runner-config.example.yml b/macOS/forgejo-runner/forgejo-runner-config.example.yml new file mode 100644 index 0000000..0b5d2fc --- /dev/null +++ b/macOS/forgejo-runner/forgejo-runner-config.example.yml @@ -0,0 +1,33 @@ +# Forgejo runner config — example. +# +# The live config (~/forgejo-runner-config.yml) is the stock output of +# `forgejo-runner generate-config` PLUS the `server:` block below. install.sh +# regenerates the stock body and `register` appends the connection, so you +# normally never hand-edit this. The fields that actually matter are here. +# +# DO NOT commit a real token. The values below are placeholders. + +runner: + file: .runner + capacity: 1 # one job at a time on the Mac mini + timeout: 3h + shutdown_timeout: 3h + fetch_interval: 2s + labels: [] # labels live per-connection (below), not here + +cache: + enabled: true + port: 0 # random free port for the internal cache server + +# Each connection points the runner at one Forgejo instance. The url MUST +# match where the server actually listens (its ROOT_URL host + port). +# This is the field that broke after the server moved from +# ollie-server:3300 to its own Tailscale node forgejo..ts.net:3000. +server: + connections: + ollie: + url: http://forgejo..ts.net:3000 + uuid: 00000000-0000-0000-0000-000000000000 # filled in by register + token: + labels: + - macos-latest:host # `:host` = run jobs directly on macOS, no Docker diff --git a/macOS/forgejo-runner/install.sh b/macOS/forgejo-runner/install.sh new file mode 100755 index 0000000..430d90f --- /dev/null +++ b/macOS/forgejo-runner/install.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# Build + install the Forgejo Actions runner on macOS, then register it and +# install the LaunchAgent so it starts at login and stays up. +# +# Forgejo ships no macOS binaries (Linux-only releases), so the runner is built +# from source with Go — matching the git-describe version already on the Mac +# mini (e.g. v12.10.2+...). +# +# Usage: +# bash install.sh # build, register (prompts for token), load +# bash install.sh --skip-build # reuse an existing binary +# bash install.sh --skip-register # don't touch server.connections +# RUNNER_VERSION=v12.12.0 bash install.sh +# FORGEJO_INSTANCE_URL=http://forgejo..ts.net:3000 bash install.sh +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" +# shellcheck source=macOS/forgejo-runner/lib.sh +source "$HERE/lib.sh" + +RUNNER_VERSION="${RUNNER_VERSION:-v12.12.0}" +RUNNER_REPO="https://code.forgejo.org/forgejo/runner" +RUNNER_NAME="${RUNNER_NAME:-$(scutil --get LocalHostName 2>/dev/null || hostname -s)}" +RUNNER_LABELS="${RUNNER_LABELS:-macos-latest:host}" + +skip_build=false +skip_register=false +for arg in "$@"; do + case "$arg" in + --skip-build) skip_build=true ;; + --skip-register) skip_register=true ;; + *) warn "ignoring unknown flag: $arg" ;; + esac +done + +[[ "$(uname -s)" == "Darwin" ]] || die "this installer is macOS-only" +[[ "$(uname -m)" == "arm64" ]] || warn "expected arm64 (Apple Silicon); continuing anyway" + +build_runner() { + command -v go >/dev/null 2>&1 || die "Go is required to build the runner. Install with: brew install go" + local tmp + tmp="$(mktemp -d)" + trap 'rm -rf "$tmp"' RETURN + info "cloning $RUNNER_REPO @ $RUNNER_VERSION" + git clone --depth 1 --branch "$RUNNER_VERSION" "$RUNNER_REPO" "$tmp/runner" + info "building (this takes a minute)" + if ! make -C "$tmp/runner" build 2>/dev/null; then + ( cd "$tmp/runner" && go build -o forgejo-runner . ) + fi + mkdir -p "$(dirname "$RUNNER_BIN")" + install -m 0755 "$tmp/runner/forgejo-runner" "$RUNNER_BIN" + ok "installed $RUNNER_BIN ($("$RUNNER_BIN" --version 2>&1 | head -1))" +} + +ensure_config() { + if [[ -f "$CONFIG" ]]; then + info "config already present: $CONFIG" + return + fi + info "generating base config: $CONFIG" + "$RUNNER_BIN" generate-config >"$CONFIG" + ok "wrote default config" +} + +register_runner() { + if grep -q 'server:' "$CONFIG" && grep -q 'connections:' "$CONFIG" && grep -q 'token:' "$CONFIG"; then + info "a server.connections entry already exists in $CONFIG — skipping registration" + info "(delete that block and re-run to re-register)" + return + fi + + local url token + url="$DEFAULT_INSTANCE_URL" + printf 'Forgejo instance URL [%s]: ' "$url" + read -r reply || true + [[ -n "${reply:-}" ]] && url="$reply" + + printf 'Registration token (Forgejo → Settings → Actions → Runners → Create new runner): ' + read -r token || true + [[ -n "${token:-}" ]] || die "a registration token is required" + + info "registering '$RUNNER_NAME' (labels: $RUNNER_LABELS) against $url" + "$RUNNER_BIN" register --no-interactive \ + --config "$CONFIG" \ + --instance "$url" \ + --token "$token" \ + --name "$RUNNER_NAME" \ + --labels "$RUNNER_LABELS" + ok "registered" +} + +install_agent() { + local node_bin tmpl + node_bin="$(command -v node >/dev/null 2>&1 && dirname "$(command -v node)" || true)" + tmpl="$HERE/net.forgejo.runner.plist.template" + mkdir -p "$(dirname "$PLIST")" "$(dirname "$LOG")" + + # PATH for jobs: node (if found) + Homebrew + system. Workflows often need node. + local path_value="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" + [[ -n "$node_bin" ]] && path_value="$node_bin:$path_value" + + sed -e "s|__RUNNER_BIN__|$RUNNER_BIN|g" \ + -e "s|__CONFIG__|$CONFIG|g" \ + -e "s|__LOG__|$LOG|g" \ + -e "s|__WORKDIR__|$HOME|g" \ + -e "s|__PATH__|$path_value|g" \ + "$tmpl" >"$PLIST" + ok "wrote $PLIST" +} + +load_agent() { + local domain + domain="$(launchd_domain)" + launchctl bootout "$domain/$LABEL" 2>/dev/null || true + launchctl bootstrap "$domain" "$PLIST" + launchctl kickstart -k "$domain/$LABEL" 2>/dev/null || true + ok "loaded LaunchAgent ($LABEL)" +} + +"$skip_build" || build_runner +[[ -x "$RUNNER_BIN" ]] || die "runner binary missing at $RUNNER_BIN (run without --skip-build)" +ensure_config +"$skip_register" || register_runner +install_agent +load_agent + +printf '\nDone. Verify with:\n bash %s/verify.sh\n' "$HERE" +printf 'Tail logs with:\n bash %s/run.sh tail\n' "$HERE" diff --git a/macOS/forgejo-runner/lib.sh b/macOS/forgejo-runner/lib.sh new file mode 100755 index 0000000..e51939b --- /dev/null +++ b/macOS/forgejo-runner/lib.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Shared constants + helpers for the Forgejo runner scripts (install/verify/run). +# Sourced, not executed. Paths mirror the live setup on the Mac mini. +# shellcheck disable=SC2034 # constants are consumed by the sourcing scripts +set -euo pipefail + +RUNNER_BIN="${RUNNER_BIN:-$HOME/.local/bin/forgejo-runner}" +CONFIG="${FORGEJO_RUNNER_CONFIG:-$HOME/forgejo-runner-config.yml}" +PLIST="$HOME/Library/LaunchAgents/net.forgejo.runner.plist" +LOG="$HOME/Library/Logs/forgejo-runner.log" +LABEL="net.forgejo.runner" + +# Default Forgejo instance the runner talks to (Tailscale MagicDNS, port 3000). +# Override with FORGEJO_INSTANCE_URL. Discover the tailnet with: +# tailscale status --json | jq -r '.MagicDNSSuffix' +DEFAULT_INSTANCE_URL="${FORGEJO_INSTANCE_URL:-http://forgejo.tail01d63b.ts.net:3000}" + +info() { printf ' %s\n' "$*"; } +ok() { printf '\033[32m✓\033[0m %s\n' "$*"; } +warn() { printf '\033[33m!\033[0m %s\n' "$*" >&2; } +fail() { printf '\033[31m✗\033[0m %s\n' "$*" >&2; } +die() { printf 'error: %s\n' "$*" >&2; exit 1; } + +launchd_domain() { printf 'gui/%s' "$(id -u)"; } diff --git a/macOS/forgejo-runner/net.forgejo.runner.plist.template b/macOS/forgejo-runner/net.forgejo.runner.plist.template new file mode 100644 index 0000000..b4b0143 --- /dev/null +++ b/macOS/forgejo-runner/net.forgejo.runner.plist.template @@ -0,0 +1,30 @@ + + + + + Label + net.forgejo.runner + ProgramArguments + + __RUNNER_BIN__ + daemon + --config + __CONFIG__ + + EnvironmentVariables + + PATH + __PATH__ + + WorkingDirectory + __WORKDIR__ + RunAtLoad + + KeepAlive + + StandardOutPath + __LOG__ + StandardErrorPath + __LOG__ + + diff --git a/macOS/forgejo-runner/run.sh b/macOS/forgejo-runner/run.sh new file mode 100755 index 0000000..ecf4488 --- /dev/null +++ b/macOS/forgejo-runner/run.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Control the Forgejo runner LaunchAgent. +# +# Usage: bash run.sh +# start load + start the daemon +# stop stop + unload the daemon +# restart force a restart (picks up config changes) +# status show launchd state + recent log lines +# logs print the full log +# tail follow the log (Ctrl-C to stop) +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" +# shellcheck source=macOS/forgejo-runner/lib.sh +source "$HERE/lib.sh" + +DOMAIN="$(launchd_domain)" +cmd="${1:-status}" + +case "$cmd" in + start) + [[ -f "$PLIST" ]] || die "LaunchAgent not installed — run: bash $HERE/install.sh" + launchctl bootstrap "$DOMAIN" "$PLIST" 2>/dev/null || launchctl kickstart "$DOMAIN/$LABEL" + ok "started $LABEL" + ;; + stop) + launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null || true + ok "stopped $LABEL" + ;; + restart) + launchctl kickstart -k "$DOMAIN/$LABEL" + ok "restarted $LABEL" + ;; + status) + launchctl print "$DOMAIN/$LABEL" 2>/dev/null | grep -E '^\s*(state|pid|last exit code) ' || warn "not loaded" + printf '\n--- last 10 log lines ---\n' + [[ -f "$LOG" ]] && tail -10 "$LOG" || info "no log yet: $LOG" + ;; + logs) + [[ -f "$LOG" ]] && cat "$LOG" || die "no log at $LOG" + ;; + tail) + [[ -f "$LOG" ]] || die "no log at $LOG" + tail -f "$LOG" + ;; + *) + die "unknown command: $cmd (try: start|stop|restart|status|logs|tail)" + ;; +esac diff --git a/macOS/forgejo-runner/verify.sh b/macOS/forgejo-runner/verify.sh new file mode 100755 index 0000000..547e7e5 --- /dev/null +++ b/macOS/forgejo-runner/verify.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Read-only health check for the Forgejo runner. Exits non-zero if any check +# fails. Mirrors the failure mode that took the runner down after a reboot: +# the configured instance URL no longer matched where Forgejo actually listens. +# +# Usage: bash verify.sh +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" +# shellcheck source=macOS/forgejo-runner/lib.sh +source "$HERE/lib.sh" + +failures=0 +check() { if "$@"; then return 0; else failures=$((failures + 1)); return 1; fi; } + +printf 'Forgejo runner health check\n\n' + +# 1. Binary +if [[ -x "$RUNNER_BIN" ]]; then + ok "binary: $RUNNER_BIN ($("$RUNNER_BIN" --version 2>&1 | head -1))" +else + fail "binary missing: $RUNNER_BIN"; failures=$((failures + 1)) +fi + +# 2. Config + endpoint +configured_url="" +if [[ -f "$CONFIG" ]]; then + configured_url="$(grep -m1 -E '^[[:space:]]*url:' "$CONFIG" | sed -E 's/^[[:space:]]*url:[[:space:]]*//')" + if [[ -n "$configured_url" ]]; then + ok "config: $CONFIG (instance: $configured_url)" + else + fail "config has no server.connections url: $CONFIG"; failures=$((failures + 1)) + fi +else + fail "config missing: $CONFIG"; failures=$((failures + 1)) +fi + +# 3. LaunchAgent installed +if [[ -f "$PLIST" ]]; then + ok "LaunchAgent installed: $PLIST" +else + fail "LaunchAgent missing: $PLIST"; failures=$((failures + 1)) +fi + +# 4. Daemon loaded + running +state="$(launchctl list "$LABEL" 2>/dev/null || true)" +if [[ -n "$state" ]]; then + pid="$(printf '%s' "$state" | sed -nE 's/.*"PID" = ([0-9]+);.*/\1/p')" + exit_status="$(printf '%s' "$state" | sed -nE 's/.*"LastExitStatus" = ([0-9]+);.*/\1/p')" + if [[ -n "$pid" ]]; then + ok "daemon running (PID $pid)" + else + fail "daemon loaded but not running (LastExitStatus=${exit_status:-?}) — see: $LOG" + failures=$((failures + 1)) + fi +else + fail "daemon not loaded — run: bash $HERE/run.sh start"; failures=$((failures + 1)) +fi + +# 5. Instance reachable (the exact thing that broke) +if [[ -n "$configured_url" ]]; then + code="$(curl -sS -m 5 -o /dev/null -w '%{http_code}' "$configured_url/api/v1/version" 2>/dev/null || echo 000)" + if [[ "$code" == "200" ]]; then + ok "instance reachable: $configured_url (HTTP 200)" + else + fail "instance NOT reachable: $configured_url (HTTP $code)" + info " fix: make sure the url + port match the server's ROOT_URL" + info " (Forgejo default is :3000; see linux-server/forgejo/docker-compose.yml)" + failures=$((failures + 1)) + fi +fi + +# 6. Declared more recently than it last failed to connect. Compares log +# positions so stale 'connection refused' spam from before a fix doesn't +# register as a current failure. +if [[ -f "$LOG" ]]; then + last_declared="$(grep -n 'declared successfully' "$LOG" | tail -1 | cut -d: -f1)" + last_refused="$(grep -n 'connection refused' "$LOG" | tail -1 | cut -d: -f1)" + if [[ -n "$last_declared" && ( -z "$last_refused" || "$last_declared" -gt "$last_refused" ) ]]; then + ok "runner declared successfully after its last connection error" + elif [[ -n "$last_refused" ]]; then + fail "log's latest connection state is 'connection refused' — can't reach the instance" + failures=$((failures + 1)) + else + info "log present but no declare line yet: $LOG" + fi +fi + +printf '\n' +if [[ "$failures" -eq 0 ]]; then + ok "all checks passed" +else + fail "$failures check(s) failed" +fi +exit "$failures" From ae573abadd9028094accf07d9121b73b6cfe3bc6 Mon Sep 17 00:00:00 2001 From: Ulises Chavarria Date: Mon, 22 Jun 2026 02:40:07 -0700 Subject: [PATCH 2/9] fix(forgejo-runner): harden token prompt, drop dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read the registration token with `read -rs` so it no longer echoes to the terminal/scrollback, and emit the swallowed newline. Remove the unused check() helper in verify.sh — every check inlines its own failure count. Co-Authored-By: Claude Opus 4.8 (1M context) --- macOS/forgejo-runner/install.sh | 3 ++- macOS/forgejo-runner/verify.sh | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/macOS/forgejo-runner/install.sh b/macOS/forgejo-runner/install.sh index 430d90f..83121de 100755 --- a/macOS/forgejo-runner/install.sh +++ b/macOS/forgejo-runner/install.sh @@ -76,7 +76,8 @@ register_runner() { [[ -n "${reply:-}" ]] && url="$reply" printf 'Registration token (Forgejo → Settings → Actions → Runners → Create new runner): ' - read -r token || true + read -rs token || true + printf '\n' [[ -n "${token:-}" ]] || die "a registration token is required" info "registering '$RUNNER_NAME' (labels: $RUNNER_LABELS) against $url" diff --git a/macOS/forgejo-runner/verify.sh b/macOS/forgejo-runner/verify.sh index 547e7e5..4e095db 100755 --- a/macOS/forgejo-runner/verify.sh +++ b/macOS/forgejo-runner/verify.sh @@ -11,7 +11,6 @@ HERE="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" source "$HERE/lib.sh" failures=0 -check() { if "$@"; then return 0; else failures=$((failures + 1)); return 1; fi; } printf 'Forgejo runner health check\n\n' From 7ddde848772ee91ede513621f294d2e444690d96 Mon Sep 17 00:00:00 2001 From: Ulises Chavarria Date: Mon, 22 Jun 2026 02:40:13 -0700 Subject: [PATCH 3/9] refactor(forgejo-runner): source instance URL from gitignored .env Replace the hardcoded tailnet in DEFAULT_INSTANCE_URL with a placeholder and source an optional .env (gitignored) for the real value; add .env.example (quoted so the placeholder isn't parsed as a shell redirection when sourced). Scrub the real hostname from the README troubleshooting note and the example config's connection name/comment. Co-Authored-By: Claude Opus 4.8 (1M context) --- macOS/forgejo-runner/.env.example | 9 +++++++++ macOS/forgejo-runner/README.md | 2 +- .../forgejo-runner/forgejo-runner-config.example.yml | 4 ++-- macOS/forgejo-runner/lib.sh | 11 +++++++++-- 4 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 macOS/forgejo-runner/.env.example diff --git a/macOS/forgejo-runner/.env.example b/macOS/forgejo-runner/.env.example new file mode 100644 index 0000000..4197ffa --- /dev/null +++ b/macOS/forgejo-runner/.env.example @@ -0,0 +1,9 @@ +# Copy this file to .env and update the value. .env is gitignored. +# This file is sourced by lib.sh, so keep values quoted (the <...> placeholder +# would otherwise be parsed as a shell redirection). + +# Forgejo instance the runner connects to (Tailscale MagicDNS host + port 3000). +# Must match the server's ROOT_URL host:port (see linux-server/forgejo/). +# Find your tailnet suffix with: +# tailscale status --json | jq -r '.MagicDNSSuffix' +FORGEJO_INSTANCE_URL="http://forgejo..ts.net:3000" diff --git a/macOS/forgejo-runner/README.md b/macOS/forgejo-runner/README.md index c57c86e..dbcc467 100644 --- a/macOS/forgejo-runner/README.md +++ b/macOS/forgejo-runner/README.md @@ -75,7 +75,7 @@ bash run.sh stop|start **`connection refused` / `fail to invoke Declare` in the log — the most common failure.** The runner host is fine; the configured instance URL no longer points at where Forgejo listens. This is exactly what happened when the server moved -from `ollie-server:3300` to its own Tailscale node `forgejo..ts.net:3000` +from `:3300` to its own Tailscale node `forgejo..ts.net:3000` — the runner kept dialing the old `:3300` and `KeepAlive` restarted it in a loop. 1. `bash verify.sh` — the instance-reachable check pinpoints it. diff --git a/macOS/forgejo-runner/forgejo-runner-config.example.yml b/macOS/forgejo-runner/forgejo-runner-config.example.yml index 0b5d2fc..6b3a87c 100644 --- a/macOS/forgejo-runner/forgejo-runner-config.example.yml +++ b/macOS/forgejo-runner/forgejo-runner-config.example.yml @@ -22,10 +22,10 @@ cache: # Each connection points the runner at one Forgejo instance. The url MUST # match where the server actually listens (its ROOT_URL host + port). # This is the field that broke after the server moved from -# ollie-server:3300 to its own Tailscale node forgejo..ts.net:3000. +# :3300 to its own Tailscale node forgejo..ts.net:3000. server: connections: - ollie: + home: url: http://forgejo..ts.net:3000 uuid: 00000000-0000-0000-0000-000000000000 # filled in by register token: diff --git a/macOS/forgejo-runner/lib.sh b/macOS/forgejo-runner/lib.sh index e51939b..ba19d22 100755 --- a/macOS/forgejo-runner/lib.sh +++ b/macOS/forgejo-runner/lib.sh @@ -4,6 +4,12 @@ # shellcheck disable=SC2034 # constants are consumed by the sourcing scripts set -euo pipefail +LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" + +# Local, gitignored overrides (instance URL, etc.). Copy .env.example to .env +# and edit — keeps tailnet-specific values out of the committed repo. +[[ -f "$LIB_DIR/.env" ]] && source "$LIB_DIR/.env" + RUNNER_BIN="${RUNNER_BIN:-$HOME/.local/bin/forgejo-runner}" CONFIG="${FORGEJO_RUNNER_CONFIG:-$HOME/forgejo-runner-config.yml}" PLIST="$HOME/Library/LaunchAgents/net.forgejo.runner.plist" @@ -11,9 +17,10 @@ LOG="$HOME/Library/Logs/forgejo-runner.log" LABEL="net.forgejo.runner" # Default Forgejo instance the runner talks to (Tailscale MagicDNS, port 3000). -# Override with FORGEJO_INSTANCE_URL. Discover the tailnet with: +# Set FORGEJO_INSTANCE_URL in .env (or the environment); the placeholder below +# is only a prompt hint. Discover your tailnet with: # tailscale status --json | jq -r '.MagicDNSSuffix' -DEFAULT_INSTANCE_URL="${FORGEJO_INSTANCE_URL:-http://forgejo.tail01d63b.ts.net:3000}" +DEFAULT_INSTANCE_URL="${FORGEJO_INSTANCE_URL:-http://forgejo..ts.net:3000}" info() { printf ' %s\n' "$*"; } ok() { printf '\033[32m✓\033[0m %s\n' "$*"; } From 0ecc16b8c315efc62d8802acf294a9179c1bbc9f Mon Sep 17 00:00:00 2001 From: Ulises Chavarria Date: Mon, 22 Jun 2026 02:40:18 -0700 Subject: [PATCH 4/9] docs: add Privacy & Security policy to CLAUDE.md Document that the repo is public: keep identifying/secret values (tailnet names, hostnames, IPs, usernames, emails, tokens, personal paths) out of tracked files, use gitignored .env + committed .env.example placeholders, and prefer placeholders in docs/configs. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index dca419b..07dc60f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,3 +54,21 @@ phases. primary cross-platform test mechanism (only one platform can run live). - App-store packages and `priority: "none"` entries are reminders only — never auto-installed. + +## Privacy & Security + +This repo is **public**. Never commit identifying or secret information. + +- Keep these out of tracked files entirely: tailnet names / MagicDNS suffixes + (`tailXXXXXX.ts.net`), real hostnames, server IPs, usernames, emails, tokens, + auth keys, and personal absolute paths. +- Put any machine-specific or private value in a `.env` file (gitignored + repo-wide) and ship a committed `.env.example` with placeholders instead — + e.g. `linux-server/forgejo/.env.example`, `macOS/forgejo-runner/.env.example`. + Scripts read these via `${VAR:-}` and source a local `.env` when + present; they never hardcode the real value. +- In docs and configs use placeholders: ``, ``, + ``, ``. Default to `.env` whenever a value is + identifying — prefer one more env var over leaking a real value. +- When editing, scan the diff for accidentally introduced real identifiers + before committing. From 2db377ae3b6d2efd8de2762d3f829e9da158dce1 Mon Sep 17 00:00:00 2001 From: Ulises Chavarria Date: Mon, 22 Jun 2026 02:40:23 -0700 Subject: [PATCH 5/9] docs(server): scrub identifying hostname/IP/tailnet from configs Replace the real hostname, LAN IP, and tailnet name with the repo's // placeholders across the server README, post-install guide, homepage .env.example, and NPM compose comment. Co-Authored-By: Claude Opus 4.8 (1M context) --- linux-server/README.md | 2 +- linux-server/homepage/.env.example | 6 +++--- linux-server/nginx-proxy-manager/docker-compose.yml | 2 +- linux-server/post-install.md | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/linux-server/README.md b/linux-server/README.md index 8cce133..e6bcd32 100644 --- a/linux-server/README.md +++ b/linux-server/README.md @@ -118,7 +118,7 @@ The Tailscale widget uses a local OAuth proxy (`linux-server/tailscale-proxy`) t Get a TLS cert from Tailscale and configure NPM to terminate HTTPS: ```sh -tailscale cert # e.g. ollie-server.tail01d63b.ts.net +tailscale cert # e.g. ..ts.net ``` In NPM admin (`http://:81`): diff --git a/linux-server/homepage/.env.example b/linux-server/homepage/.env.example index 86c64b1..96d8ba2 100644 --- a/linux-server/homepage/.env.example +++ b/linux-server/homepage/.env.example @@ -3,7 +3,7 @@ # Used in services.yaml for service hrefs — use hostname (avahi mDNS) to avoid hardcoding an IP # Access the dashboard at http://.local:3000 -HOMEPAGE_VAR_SERVER_IP=ollie-server.local +HOMEPAGE_VAR_SERVER_IP=.local # Tailscale web UI — get your Tailscale IP with: tailscale ip -4 HOMEPAGE_VAR_TAILSCALE_IP= @@ -23,8 +23,8 @@ HOMEPAGE_VAR_FORGEJO_DOMAIN= HOMEPAGE_VAR_FORGEJO_TOKEN= # Server hostname — used to build HOMEPAGE_ALLOWED_HOSTS in docker-compose.yml -HOSTNAME=ollie-server +HOSTNAME= # Server LAN IP — used in HOMEPAGE_ALLOWED_HOSTS to allow direct IP access -SERVER_IP=192.168.1.72 +SERVER_IP= # Tailscale HTTPS hostname — run: tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//' TAILSCALE_HOSTNAME=..ts.net diff --git a/linux-server/nginx-proxy-manager/docker-compose.yml b/linux-server/nginx-proxy-manager/docker-compose.yml index 0b6180d..8d18631 100644 --- a/linux-server/nginx-proxy-manager/docker-compose.yml +++ b/linux-server/nginx-proxy-manager/docker-compose.yml @@ -4,7 +4,7 @@ services: container_name: nginx-proxy-manager restart: unless-stopped ports: - - "80:80" # HTTP proxy (redirects ollie-server.local → HTTPS) + - "80:80" # HTTP proxy (redirects .local → HTTPS) - "443:443" # HTTPS proxy - "81:81" # admin UI volumes: diff --git a/linux-server/post-install.md b/linux-server/post-install.md index 7d55883..d4bc142 100644 --- a/linux-server/post-install.md +++ b/linux-server/post-install.md @@ -46,7 +46,7 @@ cd linux-server/homepage && docker compose restart | Variable | How to get the value | |---|---| -| `HOMEPAGE_VAR_SERVER_IP` | Your server hostname (e.g. `ollie-server.local`) | +| `HOMEPAGE_VAR_SERVER_IP` | Your server hostname (e.g. `.local`) | | `HOMEPAGE_VAR_TAILSCALE_IP` | `tailscale ip -4` | | `HOMEPAGE_VAR_ADGUARD_USER` / `_PASS` | Set after AdGuard wizard (step 5 below) | | `HOMEPAGE_VAR_SYNCTHING_KEY` | Set after Syncthing is running (step 5 below) | @@ -84,7 +84,7 @@ The Homepage Tailscale widget uses a local OAuth proxy to avoid 90-day key rotat - [ ] Get a TLS cert: ```sh tailscale cert - # e.g. tailscale cert ollie-server.tail01d63b.ts.net + # e.g. tailscale cert ..ts.net ``` - [ ] In NPM admin (`http://:81`): - **SSL Certificates → Add Custom Certificate** — paste `.crt` and `.key` file contents; save as e.g. "tailscale cert" From db89ab0e39a24e6ea11b8cd02401760900beb457 Mon Sep 17 00:00:00 2001 From: Ulises Chavarria Date: Mon, 22 Jun 2026 02:51:38 -0700 Subject: [PATCH 6/9] refactor(forgejo): align runner + docs to main's HTTPS sidecar After merging main, Forgejo is served over HTTPS by a tailscale serve sidecar (ROOT_URL=https://forgejo..ts.net/), not plain HTTP on :3000. Repoint the Mac mini runner tooling and the server docs accordingly: - runner: FORGEJO_INSTANCE_URL -> https://forgejo..ts.net (no port), updated lib.sh default, .env.example, README diagram/troubleshooting, config example, install.sh usage, verify.sh fix hint - server docs: forgejo web UI URLs in README.md and post-install.md Git SSH stays on :22 (unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) --- linux-server/README.md | 2 +- linux-server/post-install.md | 13 +++++++------ macOS/forgejo-runner/.env.example | 7 ++++--- macOS/forgejo-runner/README.md | 13 +++++++------ .../forgejo-runner-config.example.yml | 7 ++++--- macOS/forgejo-runner/install.sh | 2 +- macOS/forgejo-runner/lib.sh | 5 +++-- macOS/forgejo-runner/verify.sh | 4 ++-- 8 files changed, 29 insertions(+), 24 deletions(-) diff --git a/linux-server/README.md b/linux-server/README.md index 0a33f7c..e90f646 100644 --- a/linux-server/README.md +++ b/linux-server/README.md @@ -292,7 +292,7 @@ In NPM admin (`http://:81`): cp .env.example .env # set FORGEJO_DOMAIN (forgejo..ts.net) and TS_AUTHKEY; optionally FORGEJO_DATA_PATH for external drive docker compose up -d ``` - 3. Runs behind a Tailscale sidecar (its own tailnet device), so it's reachable only on the tailnet — no host port. Web UI at `http://forgejo..ts.net:3000` — complete the setup wizard on first visit, create admin account + 3. Runs behind a Tailscale sidecar (its own tailnet device, HTTPS via `tailscale serve`), so it's reachable only on the tailnet — no host port. Web UI at `https://forgejo..ts.net/` — complete the setup wizard on first visit, create admin account 4. Git over SSH on port `22` (the sidecar's own tailnet device, no conflict with the host's sshd): ```sh git clone ssh://git@forgejo..ts.net:22//.git diff --git a/linux-server/post-install.md b/linux-server/post-install.md index d4bc142..f08343a 100644 --- a/linux-server/post-install.md +++ b/linux-server/post-install.md @@ -127,10 +127,11 @@ The Homepage Tailscale widget uses a local OAuth proxy to avoid 90-day key rotat - Create admin credentials — then add them to `homepage/.env` - [ ] Point your router's DNS to `` for network-wide filtering -### Forgejo — http://\:3000 +### Forgejo — https://\/ -Forgejo runs behind a Tailscale sidecar, so it is reachable only on the tailnet -at `http://forgejo..ts.net:3000` — there is no `` host port. +Forgejo runs behind a Tailscale sidecar (HTTPS via `tailscale serve`), so it is +reachable only on the tailnet at `https://forgejo..ts.net/` — there is +no `` host port. - [ ] Copy and edit the env file: ```sh @@ -143,10 +144,10 @@ at `http://forgejo..ts.net:3000` — there is no `` host por ```sh docker compose up -d ``` -- [ ] Open `http://forgejo..ts.net:3000` and complete the setup wizard: +- [ ] Open `https://forgejo..ts.net/` and complete the setup wizard: - Database: SQLite (pre-set) - SSH server domain and port: pre-filled from `.env` — verify they look correct - - Application URL: should match `http://forgejo..ts.net:3000` + - Application URL: should match `https://forgejo..ts.net/` - Create the admin account at the bottom of the wizard page - [ ] Generate a personal access token for the Homepage widget: - Top-right avatar → **Settings → Applications → Generate Token** — scope: all (or read-only is enough for the widget) @@ -220,4 +221,4 @@ git remote set-url origin ssh://git@forgejo..ts.net:22//:9090 | | | Tailscale Web UI | http://localhost:8088 | After `tailscale up` | | Tailscale proxy | http://localhost:8089 | Internal — used by Homepage widget | -| Forgejo | http://forgejo.\.ts.net:3000 | Tailscale sidecar; Git over SSH on port 22 | +| Forgejo | https://forgejo.\.ts.net/ | Tailscale sidecar (HTTPS via serve); Git over SSH on port 22 | diff --git a/macOS/forgejo-runner/.env.example b/macOS/forgejo-runner/.env.example index 4197ffa..ac968a0 100644 --- a/macOS/forgejo-runner/.env.example +++ b/macOS/forgejo-runner/.env.example @@ -2,8 +2,9 @@ # This file is sourced by lib.sh, so keep values quoted (the <...> placeholder # would otherwise be parsed as a shell redirection). -# Forgejo instance the runner connects to (Tailscale MagicDNS host + port 3000). -# Must match the server's ROOT_URL host:port (see linux-server/forgejo/). +# Forgejo instance the runner connects to (Tailscale MagicDNS over HTTPS, served +# by the server's `tailscale serve` sidecar — no port). +# Must match the server's ROOT_URL host (see linux-server/forgejo/). # Find your tailnet suffix with: # tailscale status --json | jq -r '.MagicDNSSuffix' -FORGEJO_INSTANCE_URL="http://forgejo..ts.net:3000" +FORGEJO_INSTANCE_URL="https://forgejo..ts.net" diff --git a/macOS/forgejo-runner/README.md b/macOS/forgejo-runner/README.md index dbcc467..af49e05 100644 --- a/macOS/forgejo-runner/README.md +++ b/macOS/forgejo-runner/README.md @@ -7,8 +7,8 @@ no Docker) for repos on the home Forgejo server, reaching it over Tailscale. ``` ┌──────────────────────┐ Tailscale ┌──────────────────────────────┐ │ Mac mini (m4-mini) │ ───────────────────────▶│ Forgejo server │ -│ forgejo-runner │ http://forgejo │ forgejo..ts.net │ -│ (LaunchAgent) │ ..ts.net:3000 │ :3000 (see linux-server/) │ +│ forgejo-runner │ https://forgejo │ forgejo..ts.net │ +│ (LaunchAgent) │ ..ts.net │ serve :443 → :3000 │ └──────────────────────┘ └──────────────────────────────┘ ``` @@ -54,7 +54,7 @@ Useful overrides: ```bash RUNNER_VERSION=v12.12.0 bash install.sh -FORGEJO_INSTANCE_URL=http://forgejo..ts.net:3000 bash install.sh +FORGEJO_INSTANCE_URL=https://forgejo..ts.net bash install.sh bash install.sh --skip-build # reuse the existing binary bash install.sh --skip-register # leave server.connections untouched ``` @@ -75,12 +75,13 @@ bash run.sh stop|start **`connection refused` / `fail to invoke Declare` in the log — the most common failure.** The runner host is fine; the configured instance URL no longer points at where Forgejo listens. This is exactly what happened when the server moved -from `:3300` to its own Tailscale node `forgejo..ts.net:3000` -— the runner kept dialing the old `:3300` and `KeepAlive` restarted it in a loop. +from `:3300` to its own Tailscale node at `https://forgejo..ts.net` +(HTTPS via `tailscale serve`) — the runner kept dialing the old `:3300` and +`KeepAlive` restarted it in a loop. 1. `bash verify.sh` — the instance-reachable check pinpoints it. 2. Confirm where Forgejo actually answers: - `curl -s http://forgejo..ts.net:3000/api/v1/version` + `curl -s https://forgejo..ts.net/api/v1/version` (must return JSON, not a "Host validation failed" error — the host in the URL has to match the server's `ROOT_URL`). 3. Fix the `url:` under `server.connections` in `~/forgejo-runner-config.yml`. diff --git a/macOS/forgejo-runner/forgejo-runner-config.example.yml b/macOS/forgejo-runner/forgejo-runner-config.example.yml index 6b3a87c..be9176f 100644 --- a/macOS/forgejo-runner/forgejo-runner-config.example.yml +++ b/macOS/forgejo-runner/forgejo-runner-config.example.yml @@ -20,13 +20,14 @@ cache: port: 0 # random free port for the internal cache server # Each connection points the runner at one Forgejo instance. The url MUST -# match where the server actually listens (its ROOT_URL host + port). +# match the host in the server's ROOT_URL. # This is the field that broke after the server moved from -# :3300 to its own Tailscale node forgejo..ts.net:3000. +# :3300 to its own Tailscale node https://forgejo..ts.net +# (HTTPS via tailscale serve). server: connections: home: - url: http://forgejo..ts.net:3000 + url: https://forgejo..ts.net uuid: 00000000-0000-0000-0000-000000000000 # filled in by register token: labels: diff --git a/macOS/forgejo-runner/install.sh b/macOS/forgejo-runner/install.sh index 83121de..ad32bdc 100755 --- a/macOS/forgejo-runner/install.sh +++ b/macOS/forgejo-runner/install.sh @@ -11,7 +11,7 @@ # bash install.sh --skip-build # reuse an existing binary # bash install.sh --skip-register # don't touch server.connections # RUNNER_VERSION=v12.12.0 bash install.sh -# FORGEJO_INSTANCE_URL=http://forgejo..ts.net:3000 bash install.sh +# FORGEJO_INSTANCE_URL=https://forgejo..ts.net bash install.sh set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" diff --git a/macOS/forgejo-runner/lib.sh b/macOS/forgejo-runner/lib.sh index ba19d22..a97bc29 100755 --- a/macOS/forgejo-runner/lib.sh +++ b/macOS/forgejo-runner/lib.sh @@ -16,11 +16,12 @@ PLIST="$HOME/Library/LaunchAgents/net.forgejo.runner.plist" LOG="$HOME/Library/Logs/forgejo-runner.log" LABEL="net.forgejo.runner" -# Default Forgejo instance the runner talks to (Tailscale MagicDNS, port 3000). +# Default Forgejo instance the runner talks to (Tailscale MagicDNS over HTTPS, +# terminated by the server's `tailscale serve` sidecar — no port). # Set FORGEJO_INSTANCE_URL in .env (or the environment); the placeholder below # is only a prompt hint. Discover your tailnet with: # tailscale status --json | jq -r '.MagicDNSSuffix' -DEFAULT_INSTANCE_URL="${FORGEJO_INSTANCE_URL:-http://forgejo..ts.net:3000}" +DEFAULT_INSTANCE_URL="${FORGEJO_INSTANCE_URL:-https://forgejo..ts.net}" info() { printf ' %s\n' "$*"; } ok() { printf '\033[32m✓\033[0m %s\n' "$*"; } diff --git a/macOS/forgejo-runner/verify.sh b/macOS/forgejo-runner/verify.sh index 4e095db..45fd1d9 100755 --- a/macOS/forgejo-runner/verify.sh +++ b/macOS/forgejo-runner/verify.sh @@ -63,8 +63,8 @@ if [[ -n "$configured_url" ]]; then ok "instance reachable: $configured_url (HTTP 200)" else fail "instance NOT reachable: $configured_url (HTTP $code)" - info " fix: make sure the url + port match the server's ROOT_URL" - info " (Forgejo default is :3000; see linux-server/forgejo/docker-compose.yml)" + info " fix: make sure the url's host matches the server's ROOT_URL" + info " (served over HTTPS by the tailscale serve sidecar; see linux-server/forgejo/docker-compose.yml)" failures=$((failures + 1)) fi fi From 4b814f7c0edbb34a8f844261c013ebb11288dcf1 Mon Sep 17 00:00:00 2001 From: Ulises Chavarria Date: Mon, 22 Jun 2026 03:14:27 -0700 Subject: [PATCH 7/9] feat(forgejo): server-side Mac mini runner status monitor Add a host timer that asks Forgejo's runners API whether the Mac mini Actions runner is connected (status idle/active = up, offline/absent = down) and surfaces it three ways, reusing the backup feature's patterns: - homepage 'forgejo-runner' customapi card (JSON served by a loopback nginx added to forgejo/docker-compose.yml on 127.0.0.1:8098) - Uptime Kuma push (KUMA_PUSH_URL) - ntfy alert on down/recovery transitions only runner-status.sh parses the API defensively (bare array or {runners|entries} wrapper), falls back to 'down' when Forgejo is unreachable, and is driven by forgejo-runner-status.{service,timer} (every 2 min). Config lives in forgejo/.env (token + RUNNER_NAME + optional Kuma/ntfy); FORGEJO_DOMAIN is now quoted there since the script sources the file. Runtime JSON is gitignored. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 3 + linux-server/forgejo/.env.example | 27 ++++- linux-server/forgejo/docker-compose.yml | 13 +++ .../forgejo/forgejo-runner-status.service | 8 ++ .../forgejo/forgejo-runner-status.timer | 10 ++ linux-server/forgejo/runner-status.sh | 103 ++++++++++++++++++ linux-server/homepage/config/services.yaml | 21 ++++ linux-server/post-install.md | 30 +++++ 8 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 linux-server/forgejo/forgejo-runner-status.service create mode 100644 linux-server/forgejo/forgejo-runner-status.timer create mode 100755 linux-server/forgejo/runner-status.sh diff --git a/.gitignore b/.gitignore index cd63637..b07ea0f 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ linux-server/qbittorrent/downloads/ # Backup status JSON written at runtime (served to the homepage card) linux-server/backup/status/ + +# Forgejo runner status JSON + last-state written at runtime (homepage card) +linux-server/forgejo/runner-status/ diff --git a/linux-server/forgejo/.env.example b/linux-server/forgejo/.env.example index 654bab1..5fd9e01 100644 --- a/linux-server/forgejo/.env.example +++ b/linux-server/forgejo/.env.example @@ -7,8 +7,10 @@ TS_AUTHKEY= # Forgejo's own MagicDNS name = the sidecar `hostname:` (forgejo) + your tailnet. # Find your tailnet: tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//' -# (that prints ollie-server..ts.net — swap the host part for `forgejo`) -FORGEJO_DOMAIN=forgejo..ts.net +# (that prints ..ts.net — swap the host part for `forgejo`) +# Quoted because runner-status.sh sources this file (the <...> placeholder would +# otherwise be read as a shell redirection). +FORGEJO_DOMAIN="forgejo..ts.net" # Where to store repositories and Forgejo config. # Default: ./data (same directory as docker-compose.yml) @@ -16,3 +18,24 @@ FORGEJO_DOMAIN=forgejo..ts.net FORGEJO_DATA_PATH=./data TZ=America/Los_Angeles + +# --- Runner status monitor (runner-status.sh) ------------------------------ +# Server-side check that the Mac mini Actions runner is connected, surfaced on +# the homepage card, Uptime Kuma, and ntfy. The runner itself lives in +# macOS/forgejo-runner/. Leave the optional vars blank to skip that surface. + +# Forgejo token that can read runners. The default API URL is the instance +# (admin) scope, so this needs an admin token; create one at +# Forgejo → Settings → Applications → Generate Token. +FORGEJO_RUNNER_API_TOKEN= +# Runner name as registered in Forgejo (Settings → Actions → Runners). +RUNNER_NAME=m4-mini +# Override only if your token is org/repo-scoped rather than instance/admin: +# FORGEJO_RUNNER_API_URL="https://forgejo..ts.net/api/v1/repos///actions/runners" + +# Uptime Kuma push monitor (optional). Create a Push monitor, paste its URL. +KUMA_PUSH_URL= +# ntfy down/recovery alerts (optional). Quoted: sourced by runner-status.sh. +NTFY_URL="https://ntfy..ts.net" +NTFY_TOPIC=server-runner +NTFY_TOKEN= diff --git a/linux-server/forgejo/docker-compose.yml b/linux-server/forgejo/docker-compose.yml index 28fdbe2..3372942 100644 --- a/linux-server/forgejo/docker-compose.yml +++ b/linux-server/forgejo/docker-compose.yml @@ -47,3 +47,16 @@ services: interval: 30s timeout: 5s retries: 3 + + # Loopback static server for the homepage "forgejo-runner" card. The host + # systemd timer (forgejo-runner-status.timer → runner-status.sh) writes + # runner-status.json here; homepage is host-networked, so localhost reaches it. + # Not user-facing — bound to loopback, no Tailscale sidecar. + forgejo-runner-status: + image: nginx:alpine + container_name: forgejo-runner-status + restart: unless-stopped + ports: + - "127.0.0.1:8098:80" + volumes: + - ./runner-status:/usr/share/nginx/html:ro diff --git a/linux-server/forgejo/forgejo-runner-status.service b/linux-server/forgejo/forgejo-runner-status.service new file mode 100644 index 0000000..9280ab2 --- /dev/null +++ b/linux-server/forgejo/forgejo-runner-status.service @@ -0,0 +1,8 @@ +[Unit] +Description=Forgejo Actions runner status check (Mac mini) +After=network-online.target docker.service +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/home/ollie/github/Computer-Setup/linux-server/forgejo/runner-status.sh diff --git a/linux-server/forgejo/forgejo-runner-status.timer b/linux-server/forgejo/forgejo-runner-status.timer new file mode 100644 index 0000000..8ae21bf --- /dev/null +++ b/linux-server/forgejo/forgejo-runner-status.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Poll the Forgejo runner status every 2 minutes + +[Timer] +OnBootSec=2min +OnUnitActiveSec=2min +Unit=forgejo-runner-status.service + +[Install] +WantedBy=timers.target diff --git a/linux-server/forgejo/runner-status.sh b/linux-server/forgejo/runner-status.sh new file mode 100755 index 0000000..88b7945 --- /dev/null +++ b/linux-server/forgejo/runner-status.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Server-side check that the Mac mini Forgejo Actions runner is connected. +# Surfaces the result three ways: a JSON file for the homepage card, an Uptime +# Kuma push, and an ntfy alert when the runner transitions offline (and when it +# recovers). Run on a 2-minute systemd timer (forgejo-runner-status.timer). +# +# "Up" means Forgejo's API currently reports the runner as idle/active — i.e. +# the server actually sees it connected, not merely that the host pings. The +# runner itself lives in macOS/forgejo-runner/. + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" + +if [[ -f "$SCRIPT_DIR/.env" ]]; then + set -a + # shellcheck disable=SC1091 + source "$SCRIPT_DIR/.env" + set +a +fi + +: "${RUNNER_NAME:=m4-mini}" +: "${FORGEJO_DOMAIN:?set FORGEJO_DOMAIN in .env}" +# Endpoint that lists runners. Defaults to the instance (admin) scope; override +# in .env if your token is org/repo-scoped instead. +: "${FORGEJO_RUNNER_API_URL:=https://${FORGEJO_DOMAIN}/api/v1/admin/actions/runners}" +: "${FORGEJO_RUNNER_API_TOKEN:?set FORGEJO_RUNNER_API_TOKEN in .env (a Forgejo token that can read runners)}" +: "${STATUS_JSON:=$SCRIPT_DIR/runner-status/runner-status.json}" +: "${STATE_FILE:=$SCRIPT_DIR/runner-status/.last-state}" +: "${KUMA_PUSH_URL:=}" +: "${NTFY_TOPIC:=server-runner}" + +command -v jq >/dev/null || { printf 'error: jq not installed (apt install jq)\n' >&2; exit 1; } + +notify() { + local title="$1" priority="$2" tags="$3" msg="$4" + [[ -n "${NTFY_URL:-}" ]] || return 0 + local args=(-fsS -H "Title: $title" -H "Priority: $priority" -H "Tags: $tags" -d "$msg") + [[ -n "${NTFY_TOKEN:-}" ]] && args+=(-H "Authorization: Bearer $NTFY_TOKEN") + curl "${args[@]}" "$NTFY_URL/$NTFY_TOPIC" >/dev/null 2>&1 || true +} + +kuma_push() { + local status="$1" msg="$2" + [[ -n "$KUMA_PUSH_URL" ]] || return 0 + curl -fsS -G \ + --data-urlencode "status=$status" \ + --data-urlencode "msg=$msg" \ + "$KUMA_PUSH_URL" >/dev/null 2>&1 || true +} + +# --- query Forgejo ---------------------------------------------------------- +resp="$(curl -fsS -m 10 -H "Authorization: token $FORGEJO_RUNNER_API_TOKEN" \ + "$FORGEJO_RUNNER_API_URL" 2>/dev/null || true)" + +# The list endpoint may return a bare array or a {runners|entries:[...]} wrapper. +runners='[]' +if [[ -n "$resp" ]]; then + runners="$(jq -c 'if type=="array" then . else (.runners // .entries // .data // []) end' <<<"$resp" 2>/dev/null || echo '[]')" +fi + +runner="$(jq -c --arg n "$RUNNER_NAME" 'map(select(.name==$n)) | .[0] // {}' <<<"$runners")" +status="$(jq -r '.status // "unknown"' <<<"$runner")" +busy="$(jq -r 'if .busy == true then true else false end' <<<"$runner")" + +state=down +case "$status" in + idle | active) state=up ;; +esac + +# --- homepage JSON ---------------------------------------------------------- +mkdir -p "$(dirname "$STATUS_JSON")" +jq -n \ + --arg state "$state" \ + --arg status "$status" \ + --arg runner "$RUNNER_NAME" \ + --argjson busy "$busy" \ + --arg checked "$(date -u +%FT%TZ)" \ + '{state:$state, status:$status, runner:$runner, busy:$busy, checked:$checked}' \ + >"$STATUS_JSON" + +# --- Uptime Kuma ------------------------------------------------------------ +if [[ "$state" == up ]]; then + kuma_push up "$RUNNER_NAME: $status" +else + kuma_push down "$RUNNER_NAME: $status — Forgejo does not see the runner" +fi + +# --- ntfy, only on a state change ------------------------------------------ +prev="$(cat "$STATE_FILE" 2>/dev/null || true)" +if [[ -z "$prev" ]]; then + printf '%s\n' "$state" >"$STATE_FILE" # seed on first run, no alert +elif [[ "$state" != "$prev" ]]; then + if [[ "$state" == down ]]; then + notify "Forgejo runner DOWN" urgent rotating_light \ + "$RUNNER_NAME is $status — Forgejo no longer sees the runner. Check the Mac mini: bash macOS/forgejo-runner/run.sh status" + else + notify "Forgejo runner recovered" default white_check_mark "$RUNNER_NAME is back ($status)" + fi + printf '%s\n' "$state" >"$STATE_FILE" +fi + +printf '[runner-status] %s: %s (%s)\n' "$RUNNER_NAME" "$state" "$status" diff --git a/linux-server/homepage/config/services.yaml b/linux-server/homepage/config/services.yaml index 9e79d98..32bdb96 100644 --- a/linux-server/homepage/config/services.yaml +++ b/linux-server/homepage/config/services.yaml @@ -207,6 +207,27 @@ type: gitea url: https://{{HOMEPAGE_VAR_FORGEJO_DOMAIN}} key: "{{HOMEPAGE_VAR_FORGEJO_TOKEN}}" + - forgejo-runner: + icon: mdi-robot + description: Mac mini CI runner + server: my-docker + container: forgejo-runner-status + widget: + type: customapi + # runner-status.sh writes this JSON; the loopback nginx in + # linux-server/forgejo serves it. homepage is host-networked. + url: http://localhost:8098/runner-status.json + refreshInterval: 60000 + mappings: + - field: state + label: Runner + format: text + - field: status + label: Status + format: text + - field: checked + label: Checked + format: relativeDate - syncthing: icon: si-syncthing href: https://{{HOMEPAGE_VAR_SYNCTHING_DOMAIN}}/ diff --git a/linux-server/post-install.md b/linux-server/post-install.md index f08343a..6e2236f 100644 --- a/linux-server/post-install.md +++ b/linux-server/post-install.md @@ -171,6 +171,36 @@ Everything Forgejo needs to restore from scratch lives in `FORGEJO_DATA_PATH` (d > Hot backups of the SQLite database are safe with Forgejo — it uses WAL mode. A simple `cp` or `rsync` of the data directory while Forgejo is running is sufficient. +#### Runner status monitor + +A host timer (`runner-status.sh`) asks Forgejo whether the Mac mini Actions +runner (see [`../../macOS/forgejo-runner/`](../../macOS/forgejo-runner/)) is +connected, and surfaces it three ways: the homepage **forgejo-runner** card, +an Uptime Kuma push monitor, and an ntfy alert when it drops (and recovers). +Optional — skip if you aren't running CI. + +- [ ] In `forgejo/.env`, set `FORGEJO_RUNNER_API_TOKEN` (a Forgejo token that can + read runners; the default API URL is instance/admin scope) and + `RUNNER_NAME` (the name shown under **Settings → Actions → Runners**, + default `m4-mini`). Optionally set `KUMA_PUSH_URL` (create a Push monitor + in Uptime Kuma and paste its URL) and the `NTFY_*` vars. +- [ ] Start the loopback status server (shipped in `forgejo/docker-compose.yml`): + ```sh + cd linux-server/forgejo && docker compose up -d forgejo-runner-status + ``` +- [ ] Install the timer (polls every 2 minutes): + ```sh + sudo cp forgejo-runner-status.service forgejo-runner-status.timer /etc/systemd/system/ + sudo systemctl daemon-reload + sudo systemctl enable --now forgejo-runner-status.timer + ``` +- [ ] Verify: + ```sh + sudo systemctl start forgejo-runner-status.service + cat runner-status/runner-status.json # "state": "up" when connected + ``` + The homepage card reads the same JSON; Uptime Kuma shows up/down history. + #### Cloning / remotes from Mac ```sh From 0eb2cf3e4c82e7dacedc890c5b14acb99c226439 Mon Sep 17 00:00:00 2001 From: Ulises Chavarria Date: Mon, 22 Jun 2026 03:19:45 -0700 Subject: [PATCH 8/9] refactor(homepage): move the NAS group to the bottom of the dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorder services.yaml groups to Management, Network, Storage, NAS. Pure block move — no service entries or widgets changed. Co-Authored-By: Claude Opus 4.8 (1M context) --- linux-server/homepage/config/services.yaml | 138 ++++++++++----------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/linux-server/homepage/config/services.yaml b/linux-server/homepage/config/services.yaml index 32bdb96..a2794e8 100644 --- a/linux-server/homepage/config/services.yaml +++ b/linux-server/homepage/config/services.yaml @@ -127,75 +127,6 @@ server: my-docker container: ntfy -- NAS: - - wd 14tb r/w: - icon: mdi-harddisk - href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ - description: I/O — /mnt/wd14tb - server: my-docker - container: glances - widget: - type: glances - url: http://localhost:61208 - version: 4 - # USB enclosure: kernel names (sda/sdb/sdc) can reorder across reboots. - metric: disk:sdb - - wd 14tb usage: - icon: mdi-harddisk - href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ - description: Capacity — /mnt/wd14tb - server: my-docker - container: glances - widget: - type: glances - url: http://localhost:61208 - version: 4 - metric: fs:/mnt/wd14tb - - seagate 4tb r/w: - icon: mdi-harddisk - href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ - description: I/O — /mnt/seagate4tb - server: my-docker - container: glances - widget: - type: glances - url: http://localhost:61208 - version: 4 - metric: disk:sdc - - seagate 4tb usage: - icon: mdi-harddisk - href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ - description: Capacity — /mnt/seagate4tb - server: my-docker - container: glances - widget: - type: glances - url: http://localhost:61208 - version: 4 - metric: fs:/mnt/seagate4tb - - wd 1tb r/w: - icon: mdi-harddisk - href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ - description: I/O — /mnt/wd1tb - server: my-docker - container: glances - widget: - type: glances - url: http://localhost:61208 - version: 4 - metric: disk:sda - - wd 1tb usage: - icon: mdi-harddisk - href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ - description: Capacity — /mnt/wd1tb - server: my-docker - container: glances - widget: - type: glances - url: http://localhost:61208 - version: 4 - metric: fs:/mnt/wd1tb - - Storage: - forgejo: icon: si-forgejo @@ -272,3 +203,72 @@ url: https://{{HOMEPAGE_VAR_QBITTORRENT_DOMAIN}} username: "{{HOMEPAGE_VAR_QBITTORRENT_USERNAME}}" password: "{{HOMEPAGE_VAR_QBITTORRENT_PASSWORD}}" + +- NAS: + - wd 14tb r/w: + icon: mdi-harddisk + href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ + description: I/O — /mnt/wd14tb + server: my-docker + container: glances + widget: + type: glances + url: http://localhost:61208 + version: 4 + # USB enclosure: kernel names (sda/sdb/sdc) can reorder across reboots. + metric: disk:sdb + - wd 14tb usage: + icon: mdi-harddisk + href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ + description: Capacity — /mnt/wd14tb + server: my-docker + container: glances + widget: + type: glances + url: http://localhost:61208 + version: 4 + metric: fs:/mnt/wd14tb + - seagate 4tb r/w: + icon: mdi-harddisk + href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ + description: I/O — /mnt/seagate4tb + server: my-docker + container: glances + widget: + type: glances + url: http://localhost:61208 + version: 4 + metric: disk:sdc + - seagate 4tb usage: + icon: mdi-harddisk + href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ + description: Capacity — /mnt/seagate4tb + server: my-docker + container: glances + widget: + type: glances + url: http://localhost:61208 + version: 4 + metric: fs:/mnt/seagate4tb + - wd 1tb r/w: + icon: mdi-harddisk + href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ + description: I/O — /mnt/wd1tb + server: my-docker + container: glances + widget: + type: glances + url: http://localhost:61208 + version: 4 + metric: disk:sda + - wd 1tb usage: + icon: mdi-harddisk + href: https://{{HOMEPAGE_VAR_GLANCES_DOMAIN}}/ + description: Capacity — /mnt/wd1tb + server: my-docker + container: glances + widget: + type: glances + url: http://localhost:61208 + version: 4 + metric: fs:/mnt/wd1tb From 8dddf05ee2152747ec56fa2960a1deabbe0d8e66 Mon Sep 17 00:00:00 2001 From: Ulises Chavarria Date: Mon, 22 Jun 2026 03:27:15 -0700 Subject: [PATCH 9/9] chore(railguard): enable project-local fence overrides; ignore stray root policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Set fence.allow_local_overrides: true in the active policy so a project can ship an additive .railguard.local.yaml (existing railguard feature; only ADDs to allowed_paths, never weakens denies — grants nothing on its own). - Gitignore the stray repo-root railguard.yaml (a `railguard init` starter); the real policy lives in agentic-ai/Claude/railguard.yaml. Anchored so only the root file is ignored. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 3 +++ agentic-ai/Claude/railguard.yaml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index b07ea0f..d4c7bc5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ Thumbs.db # Railguard per-session traces and snapshots .railguard/ +# Stray `railguard init` starter in the repo root — the real policy lives in +# agentic-ai/Claude/railguard.yaml. Anchored so only the root file is ignored. +/railguard.yaml linux-server/adguard/work/ linux-server/adguard/conf/ diff --git a/agentic-ai/Claude/railguard.yaml b/agentic-ai/Claude/railguard.yaml index e8f7aff..9cbca6f 100644 --- a/agentic-ai/Claude/railguard.yaml +++ b/agentic-ai/Claude/railguard.yaml @@ -26,6 +26,10 @@ allowlist: [] fence: enabled: true + # Opt in to project-local `.railguard.local.yaml` files, which may only ADD + # to allowed_paths (never remove denies; denied_paths always win). Grants + # nothing by itself — each project must ship its own .railguard.local.yaml. + allow_local_overrides: true allowed_paths: - "~/.claude" - "/tmp"