From 71f7755c455da0b6b8312da74a980f16d755d352 Mon Sep 17 00:00:00 2001 From: Florian <52180080+florianjs@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:43:31 +0200 Subject: [PATCH 1/2] feat(monitoring): pairing script and step-by-step beszel guide scripts/pair-monitoring.sh asks for the key and token shown by Beszel, writes them to .env with the monitoring profile, starts the agent and checks it stays up. The guide now walks through each screen (checked against Beszel 0.21.0) and where to run what on the server. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- doc/guide/monitoring.md | 72 ++++++++++++++++++----- doc/guide/troubleshooting.md | 2 +- scripts/install.sh | 4 +- scripts/pair-monitoring.sh | 108 +++++++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 17 deletions(-) create mode 100755 scripts/pair-monitoring.sh diff --git a/README.md b/README.md index 96734df8..d5891406 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ The installer will: > **Monitoring**: After installation, open `https://monitoring.your-domain.com` right away. It is protected by basic auth (user `admin`, password shown at the end of the install); behind it, the first visitor creates the Beszel admin account. > -> Then pair the agent once: in Beszel, click **Add system**, use host `/beszel_socket/beszel.sock`, copy the public key and token into `.env` as `BESZEL_AGENT_KEY` and `BESZEL_AGENT_TOKEN`, add `COMPOSE_PROFILES=monitoring`, and run `docker compose -f docker-compose.prod.yml up -d`. See the [Monitoring guide](https://florianjs.github.io/trackarr/guide/monitoring). +> Then pair the agent once: in Beszel, click **Add system** with host `/beszel_socket/beszel.sock`, then run `sudo ./scripts/pair-monitoring.sh` on the server and paste the public key and token it shows. Step by step in the [Monitoring guide](https://florianjs.github.io/trackarr/guide/monitoring#pairing-the-agent). > > Application and tracker figures (growth charts, tracker health, security alerts) are in the Trackarr admin dashboard. diff --git a/doc/guide/monitoring.md b/doc/guide/monitoring.md index 0f67f262..22e4f91b 100644 --- a/doc/guide/monitoring.md +++ b/doc/guide/monitoring.md @@ -30,24 +30,68 @@ In production, Caddy puts basic auth in front of Beszel (`MONITORING_USER` / `MO ### Pairing the agent -Do this once, after the first start (or after upgrading from Grafana): +Do this once, after the first install (or after upgrading from Grafana). It connects the agent, which collects the stats, to the Beszel web UI. Until then, Beszel shows no system. -1. Open the monitoring URL **right away**: the first visitor creates the Beszel admin account, which is why basic auth stays in front of it. -2. Create the admin account. -3. Click **Add system** and use `/beszel_socket/beszel.sock` as the host. -4. Copy the public key and token shown into `.env`, and enable the profile: +You need two windows: a **browser** for Beszel, and a **terminal on the server** for the agent. - ```bash - BESZEL_AGENT_KEY= - BESZEL_AGENT_TOKEN= - COMPOSE_PROFILES=monitoring - ``` +#### 1. Create the Beszel admin account -5. Start the agent: +Open `https://monitoring.your-domain.com` **right away**: the first visitor creates the Beszel admin account. - ```bash - docker compose -f docker-compose.prod.yml up -d - ``` +1. The browser first asks for the basic auth login: user `admin`, and the monitoring password shown at the end of the install. +2. Beszel then asks you to create its own admin account (email and password). Keep them: this is your Beszel login from now on. + +#### 2. Add the server in Beszel + +1. Click **Add system** (top right). +2. Fill in the form: + + | Field | Value | + | --------- | ---------------------------- | + | Name | anything, e.g. `trackarr` | + | Host / IP | `/beszel_socket/beszel.sock` | + + The host is a socket shared by the two containers, not an address: the **Port** field disappears once you type it. +3. Below, the dialog shows a **public key** (starts with `ssh-ed25519`) and a **token**, each with a copy button. You will paste them on the server in the next step. +4. Click **Add system**. The system appears with a yellow dot: it is waiting for the agent. + +#### 3. Start the agent on the server + +Connect to the server and go to the Trackarr directory (`/opt/trackarr` when installed with `install.sh`): + +```bash +ssh root@your-server +cd /opt/trackarr +sudo ./scripts/pair-monitoring.sh +``` + +Paste the public key, then the token, when asked. The script writes them to `.env`, turns the monitoring profile on, starts the agent and checks it stays up. + +The dot turns green in Beszel within a minute. + +::: details Doing it by hand instead +Open `.env` in the Trackarr directory (`nano .env`). The installer left three commented lines for the agent: uncomment them and fill in the values, without quotes: + +```bash +BESZEL_AGENT_KEY=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... +BESZEL_AGENT_TOKEN=0d1e2f3a-... +COMPOSE_PROFILES=monitoring +``` + +Save (Ctrl+O, Enter, Ctrl+X in nano), then start the agent: + +```bash +docker compose -f docker-compose.prod.yml up -d +``` +::: + +For the local production stack, run `./scripts/pair-monitoring.sh --local` (it uses `.env.local-prod`). + +#### If the dot does not turn green + +- Check the agent: `docker compose -f docker-compose.prod.yml ps beszel-agent` should say `running`. +- Read its logs: `docker compose -f docker-compose.prod.yml logs beszel-agent`. A key or token error means a copy/paste mistake: run the script again with the values from Beszel. To see them again, click **⋯** at the end of the system's row, then **Edit**. +- More cases in [Troubleshooting](/guide/troubleshooting). The installers keep `BESZEL_AGENT_KEY`, `BESZEL_AGENT_TOKEN` and `COMPOSE_PROFILES` when you re-run them. diff --git a/doc/guide/troubleshooting.md b/doc/guide/troubleshooting.md index 731272f0..1636e233 100644 --- a/doc/guide/troubleshooting.md +++ b/doc/guide/troubleshooting.md @@ -323,7 +323,7 @@ e21V5@postgres:5432/trackarr = host=postgres port=5432 auth_user=tracker **Solutions:** -1. Pair the agent as described in [Monitoring](/guide/monitoring#pairing-the-agent) (host `/beszel_socket/beszel.sock`), and check `.env`: +1. Pair the agent as described in [Monitoring](/guide/monitoring#pairing-the-agent): add the system in Beszel (host `/beszel_socket/beszel.sock`), then run `sudo ./scripts/pair-monitoring.sh` in the Trackarr directory. It writes these lines to `.env`: ``` BESZEL_AGENT_KEY=... diff --git a/scripts/install.sh b/scripts/install.sh index 02149e32..3fed3b6e 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -734,7 +734,9 @@ print_summary() { echo -e " 2. Monitoring is paired and running." else echo -e " 2. Open ${CYAN}https://${MONITORING_DOMAIN}${NC} now: the first visitor creates the" - echo -e " Beszel admin. Then pair this server: ${CYAN}${DOCS_URL}/guide/monitoring${NC}" + echo -e " Beszel admin. Click Add system, host ${CYAN}/beszel_socket/beszel.sock${NC}, then run" + echo -e " ${CYAN}sudo ${INSTALL_DIR}/scripts/pair-monitoring.sh${NC} and paste the key and token." + echo -e " Step by step: ${CYAN}${DOCS_URL}/guide/monitoring#pairing-the-agent${NC}" fi echo "" echo -e " ${BOLD}Useful commands${NC} (in ${INSTALL_DIR})" diff --git a/scripts/pair-monitoring.sh b/scripts/pair-monitoring.sh new file mode 100755 index 00000000..3ff91162 --- /dev/null +++ b/scripts/pair-monitoring.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# ============================================================================= +# Trackarr - Pair the Beszel agent +# ============================================================================= +# Asks for the public key and token shown by Beszel's "Add system" dialog, +# writes them to .env, enables the monitoring profile and starts the agent. +# +# Usage: sudo ./scripts/pair-monitoring.sh [--local] +# --local local production stack (docker-compose.local.yml, .env.local-prod) +# +# Unattended: BESZEL_AGENT_KEY=... BESZEL_AGENT_TOKEN=... ./scripts/pair-monitoring.sh +# ============================================================================= + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +ok() { echo -e " ${GREEN}✓${NC} $1"; } +warn() { echo -e " ${YELLOW}!${NC} $1"; } +die() { echo -e " ${RED}✗${NC} $1" >&2; exit 1; } + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +ENV_FILE="$PROJECT_DIR/.env" +COMPOSE_FILE="docker-compose.prod.yml" + +case "${1:-}" in + --local) + COMPOSE_FILE="docker-compose.local.yml" + ENV_FILE="$PROJECT_DIR/.env.local-prod" + ;; + -h|--help) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + "") ;; + *) die "Unknown option: $1 (see --help)" ;; +esac + +[[ -f $ENV_FILE ]] || die "$ENV_FILE not found: run the installer first" +COMPOSE=(docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE") +LOGS_CMD="${COMPOSE[*]} logs beszel-agent" +[[ -w $ENV_FILE ]] || die "Cannot write $ENV_FILE: run with sudo" +command -v docker >/dev/null || die "Docker is not installed" + +# Replace KEY=... (or a commented # KEY=...) in .env, or append it +set_env() { + local key=$1 value=$2 tmp + tmp=$(mktemp) + awk -v k="$key" -v v="$value" ' + $0 ~ "^#? *" k "=" { if (!done) print k "=" v; done = 1; next } + { print } + END { if (!done) print k "=" v } + ' "$ENV_FILE" > "$tmp" + cat "$tmp" > "$ENV_FILE" + rm -f "$tmp" +} + +current_value() { + grep -m1 "^$1=" "$ENV_FILE" | cut -d= -f2- || true +} + +echo "" +echo -e " ${BOLD}Trackarr · pair the monitoring agent${NC}" +echo "" +echo " In Beszel: Add system, host /beszel_socket/beszel.sock, then copy the" +echo " public key and the token the dialog shows." +echo "" + +KEY=${BESZEL_AGENT_KEY:-} +TOKEN=${BESZEL_AGENT_TOKEN:-} +while [[ ! $KEY =~ ^ssh-ed25519\ [A-Za-z0-9+/=]+$ ]]; do + [[ -n $KEY ]] && warn "That is not a Beszel public key (it starts with ssh-ed25519)" + read -rp " Public key: " KEY + KEY=$(echo "$KEY" | xargs) +done +while [[ ! $TOKEN =~ ^[A-Za-z0-9_-]+$ ]]; do + [[ -n $TOKEN ]] && warn "That token contains unexpected characters" + read -rp " Token: " TOKEN + TOKEN=$(echo "$TOKEN" | xargs) +done + +set_env BESZEL_AGENT_KEY "$KEY" +set_env BESZEL_AGENT_TOKEN "$TOKEN" +profiles=$(current_value COMPOSE_PROFILES) +if [[ ,$profiles, != *,monitoring,* ]]; then + set_env COMPOSE_PROFILES "${profiles:+$profiles,}monitoring" +fi +ok "Key, token and monitoring profile saved in $ENV_FILE" + +cd "$PROJECT_DIR" +"${COMPOSE[@]}" up -d --force-recreate beszel-agent docker-proxy >/dev/null 2>&1 \ + || die "Could not start the agent: $LOGS_CMD" + +# The agent exits right away when the key is wrong: give it a moment +sleep 5 +if [[ $(docker inspect -f '{{.State.Running}}' trackarr-beszel-agent 2>/dev/null) == true ]]; then + ok "Agent running" + echo "" + echo " The system turns green in Beszel within a minute. If it does not," + echo -e " check the key and token, then: ${CYAN}${LOGS_CMD}${NC}" + echo "" +else + "${COMPOSE[@]}" logs --tail 20 beszel-agent >&2 || true + die "The agent stopped: check the key and token, then run this script again" +fi From d52eaeb21d85d24ec020570328edf342a3c59645 Mon Sep 17 00:00:00 2001 From: Florian <52180080+florianjs@users.noreply.github.com> Date: Tue, 20 Oct 2026 10:48:54 +0200 Subject: [PATCH 2/2] fix(monitoring): replace .env atomically and fail clearly without input Co-Authored-By: Claude Opus 5.5 --- scripts/pair-monitoring.sh | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/scripts/pair-monitoring.sh b/scripts/pair-monitoring.sh index 3ff91162..ecd13ae5 100755 --- a/scripts/pair-monitoring.sh +++ b/scripts/pair-monitoring.sh @@ -42,20 +42,27 @@ esac [[ -f $ENV_FILE ]] || die "$ENV_FILE not found: run the installer first" COMPOSE=(docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE") LOGS_CMD="${COMPOSE[*]} logs beszel-agent" -[[ -w $ENV_FILE ]] || die "Cannot write $ENV_FILE: run with sudo" +[[ -w $ENV_FILE && -w $PROJECT_DIR ]] || die "Cannot write $ENV_FILE: run with sudo" command -v docker >/dev/null || die "Docker is not installed" -# Replace KEY=... (or a commented # KEY=...) in .env, or append it +# Replace KEY=... (or a commented # KEY=...) in .env, or append it. +# Written to a copy next to .env, then renamed over it: a failed write +# (full disk) never leaves a truncated .env, which holds every secret set_env() { local key=$1 value=$2 tmp - tmp=$(mktemp) - awk -v k="$key" -v v="$value" ' + tmp=$(mktemp "$ENV_FILE.XXXXXX") + if ! awk -v k="$key" -v v="$value" ' $0 ~ "^#? *" k "=" { if (!done) print k "=" v; done = 1; next } { print } END { if (!done) print k "=" v } - ' "$ENV_FILE" > "$tmp" - cat "$tmp" > "$ENV_FILE" - rm -f "$tmp" + ' "$ENV_FILE" > "$tmp"; then + rm -f "$tmp" + die "Could not update $ENV_FILE (disk full?): it was left unchanged" + fi + # Keep the original mode and owner (GNU stat, then BSD stat) + chmod "$(stat -c %a "$ENV_FILE" 2>/dev/null || stat -f %Lp "$ENV_FILE")" "$tmp" + chown "$(stat -c %u:%g "$ENV_FILE" 2>/dev/null || stat -f %u:%g "$ENV_FILE")" "$tmp" 2>/dev/null || true + mv -f "$tmp" "$ENV_FILE" } current_value() { @@ -73,12 +80,14 @@ KEY=${BESZEL_AGENT_KEY:-} TOKEN=${BESZEL_AGENT_TOKEN:-} while [[ ! $KEY =~ ^ssh-ed25519\ [A-Za-z0-9+/=]+$ ]]; do [[ -n $KEY ]] && warn "That is not a Beszel public key (it starts with ssh-ed25519)" - read -rp " Public key: " KEY + read -rp " Public key: " KEY \ + || die "No input: unattended, set BESZEL_AGENT_KEY and BESZEL_AGENT_TOKEN to valid values" KEY=$(echo "$KEY" | xargs) done while [[ ! $TOKEN =~ ^[A-Za-z0-9_-]+$ ]]; do [[ -n $TOKEN ]] && warn "That token contains unexpected characters" - read -rp " Token: " TOKEN + read -rp " Token: " TOKEN \ + || die "No input: unattended, set BESZEL_AGENT_KEY and BESZEL_AGENT_TOKEN to valid values" TOKEN=$(echo "$TOKEN" | xargs) done