#!/bin/sh set -eu SERVICE_NAME="incus-backup-agent" DEFAULT_REPO_URL="https://forgejo.digital-droplets.de/philschlo/incus-backup-ui.git" INSTALL_DIR="${INSTALL_DIR:-/opt/incus-backup-ui}" REPO_URL="${INCUS_BACKUP_REPO_URL:-$DEFAULT_REPO_URL}" BRANCH="${INCUS_BACKUP_BRANCH:-main}" AGENT_DIR="$INSTALL_DIR/agent" OLD_AGENT_DIR="$INSTALL_DIR/backend" STATE_DIR="${STATE_DIR:-/var/lib/incus-backup-agent}" CONFIG_DIR="${CONFIG_DIR:-/etc/incus-backup-agent}" ENV_FILE="$AGENT_DIR/.env" SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service" log() { printf '%s\n' "$*" } fail() { printf 'ERROR: %s\n' "$*" >&2 exit 1 } need_cmd() { command -v "$1" >/dev/null 2>&1 || fail "Required command not found: $1" } node_major_version() { node -p "Number(process.versions.node.split('.')[0])" } check_node_version() { need_cmd node major=$(node_major_version) if [ "$major" -lt 22 ]; then fail "Node.js 22.5 or newer is required. Found: $(node -v). Install a current Node.js release and rerun this installer." fi } as_root() { if [ "$(id -u)" -eq 0 ]; then "$@" return fi command -v sudo >/dev/null 2>&1 || fail "Run as root or install sudo." sudo "$@" } copy_repo_from_script_location() { script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) repo_dir=$(CDPATH= cd -- "$script_dir/.." && pwd) [ -d "$repo_dir/agent" ] || return 1 mkdir -p "$INSTALL_DIR" if [ "$repo_dir" = "$INSTALL_DIR" ]; then return 0 fi command -v rsync >/dev/null 2>&1 || return 1 rsync -a --delete \ --exclude '.git' \ --exclude 'agent/.env' \ --exclude 'agent/node_modules' \ --exclude 'management/node_modules' \ --exclude 'frontend/node_modules' \ "$repo_dir/" "$INSTALL_DIR/" } checkout_or_update_repo() { need_cmd git if [ -d "$INSTALL_DIR/.git" ]; then log "Updating $INSTALL_DIR from Git..." git -C "$INSTALL_DIR" fetch --prune origin "$BRANCH" git -C "$INSTALL_DIR" checkout "$BRANCH" git -C "$INSTALL_DIR" pull --ff-only origin "$BRANCH" return fi if copy_repo_from_script_location; then log "Installed files from local checkout to $INSTALL_DIR." return fi log "Cloning $REPO_URL to $INSTALL_DIR..." mkdir -p "$(dirname "$INSTALL_DIR")" git clone --branch "$BRANCH" "$REPO_URL" "$INSTALL_DIR" } move_old_agent_dir() { if [ -d "$OLD_AGENT_DIR" ] && [ ! -d "$AGENT_DIR" ]; then log "Renaming old backend directory to agent..." mv "$OLD_AGENT_DIR" "$AGENT_DIR" fi if [ -f "$OLD_AGENT_DIR/.env" ] && [ ! -f "$ENV_FILE" ]; then log "Moving existing backend .env to agent .env..." mv "$OLD_AGENT_DIR/.env" "$ENV_FILE" fi } ensure_env_file() { [ -d "$AGENT_DIR" ] || fail "Agent directory not found: $AGENT_DIR" if [ ! -f "$ENV_FILE" ]; then cp "$AGENT_DIR/.env.example" "$ENV_FILE" chmod 600 "$ENV_FILE" fi } env_has_key() { key="$1" grep -Eq "^[[:space:]]*$key=" "$ENV_FILE" } append_env_if_missing() { key="$1" value="$2" if ! env_has_key "$key"; then printf '%s="%s"\n' "$key" "$value" >> "$ENV_FILE" fi } replace_env_value() { key="$1" value="$2" tmp_file="$ENV_FILE.tmp" awk -v key="$key" -v value="$value" ' BEGIN { done = 0 } $0 ~ "^[[:space:]]*" key "=" { print key "=\"" value "\"" done = 1 next } { print } END { if (!done) print key "=\"" value "\"" } ' "$ENV_FILE" > "$tmp_file" mv "$tmp_file" "$ENV_FILE" chmod 600 "$ENV_FILE" } current_env_value() { key="$1" awk -F= -v key="$key" ' $1 == key { value = $0 sub("^[^=]*=", "", value) gsub(/^"/, "", value) gsub(/"$/, "", value) print value exit } ' "$ENV_FILE" } random_token() { if command -v openssl >/dev/null 2>&1; then openssl rand -hex 32 return fi need_cmd od od -An -N32 -tx1 /dev/urandom | tr -d ' \n' } configure_env() { append_env_if_missing "AGENT_DATABASE_PATH" "$STATE_DIR/agent.sqlite" replace_env_value "AGENT_DATABASE_PATH" "${AGENT_DATABASE_PATH:-$STATE_DIR/agent.sqlite}" if [ -n "${API_TOKEN:-}" ]; then replace_env_value "API_TOKEN" "$API_TOKEN" else token=$(current_env_value API_TOKEN) if [ ${#token} -lt 32 ] || [ "$token" = "change-me-to-at-least-32-characters" ]; then generated_token=$(random_token) replace_env_value "API_TOKEN" "$generated_token" log "Generated API_TOKEN for this agent:" log "$generated_token" log "Store this token in the management node configuration." fi fi [ -z "${HOST:-}" ] || replace_env_value "HOST" "$HOST" [ -z "${PORT:-}" ] || replace_env_value "PORT" "$PORT" [ -z "${ALLOWED_MANAGEMENT_IPS:-}" ] || replace_env_value "ALLOWED_MANAGEMENT_IPS" "$ALLOWED_MANAGEMENT_IPS" [ -z "${HTTPS_ENABLED:-}" ] || replace_env_value "HTTPS_ENABLED" "$HTTPS_ENABLED" [ -z "${TLS_CERT_FILE:-}" ] || replace_env_value "TLS_CERT_FILE" "$TLS_CERT_FILE" [ -z "${TLS_KEY_FILE:-}" ] || replace_env_value "TLS_KEY_FILE" "$TLS_KEY_FILE" } install_dependencies() { need_cmd npm check_node_version log "Installing agent dependencies..." if [ -f "$AGENT_DIR/package-lock.json" ]; then npm --prefix "$AGENT_DIR" ci --omit=dev else npm --prefix "$AGENT_DIR" install --omit=dev fi } install_systemd_service() { install -d -m 700 "$STATE_DIR" install -d -m 700 "$CONFIG_DIR" tmp_service="$SERVICE_FILE.tmp" awk -v agent_dir="$AGENT_DIR" ' /^WorkingDirectory=/ { print "WorkingDirectory=" agent_dir next } { print } ' "$INSTALL_DIR/deploy/systemd/$SERVICE_NAME.service" > "$tmp_service" chmod 644 "$tmp_service" mv "$tmp_service" "$SERVICE_FILE" systemctl daemon-reload systemctl enable "$SERVICE_NAME" } start_service() { if [ "${SKIP_START:-false}" = "true" ]; then log "SKIP_START=true; service was installed but not started." return fi systemctl restart "$SERVICE_NAME" systemctl --no-pager --full status "$SERVICE_NAME" || true } main() { need_cmd awk need_cmd grep as_root sh -c "true" if [ "$(id -u)" -ne 0 ]; then exec sudo \ INSTALL_DIR="$INSTALL_DIR" \ INCUS_BACKUP_REPO_URL="$REPO_URL" \ INCUS_BACKUP_BRANCH="$BRANCH" \ STATE_DIR="$STATE_DIR" \ CONFIG_DIR="$CONFIG_DIR" \ API_TOKEN="${API_TOKEN:-}" \ AGENT_DATABASE_PATH="${AGENT_DATABASE_PATH:-}" \ HOST="${HOST:-}" \ PORT="${PORT:-}" \ ALLOWED_MANAGEMENT_IPS="${ALLOWED_MANAGEMENT_IPS:-}" \ HTTPS_ENABLED="${HTTPS_ENABLED:-}" \ TLS_CERT_FILE="${TLS_CERT_FILE:-}" \ TLS_KEY_FILE="${TLS_KEY_FILE:-}" \ SKIP_START="${SKIP_START:-false}" \ sh "$0" fi checkout_or_update_repo move_old_agent_dir ensure_env_file configure_env install_dependencies install_systemd_service start_service log "Agent install/update complete." log "Config: $ENV_FILE" log "Service: $SERVICE_NAME" } main "$@"