#!/bin/sh # ServerCrate backup installer # # curl -fsSL https://servercrate.net/install.sh | sh -s -- --repo "rest:https://vaultuser:TOKEN@SHORTID.vault.servercrate.net/" # # Installs restic, initializes your vault, takes the first snapshot, and installs a # daily systemd timer. Your encryption password is generated on THIS machine and never # leaves it - ServerCrate cannot read your data and cannot recover a lost password. set -eu REPO="" PASSWORD="" BACKUP_PATHS="" KEEP_DAILY=7 KEEP_WEEKLY=4 KEEP_MONTHLY=0 KEEP_YEARLY=0 # sc-install-retention-v1 KSET="" CONF_DIR=/etc/servercrate ENV_FILE="$CONF_DIR/backup.env" NONINTERACTIVE=0 CLEANUP="" # sc-install-cleanup-v1 say() { printf '%s\n' "$*"; } step() { printf '\n==> %s\n' "$*"; } die() { printf 'error: %s\n' "$*" >&2; exit 1; } usage() { cat <<'USAGE' ServerCrate backup installer --repo REPO restic repository string from your portal (required) --password PASS encryption password (default: generated here and shown once) --paths "A B C" directories to back up (default: detected) --keep-daily N daily snapshots to retain (default: 7) --keep-weekly N weekly snapshots to retain (default: 4) --keep-monthly N monthly snapshots to retain (default: 0) --keep-yearly N yearly snapshots to retain (default: 0) --cleanup-key KEY cleanup key from your vault page: trims old snapshots weekly --yes do not prompt USAGE } while [ $# -gt 0 ]; do case "$1" in --repo) REPO="${2:-}"; shift 2 ;; --password) PASSWORD="${2:-}"; shift 2 ;; --paths) BACKUP_PATHS="${2:-}"; shift 2 ;; --keep-daily) KEEP_DAILY="${2:-7}"; KSET="$KSET d"; shift 2 ;; --keep-weekly) KEEP_WEEKLY="${2:-4}"; KSET="$KSET w"; shift 2 ;; --keep-monthly) KEEP_MONTHLY="${2:-0}"; KSET="$KSET m"; shift 2 ;; --keep-yearly) KEEP_YEARLY="${2:-0}"; KSET="$KSET y"; shift 2 ;; --cleanup-key) CLEANUP="${2:-}"; shift 2 ;; --yes|-y) NONINTERACTIVE=1; shift ;; -h|--help) usage; exit 0 ;; *) die "unknown option: $1 (try --help)" ;; esac done [ "$(id -u)" = "0" ] || die "run as root: curl -fsSL https://servercrate.net/install.sh | sudo sh -s -- --repo \"...\"" # re-running with only --cleanup-key reuses the vault already configured here if [ -z "$REPO" ] && [ -f "$ENV_FILE" ]; then REPO="$(sed -n "s/^RESTIC_REPOSITORY='\(.*\)'\$/\1/p" "$ENV_FILE" | head -1)" fi [ -n "$REPO" ] || { usage; die "--repo is required (copy it from your vault page in the portal)"; } case "$REPO" in rest:https://*) : ;; *) die "--repo must look like rest:https://vaultuser:TOKEN@SHORTID.vault.servercrate.net/" ;; esac if [ -n "$CLEANUP" ]; then case "$CLEANUP" in rest:https://maint:*@*-maint.vault.servercrate.net/*) : ;; *) die "--cleanup-key must look like rest:https://maint:KEY@SHORTID-maint.vault.servercrate.net/ (copy it from your vault page)" ;; esac fi for n in "$KEEP_DAILY" "$KEEP_WEEKLY" "$KEEP_MONTHLY" "$KEEP_YEARLY"; do case "$n" in ""|*[!0-9]*) die "--keep-* values must be whole numbers" ;; esac done step "Installing restic" if command -v restic >/dev/null 2>&1; then say "already installed: $(restic version 2>/dev/null | head -1)" elif command -v apt-get >/dev/null 2>&1; then DEBIAN_FRONTEND=noninteractive apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -qq restic elif command -v dnf >/dev/null 2>&1; then dnf install -y -q restic elif command -v yum >/dev/null 2>&1; then yum install -y -q restic elif command -v apk >/dev/null 2>&1; then apk add --no-cache restic elif command -v pacman >/dev/null 2>&1; then pacman -Sy --noconfirm restic elif command -v zypper >/dev/null 2>&1; then zypper --non-interactive install restic else die "no supported package manager found. Install restic (https://restic.net), then re-run this command." fi command -v restic >/dev/null 2>&1 || die "restic install did not succeed" step "Choosing what to back up" if [ -z "$BACKUP_PATHS" ]; then AUTO_PATHS=1 for d in /etc /home /root /var/www /srv; do [ -d "$d" ] && BACKUP_PATHS="$BACKUP_PATHS $d" done BACKUP_PATHS="$(printf '%s' "$BACKUP_PATHS" | sed 's/^ //')" fi [ -n "$BACKUP_PATHS" ] || die "nothing to back up; pass --paths \"/your/data\"" say "paths: $BACKUP_PATHS" step "Writing configuration" GENERATED=0 if [ -f "$ENV_FILE" ]; then say "existing config found at $ENV_FILE, reusing its password" # shellcheck disable=SC1090 . "$ENV_FILE" PASSWORD="${RESTIC_PASSWORD:-}" [ -n "$CLEANUP" ] || CLEANUP="${SERVERCRATE_CLEANUP_REPO:-}" case "$KSET" in *d*) : ;; *) KEEP_DAILY="${SERVERCRATE_KEEP_DAILY:-$KEEP_DAILY}" ;; esac case "$KSET" in *w*) : ;; *) KEEP_WEEKLY="${SERVERCRATE_KEEP_WEEKLY:-$KEEP_WEEKLY}" ;; esac case "$KSET" in *m*) : ;; *) KEEP_MONTHLY="${SERVERCRATE_KEEP_MONTHLY:-$KEEP_MONTHLY}" ;; esac case "$KSET" in *y*) : ;; *) KEEP_YEARLY="${SERVERCRATE_KEEP_YEARLY:-$KEEP_YEARLY}" ;; esac [ -n "$PASSWORD" ] || die "$ENV_FILE exists but has no RESTIC_PASSWORD; move it aside and re-run" elif [ -z "$PASSWORD" ]; then PASSWORD="$(head -c 32 /dev/urandom | base64 | tr -d '\n=' | cut -c1-32)" GENERATED=1 fi mkdir -p "$CONF_DIR" umask 077 cat > "$ENV_FILE" </dev/null 2>&1; then say "repository already initialized" else restic init say "repository initialized" fi step "Checking backup size" # shellcheck disable=SC2086 EST_KB="$(du -sck $BACKUP_PATHS 2>/dev/null | tail -1 | cut -f1)" if [ -n "$EST_KB" ]; then EST_GB=$((EST_KB / 1024 / 1024)) say "estimated size: ~${EST_GB} GB" if [ "$EST_GB" -gt 9 ]; then # sc-install-fit-v1: offer to back up only what fits instead of a dead end say "about ${EST_GB} GB selected. A Free vault holds 10 GB." for d in $BACKUP_PATHS; do say " $(du -sh "$d" 2>/dev/null | cut -f1) $d"; done FIT=""; SKIP=""; FIT_KB=0 if [ "${AUTO_PATHS:-0}" = "1" ]; then for d in /etc /root /srv /var/www /home; do case " $BACKUP_PATHS " in *" $d "*) ;; *) continue ;; esac k="$(du -sk "$d" 2>/dev/null | cut -f1)"; k="${k:-0}" if [ $((FIT_KB + k)) -le 9437184 ]; then FIT="$FIT $d"; FIT_KB=$((FIT_KB + k)); else SKIP="$SKIP $d"; fi done FIT="$(printf '%s' "$FIT" | sed 's/^ //')"; SKIP="$(printf '%s' "$SKIP" | sed 's/^ //')" fi if [ "$NONINTERACTIVE" != "1" ]; then if [ -n "$FIT" ] && [ -n "$SKIP" ]; then say " f) back up what fits in 10 GB now: $FIT (default)" DEF=f else DEF=n; fi say " y) back up everything (needs a paid plan: https://portal.servercrate.net/billing)" say " n) cancel" printf 'choose [%s]: ' "$DEF" if (exec /dev/null; then read -r ans /dev/null 2>&1 && [ -d /etc/systemd/system ]; then cat > /etc/systemd/system/servercrate-backup.service < /etc/systemd/system/servercrate-backup.timer <<'EOF' [Unit] Description=Daily ServerCrate backup [Timer] OnCalendar=daily RandomizedDelaySec=2h Persistent=true [Install] WantedBy=timers.target EOF systemctl daemon-reload systemctl enable --now servercrate-backup.timer >/dev/null 2>&1 || true say "systemd timer enabled (daily, randomized up to 2h)" if [ -n "$CLEANUP" ]; then cat > /etc/systemd/system/servercrate-cleanup.service < /etc/systemd/system/servercrate-cleanup.timer <<'EOF' [Unit] Description=Weekly ServerCrate cleanup of old snapshots [Timer] OnCalendar=Sun *-*-* 12:00:00 RandomizedDelaySec=2h Persistent=true [Install] WantedBy=timers.target EOF systemctl daemon-reload systemctl enable --now servercrate-cleanup.timer >/dev/null 2>&1 || true say "weekly cleanup enabled (keeps $KEEP_DAILY daily, $KEEP_WEEKLY weekly, $KEEP_MONTHLY monthly, $KEEP_YEARLY yearly)" else say "every snapshot is kept; add a cleanup key from your vault page to trim old ones weekly" fi else say "systemd not found. Add this to cron yourself:" say " 0 3 * * * . $ENV_FILE && restic backup \$SERVERCRATE_PATHS --tag servercrate" fi step "Verifying" restic snapshots --tag servercrate --latest 1 cat <