234 lines
5.6 KiB
Bash
234 lines
5.6 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ===========================
|
|
# SoloForge Toolkit (forge)
|
|
# ===========================
|
|
#
|
|
# Handy commands for managing:
|
|
# - Gitea (SoloForge)
|
|
# - Gitea Actions runner
|
|
# - Backups
|
|
#
|
|
# Paths: adjust if you move things.
|
|
GITEA_DIR="/gitea"
|
|
RUNNER_DIR="/gitea/gitea-runner"
|
|
BACKUP_DIR="/gitea/backups"
|
|
|
|
GITEA_COMPOSE="$GITEA_DIR/docker-compose.yml"
|
|
RUNNER_COMPOSE="$RUNNER_DIR/docker-compose.yml"
|
|
|
|
GITEA_CONTAINER_NAME="Gitea"
|
|
RUNNER_CONTAINER_NAME="gitea-act-runner"
|
|
|
|
# Prefer `docker compose`, fall back to `docker-compose` if needed
|
|
dc() {
|
|
if command -v docker >/dev/null 2>&1; then
|
|
if docker compose version >/dev/null 2>&1; then
|
|
docker compose "$@"
|
|
elif command -v docker-compose >/dev/null 2>&1; then
|
|
docker-compose "$@"
|
|
else
|
|
echo "Error: neither 'docker compose' nor 'docker-compose' found." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "Error: docker not found in PATH." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
SoloForge toolkit
|
|
|
|
Usage:
|
|
forge status Show status of Gitea and runner containers
|
|
forge ps Alias for status
|
|
forge gitea-logs Tail logs from Gitea container
|
|
forge runner-logs Tail logs from Actions runner container
|
|
forge backup Run a Gitea dump and move it into BACKUP_DIR
|
|
forge restore-test Run backup restore sanity checks (unzip + extract)
|
|
forge restart-gitea Restart Gitea stack
|
|
forge restart-runner Restart Actions runner stack
|
|
forge runner-reset Re-register runner with current labels (destroys .runner)
|
|
forge diag Quick diagnostic summary
|
|
|
|
Config:
|
|
GITEA_DIR = $GITEA_DIR
|
|
RUNNER_DIR = $RUNNER_DIR
|
|
BACKUP_DIR = $BACKUP_DIR
|
|
EOF
|
|
}
|
|
|
|
ensure_dirs() {
|
|
mkdir -p "$BACKUP_DIR"
|
|
}
|
|
|
|
cmd_status() {
|
|
echo "== Docker containers =="
|
|
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' \
|
|
| grep -E "$GITEA_CONTAINER_NAME|$RUNNER_CONTAINER_NAME" || true
|
|
echo
|
|
echo "== Gitea compose =="
|
|
echo " $GITEA_COMPOSE"
|
|
echo "== Runner compose =="
|
|
echo " $RUNNER_COMPOSE"
|
|
}
|
|
|
|
cmd_gitea_logs() {
|
|
echo "Tailing logs for container: $GITEA_CONTAINER_NAME"
|
|
docker logs -f "$GITEA_CONTAINER_NAME"
|
|
}
|
|
|
|
cmd_runner_logs() {
|
|
echo "Tailing logs for container: $RUNNER_CONTAINER_NAME"
|
|
docker logs -f "$RUNNER_CONTAINER_NAME"
|
|
}
|
|
|
|
cmd_backup() {
|
|
ensure_dirs
|
|
|
|
local ts dump dest
|
|
|
|
ts=$(date +%Y%m%d-%H%M%S)
|
|
dump="$GITEA_DIR/gitea/gitea-dump-$ts.zip"
|
|
dest="$BACKUP_DIR/gitea-dump-$ts.zip"
|
|
|
|
echo "Running Gitea dump inside container: $GITEA_CONTAINER_NAME"
|
|
if ! docker exec -u 1000 "$GITEA_CONTAINER_NAME" sh -lc \
|
|
"gitea dump -c /data/gitea/conf/app.ini --file /data/gitea-dump-$ts.zip"; then
|
|
echo "Error: gitea dump failed" >&2
|
|
forge-alert.sh "ERROR" "backup" "gitea dump failed"
|
|
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$dump" ]]; then
|
|
echo "Error: expected dump file not found: $dump" >&2
|
|
forge-alert.sh "ERROR" "backup" "dump file missing: $dump"
|
|
|
|
exit 1
|
|
fi
|
|
|
|
echo "Moving $dump -> $dest"
|
|
mv "$dump" "$dest"
|
|
|
|
echo "Backup complete: $dest"
|
|
forge-alert.sh "INFO" "backup" "Backup complete: $(basename "$dest")"
|
|
}
|
|
|
|
cmd_restart_gitea() {
|
|
echo "Restarting Gitea stack (compose: $GITEA_COMPOSE)"
|
|
(cd "$GITEA_DIR" && dc -f "$GITEA_COMPOSE" down)
|
|
(cd "$GITEA_DIR" && dc -f "$GITEA_COMPOSE" up -d)
|
|
echo "Gitea stack restarted."
|
|
}
|
|
|
|
cmd_restart_runner() {
|
|
echo "Restarting runner stack (compose: $RUNNER_COMPOSE)"
|
|
(cd "$RUNNER_DIR" && dc -f "$RUNNER_COMPOSE" down || true)
|
|
(cd "$RUNNER_DIR" && dc -f "$RUNNER_COMPOSE" up -d)
|
|
echo "Runner stack restarted."
|
|
}
|
|
|
|
cmd_runner_reset() {
|
|
echo "Resetting runner registration..."
|
|
(cd "$RUNNER_DIR" && dc -f "$RUNNER_COMPOSE" down || true)
|
|
|
|
local runner_state="$RUNNER_DIR/data/.runner"
|
|
if [[ -f "$runner_state" ]]; then
|
|
echo "Removing $runner_state"
|
|
rm -f "$runner_state"
|
|
else
|
|
echo "No existing .runner file found (nothing to delete)."
|
|
fi
|
|
|
|
echo "Bringing runner stack up (will auto-register with current env)..."
|
|
(cd "$RUNNER_DIR" && dc -f "$RUNNER_COMPOSE" up -d)
|
|
|
|
echo "Runner reset requested. Check runner logs via: forge runner-logs"
|
|
}
|
|
|
|
cmd_diag() {
|
|
echo "== SoloForge diagnostic =="
|
|
echo
|
|
echo "-- Paths --"
|
|
echo "GITEA_DIR = $GITEA_DIR"
|
|
echo "RUNNER_DIR = $RUNNER_DIR"
|
|
echo "BACKUP_DIR = $BACKUP_DIR"
|
|
echo
|
|
|
|
echo "-- Containers --"
|
|
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' \
|
|
| grep -E "$GITEA_CONTAINER_NAME|$RUNNER_CONTAINER_NAME" || echo " (no Gitea/runner containers found)"
|
|
echo
|
|
|
|
echo "-- Runner .runner file --"
|
|
if [[ -f "$RUNNER_DIR/data/.runner" ]]; then
|
|
echo "Found: $RUNNER_DIR/data/.runner"
|
|
else
|
|
echo "No .runner file present (runner may not be registered)."
|
|
fi
|
|
echo
|
|
|
|
echo "-- Latest backup --"
|
|
if [[ -d "$BACKUP_DIR" ]]; then
|
|
local latest
|
|
latest=$(ls -1t "$BACKUP_DIR"/gitea-dump-*.zip 2>/dev/null | head -n1 || true)
|
|
if [[ -n "$latest" ]]; then
|
|
echo " $latest"
|
|
else
|
|
echo " No backups found in $BACKUP_DIR"
|
|
fi
|
|
else
|
|
echo " Backup dir does not exist: $BACKUP_DIR"
|
|
fi
|
|
}
|
|
|
|
# ===========================
|
|
# Dispatch
|
|
# ===========================
|
|
|
|
cmd="${1:-help}"
|
|
shift || true
|
|
|
|
case "$cmd" in
|
|
status|ps)
|
|
cmd_status
|
|
;;
|
|
gitea-logs)
|
|
cmd_gitea_logs
|
|
;;
|
|
runner-logs)
|
|
cmd_runner_logs
|
|
;;
|
|
backup)
|
|
cmd_backup
|
|
;;
|
|
restore-test)
|
|
/usr/local/bin/forge-restore-test.sh
|
|
;;
|
|
restart-gitea)
|
|
cmd_restart_gitea
|
|
;;
|
|
restart-runner)
|
|
cmd_restart_runner
|
|
;;
|
|
runner-reset)
|
|
cmd_runner_reset
|
|
;;
|
|
diag)
|
|
cmd_diag
|
|
;;
|
|
help|--help|-h)
|
|
usage
|
|
;;
|
|
*)
|
|
echo "Unknown command: $cmd" >&2
|
|
echo
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|