265 lines
6.7 KiB
Bash
Executable File
265 lines
6.7 KiB
Bash
Executable File
#!/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 "$@"
|