#!/bin/sh # # kopia-server - start/stop the Kopia web UI server for the current user. # # No root, no /etc/rc.d. The server runs as whoever invokes this script, # with its password kept in a 0600 env file rather than on the command # line (where "ps" would show it to every user on the box). # # Usage: # kopia-server setup create the env file with a random password # kopia-server start # kopia-server stop # kopia-server restart # kopia-server status # kopia-server log tail the server log # # Environment overrides: # KOPIA_ADDRESS listen address (default 127.0.0.1:51515) # KOPIA_BIN path to the kopia binary (default: from PATH) set -e PRGNAM=kopia-server KOPIA_BIN="${KOPIA_BIN:-kopia}" ADDRESS="${KOPIA_ADDRESS:-127.0.0.1:51515}" CONFDIR="${XDG_CONFIG_HOME:-$HOME/.config}/kopia" ENVFILE="$CONFDIR/server.env" RUNDIR="${XDG_RUNTIME_DIR:-$HOME/.cache}/kopia" PIDFILE="$RUNDIR/server.pid" LOGFILE="$RUNDIR/server.log" die() { echo "$PRGNAM: $*" 1>&2; exit 1; } ensure_dirs() { mkdir -p "$CONFDIR" "$RUNDIR" chmod 700 "$CONFDIR" "$RUNDIR" } # True only if the pidfile points at a live process that is actually kopia. # The comm check matters: PIDs get reused, and killing a stranger's process # because a stale pidfile happened to match would be a bad day. running() { [ -s "$PIDFILE" ] || return 1 pid=$(cat "$PIDFILE" 2>/dev/null) || return 1 case "$pid" in ''|*[!0-9]*) return 1 ;; esac [ -d "/proc/$pid" ] || return 1 read -r comm < "/proc/$pid/comm" 2>/dev/null || return 1 [ "$comm" = "kopia" ] } check_envfile() { [ -f "$ENVFILE" ] || die "no $ENVFILE - run '$PRGNAM setup' first" perms=$(stat -c %a "$ENVFILE") case "$perms" in 600|400) ;; *) die "$ENVFILE is mode $perms - must be 600 (chmod 600 '$ENVFILE')" ;; esac } cmd_setup() { ensure_dirs if [ -e "$ENVFILE" ]; then echo "$PRGNAM: $ENVFILE already exists, leaving it alone" return 0 fi pw=$(od -An -tx1 -N32 /dev/urandom | tr -d ' \n') ( umask 077 cat > "$ENVFILE" </dev/null 2>&1 || die "kopia binary not found" if running; then echo "$PRGNAM: already running (pid $(cat "$PIDFILE")) on http://$ADDRESS" return 0 fi # shellcheck disable=SC1090 . "$ENVFILE" export KOPIA_SERVER_USERNAME KOPIA_SERVER_PASSWORD # --insecure is fine only because we bind to loopback. If you ever change # ADDRESS to a routable interface, drop --insecure and add TLS instead: # --tls-generate-cert --tls-cert-file ... --tls-key-file ... case "$ADDRESS" in 127.*|localhost:*|\[::1\]:*) insecure="--insecure" ;; *) die "refusing --insecure on non-loopback address $ADDRESS; add TLS flags and edit this script" ;; esac nohup "$KOPIA_BIN" server start \ $insecure \ --address "$ADDRESS" \ --server-username "$KOPIA_SERVER_USERNAME" \ >> "$LOGFILE" 2>&1 & echo $! > "$PIDFILE" chmod 600 "$PIDFILE" "$LOGFILE" # Give it a moment to either bind or die, so the exit status means something. sleep 1 if running; then echo "$PRGNAM: started (pid $(cat "$PIDFILE")) on http://$ADDRESS" echo " username: $KOPIA_SERVER_USERNAME" echo " log: $LOGFILE" else : > "$PIDFILE" echo "$PRGNAM: failed to start - last lines of $LOGFILE:" 1>&2 tail -n 20 "$LOGFILE" 1>&2 exit 1 fi } cmd_stop() { if ! running; then echo "$PRGNAM: not running" [ -f "$PIDFILE" ] && : > "$PIDFILE" return 0 fi pid=$(cat "$PIDFILE") kill "$pid" 2>/dev/null || true n=0 while [ $n -lt 20 ]; do running || break sleep 0.5 n=$((n + 1)) done if running; then echo "$PRGNAM: pid $pid ignored SIGTERM, sending SIGKILL" 1>&2 kill -9 "$pid" 2>/dev/null || true sleep 1 fi : > "$PIDFILE" echo "$PRGNAM: stopped" } cmd_status() { if running; then pid=$(cat "$PIDFILE") echo "$PRGNAM: running (pid $pid) on http://$ADDRESS" echo " started: $(ps -o lstart= -p "$pid" 2>/dev/null)" echo " log: $LOGFILE" else echo "$PRGNAM: not running" return 3 fi } case "${1:-}" in setup) cmd_setup ;; start) cmd_start ;; stop) cmd_stop ;; restart) cmd_stop; cmd_start ;; status) cmd_status ;; log) exec tail -n 50 -f "$LOGFILE" ;; *) echo "usage: $PRGNAM {setup|start|stop|restart|status|log}" 1>&2; exit 1 ;; esac