02e4ef7544
Smoothes the rough edges that came up exercising a live deployment.
First-run bootstrap UI: /bootstrap renders a username + password form
that uses the in-memory token directly (operator no longer copies it
out of the log); /login redirects there while bootstrap is available.
Agent reliability: failJob synthetic envelopes so command.run early
returns no longer hang the server-side job; runtime probe of restic
restore --help drives --no-ownership instead of version sniffing
(0.18.x had it removed). Server unit re-shaped: ProtectSystem=full
plus ReadWritePaths=/etc/restic-manager, no ProtectHome — restore
can now write anywhere a user might want.
Restore wizard: default target is /root/rm-restore/<job-id>/ with
clearer help text. Re-init confirm input uses .field (was .input,
which doesn't exist — text was invisible).
NS-01 host delete: store DeleteHost, admin-band /hosts/{id}/delete
with hostname-confirm danger zone, audit, FK cascade, live WS close.
NS-02 enrollment-token recovery: outstanding-tokens panel on
/hosts/new, regenerate (preserves attachments) and revoke handlers
+ audit, store-level ListOutstandingEnrollmentTokens and
DeleteEnrollmentToken.
NS-03 repo init / probe surface: migration 0020 adds
hosts.repo_status + repo_status_error; WS handler projects every
init job's outcome onto the host row (idempotent already-initialised
collapses to ready); creds-save resets status and dispatches a fresh
probe; /hosts/{id}/repo/probe retry endpoint with banner.
NS-04 dashboard live + sort + filter: query-string filter
(q/status/repo_status/tag/sort/dir), 5s htmx live poll mirroring the
alerts pattern with a localStorage live toggle, sortable column
headers, filter row + clear.
Alerts page: ack'd-by line resolves user_id ULID to username.
Compose.yaml ignored — host-specific.
153 lines
4.9 KiB
Bash
Executable File
153 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# install.sh — Linux installer for the restic-manager agent.
|
|
#
|
|
# Usage (paste in shell):
|
|
# curl -fsSL https://restic.lab.example/install/install.sh | \
|
|
# sudo RM_SERVER=https://restic.lab.example RM_TOKEN=<one-time-token> bash
|
|
#
|
|
# What it does:
|
|
# 1. detects arch (amd64 / arm64)
|
|
# 2. fetches the matching agent binary from the server
|
|
# 3. lays down /etc/restic-manager/, /var/lib/restic-manager/ (root:root, 0700)
|
|
# 4. enrolls (POST /api/agents/enroll) using RM_TOKEN
|
|
# 5. installs the systemd unit, enables, starts
|
|
# 6. surfaces (but does NOT disable) any existing restic timers /
|
|
# cron entries so the operator can decide what to do
|
|
#
|
|
# The agent runs as root. See restic-manager-agent.service for the
|
|
# rationale (in short: a fleet-backup tool must read every file on
|
|
# the system; trying to do that unprivileged buys very little
|
|
# security and creates large UX cliffs).
|
|
#
|
|
# Idempotent — safe to re-run; will refuse if already enrolled
|
|
# unless RM_FORCE_REENROLL=1 is set.
|
|
|
|
set -euo pipefail
|
|
|
|
: "${RM_SERVER:?must be set, e.g. https://restic.lab.example}"
|
|
: "${RM_TOKEN:?must be set, the one-time token from the operator UI}"
|
|
: "${RM_INSTALL_PREFIX:=/usr/local/bin}"
|
|
: "${RM_CONFIG_DIR:=/etc/restic-manager}"
|
|
: "${RM_STATE_DIR:=/var/lib/restic-manager}"
|
|
: "${RM_FORCE_REENROLL:=0}"
|
|
|
|
require_root() {
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "install.sh: must be run as root" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
detect_arch() {
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) echo amd64 ;;
|
|
aarch64|arm64) echo arm64 ;;
|
|
*) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
ensure_dirs() {
|
|
install -d -m 0700 -o root -g root "$RM_CONFIG_DIR"
|
|
install -d -m 0700 -o root -g root "$RM_STATE_DIR"
|
|
# Default new-directory restore target: $HOME/rm-restore. With the
|
|
# current unit (ProtectSystem=full, no ReadWritePaths pin) the agent
|
|
# can mkdir anywhere on real filesystems, so this is just a courtesy
|
|
# pre-create so the wizard's default lands in a tidy spot.
|
|
install -d -m 0700 -o root -g root /root/rm-restore
|
|
}
|
|
|
|
detect_existing_schedulers() {
|
|
echo
|
|
echo "==> Scanning for existing restic schedules (we will NOT touch them)"
|
|
local found=0
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
while IFS= read -r unit; do
|
|
[ -n "$unit" ] || continue
|
|
echo " [systemd] $unit"
|
|
echo " disable with: systemctl disable --now $unit"
|
|
found=1
|
|
done < <(systemctl list-unit-files --no-legend --type=timer 2>/dev/null \
|
|
| awk 'tolower($1) ~ /restic/ {print $1}')
|
|
fi
|
|
for f in /etc/cron.d/* /etc/cron.daily/* /etc/cron.hourly/* /etc/cron.weekly/*; do
|
|
[ -f "$f" ] || continue
|
|
if grep -qiI restic "$f" 2>/dev/null; then
|
|
echo " [cron] $f"
|
|
echo " review and remove or rename if you want this agent to take over"
|
|
found=1
|
|
fi
|
|
done
|
|
if root_cron=$(crontab -l 2>/dev/null); then
|
|
if echo "$root_cron" | grep -qi restic; then
|
|
echo " [crontab -l] root crontab contains a restic entry"
|
|
echo " review with: crontab -l"
|
|
found=1
|
|
fi
|
|
fi
|
|
if [ $found -eq 0 ]; then
|
|
echo " (none found)"
|
|
fi
|
|
echo
|
|
}
|
|
|
|
download_agent() {
|
|
local arch out
|
|
arch=$(detect_arch)
|
|
out="$RM_INSTALL_PREFIX/restic-manager-agent"
|
|
|
|
echo "==> Downloading restic-manager-agent (linux/$arch) from $RM_SERVER"
|
|
# The server's /agent/binary endpoint serves the matching binary
|
|
# for the requesting agent's arch. (P1-31; until then this URL
|
|
# may need to be a static download.)
|
|
curl -fsSL --retry 3 \
|
|
"$RM_SERVER/agent/binary?os=linux&arch=$arch" \
|
|
-o "$out.new"
|
|
chmod +x "$out.new"
|
|
mv -f "$out.new" "$out"
|
|
echo " installed $out ($(file -b "$out" | head -1))"
|
|
}
|
|
|
|
enroll_agent() {
|
|
local cfg="$RM_CONFIG_DIR/agent.yaml"
|
|
if [ -s "$cfg" ] && [ "$RM_FORCE_REENROLL" != "1" ]; then
|
|
echo "==> $cfg already exists; skipping enrollment"
|
|
echo " (set RM_FORCE_REENROLL=1 to overwrite)"
|
|
return
|
|
fi
|
|
|
|
echo "==> Enrolling agent with $RM_SERVER"
|
|
"$RM_INSTALL_PREFIX/restic-manager-agent" \
|
|
-config "$cfg" \
|
|
-enroll-server "$RM_SERVER" \
|
|
-enroll-token "$RM_TOKEN"
|
|
}
|
|
|
|
install_unit() {
|
|
local unit="/etc/systemd/system/restic-manager-agent.service"
|
|
echo "==> Installing systemd unit at $unit"
|
|
curl -fsSL --retry 3 \
|
|
"$RM_SERVER/install/restic-manager-agent.service" \
|
|
-o "$unit"
|
|
chmod 0644 "$unit"
|
|
systemctl daemon-reload
|
|
systemctl enable --now restic-manager-agent.service
|
|
echo " started; tail with: journalctl -fu restic-manager-agent"
|
|
}
|
|
|
|
main() {
|
|
require_root
|
|
ensure_dirs
|
|
download_agent
|
|
detect_existing_schedulers
|
|
enroll_agent
|
|
install_unit
|
|
echo
|
|
echo "==> done."
|
|
echo " config: $RM_CONFIG_DIR/agent.yaml"
|
|
echo " binary: $RM_INSTALL_PREFIX/restic-manager-agent"
|
|
echo " service: systemctl status restic-manager-agent"
|
|
echo " logs: journalctl -fu restic-manager-agent"
|
|
}
|
|
|
|
main "$@"
|