Add an interactive setup flow to the systemd installer with defaults for install paths, service identity, backup storage, bind address, allowed hosts, CSRF origins, OS package installation, MariaDB support, nginx setup, and first superuser creation. Keep scripted installs supported through non-interactive mode, existing overrides, environment variables, and explicit superuser flags. Print a user-facing completion summary with the control panel URL, Self Check reminder, first setup steps, and useful service log commands.
447 lines
13 KiB
Bash
Executable File
447 lines
13 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
SOURCE_DIR=${POBSYNC_SOURCE_DIR:-$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)}
|
|
APP_DIR=${POBSYNC_APP_DIR:-/opt/pobsync/app}
|
|
VENV_DIR=${POBSYNC_VENV_DIR:-/opt/pobsync/venv}
|
|
ENV_FILE=${POBSYNC_ENV_FILE:-/etc/pobsync/pobsync.env}
|
|
SERVICE_USER=${POBSYNC_SERVICE_USER:-pobsync}
|
|
SERVICE_GROUP=${POBSYNC_SERVICE_GROUP:-pobsync}
|
|
INSTALL_EXTRAS=${POBSYNC_INSTALL_EXTRAS:-}
|
|
SERVER_NAME=${POBSYNC_SERVER_NAME:-_}
|
|
ALLOWED_HOSTS=${POBSYNC_ALLOWED_HOSTS:-localhost,127.0.0.1}
|
|
CSRF_TRUSTED_ORIGINS=${POBSYNC_CSRF_TRUSTED_ORIGINS:-}
|
|
BACKUP_ROOT=${POBSYNC_BACKUP_ROOT:-/backups}
|
|
WEB_BIND=${POBSYNC_WEB_BIND:-127.0.0.1:8010}
|
|
FORCE_ENV=0
|
|
INSTALL_OS_PACKAGES=1
|
|
WITH_NGINX=0
|
|
INTERACTIVE=0
|
|
CREATE_SUPERUSER=ask
|
|
SUPERUSER_USERNAME=${POBSYNC_SUPERUSER_USERNAME:-}
|
|
SUPERUSER_EMAIL=${POBSYNC_SUPERUSER_EMAIL:-}
|
|
SUPERUSER_PASSWORD=${POBSYNC_SUPERUSER_PASSWORD:-}
|
|
|
|
if [ -t 0 ]; then
|
|
INTERACTIVE=1
|
|
fi
|
|
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--source-dir)
|
|
SOURCE_DIR=$2
|
|
shift 2
|
|
;;
|
|
--app-dir)
|
|
APP_DIR=$2
|
|
shift 2
|
|
;;
|
|
--venv-dir)
|
|
VENV_DIR=$2
|
|
shift 2
|
|
;;
|
|
--env-file)
|
|
ENV_FILE=$2
|
|
shift 2
|
|
;;
|
|
--service-user)
|
|
SERVICE_USER=$2
|
|
shift 2
|
|
;;
|
|
--service-group)
|
|
SERVICE_GROUP=$2
|
|
shift 2
|
|
;;
|
|
--backup-root)
|
|
BACKUP_ROOT=$2
|
|
shift 2
|
|
;;
|
|
--allowed-hosts)
|
|
ALLOWED_HOSTS=$2
|
|
shift 2
|
|
;;
|
|
--csrf-trusted-origins)
|
|
CSRF_TRUSTED_ORIGINS=$2
|
|
shift 2
|
|
;;
|
|
--web-bind)
|
|
WEB_BIND=$2
|
|
shift 2
|
|
;;
|
|
--force-env)
|
|
FORCE_ENV=1
|
|
shift
|
|
;;
|
|
--interactive)
|
|
INTERACTIVE=1
|
|
shift
|
|
;;
|
|
--non-interactive)
|
|
INTERACTIVE=0
|
|
shift
|
|
;;
|
|
--no-install-os-packages)
|
|
INSTALL_OS_PACKAGES=0
|
|
shift
|
|
;;
|
|
--install-extras)
|
|
INSTALL_EXTRAS=$2
|
|
shift 2
|
|
;;
|
|
--with-nginx)
|
|
WITH_NGINX=1
|
|
shift
|
|
;;
|
|
--server-name)
|
|
SERVER_NAME=$2
|
|
shift 2
|
|
;;
|
|
--create-superuser)
|
|
CREATE_SUPERUSER=1
|
|
shift
|
|
;;
|
|
--no-create-superuser)
|
|
CREATE_SUPERUSER=0
|
|
shift
|
|
;;
|
|
--superuser-username)
|
|
SUPERUSER_USERNAME=$2
|
|
shift 2
|
|
;;
|
|
--superuser-email)
|
|
SUPERUSER_EMAIL=$2
|
|
shift 2
|
|
;;
|
|
--superuser-password)
|
|
SUPERUSER_PASSWORD=$2
|
|
shift 2
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "Run this installer as root." >&2
|
|
exit 1
|
|
fi
|
|
|
|
prompt_value() {
|
|
prompt=$1
|
|
default=$2
|
|
if [ "$INTERACTIVE" -ne 1 ]; then
|
|
printf '%s\n' "$default"
|
|
return
|
|
fi
|
|
|
|
printf '%s [%s]: ' "$prompt" "$default" >&2
|
|
read -r answer
|
|
if [ -n "$answer" ]; then
|
|
printf '%s\n' "$answer"
|
|
else
|
|
printf '%s\n' "$default"
|
|
fi
|
|
}
|
|
|
|
prompt_yes_no() {
|
|
prompt=$1
|
|
default=$2
|
|
if [ "$INTERACTIVE" -ne 1 ]; then
|
|
printf '%s\n' "$default"
|
|
return
|
|
fi
|
|
|
|
if [ "$default" -eq 1 ]; then
|
|
suffix=Y/n
|
|
else
|
|
suffix=y/N
|
|
fi
|
|
|
|
while :; do
|
|
printf '%s [%s]: ' "$prompt" "$suffix" >&2
|
|
read -r answer
|
|
case "$answer" in
|
|
"")
|
|
printf '%s\n' "$default"
|
|
return
|
|
;;
|
|
y|Y|yes|YES|Yes)
|
|
printf '1\n'
|
|
return
|
|
;;
|
|
n|N|no|NO|No)
|
|
printf '0\n'
|
|
return
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
prompt_secret() {
|
|
prompt=$1
|
|
if [ "$INTERACTIVE" -ne 1 ]; then
|
|
printf '\n'
|
|
return
|
|
fi
|
|
|
|
printf '%s: ' "$prompt" >&2
|
|
stty -echo
|
|
read -r secret
|
|
stty echo
|
|
printf '\n' >&2
|
|
printf '%s\n' "$secret"
|
|
}
|
|
|
|
if [ "$INTERACTIVE" -eq 1 ]; then
|
|
echo "pobsync native installer"
|
|
echo
|
|
echo "Press Enter to accept defaults. Existing command-line flags are already applied as defaults."
|
|
echo
|
|
|
|
SOURCE_DIR=$(prompt_value "Source checkout" "$SOURCE_DIR")
|
|
APP_DIR=$(prompt_value "Install app directory" "$APP_DIR")
|
|
VENV_DIR=$(prompt_value "Python virtualenv directory" "$VENV_DIR")
|
|
ENV_FILE=$(prompt_value "Environment file" "$ENV_FILE")
|
|
SERVICE_USER=$(prompt_value "Service user" "$SERVICE_USER")
|
|
SERVICE_GROUP=$(prompt_value "Service group" "$SERVICE_GROUP")
|
|
BACKUP_ROOT=$(prompt_value "Backup storage path" "$BACKUP_ROOT")
|
|
WEB_BIND=$(prompt_value "Gunicorn bind address" "$WEB_BIND")
|
|
ALLOWED_HOSTS=$(prompt_value "Allowed hosts" "$ALLOWED_HOSTS")
|
|
CSRF_TRUSTED_ORIGINS=$(prompt_value "CSRF trusted origins, comma-separated or blank" "$CSRF_TRUSTED_ORIGINS")
|
|
INSTALL_OS_PACKAGES=$(prompt_yes_no "Install required OS packages with apt-get" "$INSTALL_OS_PACKAGES")
|
|
|
|
use_mariadb=0
|
|
if [ "$INSTALL_EXTRAS" = "mariadb" ] || [ "$INSTALL_EXTRAS" = "[mariadb]" ] || [ "$INSTALL_EXTRAS" = ".[mariadb]" ]; then
|
|
use_mariadb=1
|
|
fi
|
|
use_mariadb=$(prompt_yes_no "Install MariaDB Python/client support" "$use_mariadb")
|
|
if [ "$use_mariadb" -eq 1 ]; then
|
|
INSTALL_EXTRAS=mariadb
|
|
else
|
|
INSTALL_EXTRAS=
|
|
fi
|
|
|
|
WITH_NGINX=$(prompt_yes_no "Install starter nginx reverse proxy config" "$WITH_NGINX")
|
|
if [ "$WITH_NGINX" -eq 1 ]; then
|
|
SERVER_NAME=$(prompt_value "Nginx server_name" "$SERVER_NAME")
|
|
fi
|
|
|
|
if [ "$CREATE_SUPERUSER" = "ask" ]; then
|
|
CREATE_SUPERUSER=$(prompt_yes_no "Create first Django superuser after install" 1)
|
|
fi
|
|
if [ "$CREATE_SUPERUSER" -eq 1 ]; then
|
|
SUPERUSER_USERNAME=$(prompt_value "Superuser username" "${SUPERUSER_USERNAME:-admin}")
|
|
SUPERUSER_EMAIL=$(prompt_value "Superuser email, blank allowed" "$SUPERUSER_EMAIL")
|
|
if [ -z "$SUPERUSER_PASSWORD" ]; then
|
|
SUPERUSER_PASSWORD=$(prompt_secret "Superuser password, leave blank to run createsuperuser interactively later")
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
fi
|
|
|
|
if [ "$CREATE_SUPERUSER" = "ask" ]; then
|
|
if [ -n "$SUPERUSER_USERNAME" ] && [ -n "$SUPERUSER_PASSWORD" ]; then
|
|
CREATE_SUPERUSER=1
|
|
else
|
|
CREATE_SUPERUSER=0
|
|
fi
|
|
fi
|
|
|
|
install_os_packages() {
|
|
if [ "$INSTALL_OS_PACKAGES" -ne 1 ]; then
|
|
return
|
|
fi
|
|
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
packages="python3 python3-venv python3-pip rsync openssh-client"
|
|
if [ "$WITH_NGINX" -eq 1 ]; then
|
|
packages="$packages nginx"
|
|
fi
|
|
if [ "$INSTALL_EXTRAS" = "mariadb" ] || [ "$INSTALL_EXTRAS" = "[mariadb]" ] || [ "$INSTALL_EXTRAS" = ".[mariadb]" ]; then
|
|
packages="$packages default-libmysqlclient-dev build-essential pkg-config"
|
|
fi
|
|
apt-get update
|
|
apt-get install -y --no-install-recommends $packages
|
|
return
|
|
fi
|
|
|
|
echo "No supported package manager found; install python3, python3-venv, rsync, and openssh-client manually." >&2
|
|
}
|
|
|
|
install_os_packages
|
|
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
echo "python3 is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v rsync >/dev/null 2>&1; then
|
|
echo "rsync is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v ssh >/dev/null 2>&1; then
|
|
echo "openssh-client is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$SOURCE_DIR/manage.py" ]; then
|
|
echo "Source directory does not look like a pobsync checkout: $SOURCE_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! getent group "$SERVICE_GROUP" >/dev/null 2>&1; then
|
|
groupadd --system "$SERVICE_GROUP"
|
|
fi
|
|
|
|
if ! id "$SERVICE_USER" >/dev/null 2>&1; then
|
|
useradd --system --home /var/lib/pobsync --shell /usr/sbin/nologin --gid "$SERVICE_GROUP" "$SERVICE_USER"
|
|
fi
|
|
|
|
mkdir -p /etc/pobsync /var/lib/pobsync /var/log/pobsync "$(dirname "$VENV_DIR")" "$APP_DIR" "$BACKUP_ROOT"
|
|
chown "$SERVICE_USER:$SERVICE_GROUP" /var/lib/pobsync /var/log/pobsync
|
|
chmod 0750 /var/lib/pobsync /var/log/pobsync
|
|
|
|
if [ "$SOURCE_DIR" != "$APP_DIR" ]; then
|
|
rsync -a --delete \
|
|
--exclude .git \
|
|
--exclude .venv \
|
|
--exclude __pycache__ \
|
|
--exclude .pytest_cache \
|
|
--exclude .mypy_cache \
|
|
--exclude var \
|
|
"$SOURCE_DIR"/ "$APP_DIR"/
|
|
fi
|
|
|
|
python3 -m venv "$VENV_DIR"
|
|
"$VENV_DIR/bin/python" -m pip install --upgrade pip
|
|
case "$INSTALL_EXTRAS" in
|
|
"")
|
|
pip_target=$APP_DIR
|
|
;;
|
|
mariadb)
|
|
pip_target="$APP_DIR[mariadb]"
|
|
;;
|
|
\[*\])
|
|
pip_target="$APP_DIR$INSTALL_EXTRAS"
|
|
;;
|
|
.\[*\])
|
|
pip_target="$APP_DIR${INSTALL_EXTRAS#.}"
|
|
;;
|
|
*)
|
|
echo "Unsupported install extras: $INSTALL_EXTRAS" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
"$VENV_DIR/bin/python" -m pip install -e "$pip_target"
|
|
|
|
if [ ! -f "$ENV_FILE" ] || [ "$FORCE_ENV" -eq 1 ]; then
|
|
secret=$("$VENV_DIR/bin/python" -c "import secrets; print(secrets.token_urlsafe(48))")
|
|
cat > "$ENV_FILE" <<EOF
|
|
POBSYNC_DJANGO_DEBUG=0
|
|
POBSYNC_DJANGO_SECRET_KEY=$secret
|
|
POBSYNC_DJANGO_ALLOWED_HOSTS=$ALLOWED_HOSTS
|
|
POBSYNC_DJANGO_CSRF_TRUSTED_ORIGINS=$CSRF_TRUSTED_ORIGINS
|
|
|
|
POBSYNC_HOME=/var/lib/pobsync
|
|
POBSYNC_BACKUP_ROOT=$BACKUP_ROOT
|
|
POBSYNC_SQLITE_PATH=/var/lib/pobsync/pobsync.sqlite3
|
|
POBSYNC_STATIC_ROOT=/var/lib/pobsync/static
|
|
|
|
POBSYNC_WEB_BIND=$WEB_BIND
|
|
POBSYNC_GUNICORN_WORKERS=2
|
|
POBSYNC_GUNICORN_TIMEOUT=120
|
|
POBSYNC_WORKER_INTERVAL=15
|
|
POBSYNC_SCHEDULER_INTERVAL=60
|
|
EOF
|
|
chmod 0640 "$ENV_FILE"
|
|
chown "root:$SERVICE_GROUP" "$ENV_FILE"
|
|
echo "Wrote $ENV_FILE."
|
|
else
|
|
echo "Keeping existing $ENV_FILE. Use --force-env to rewrite it."
|
|
fi
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
. "$ENV_FILE"
|
|
set +a
|
|
|
|
install_unit() {
|
|
src=$1
|
|
dest=$2
|
|
sed \
|
|
-e "s|@POBSYNC_APP_DIR@|$APP_DIR|g" \
|
|
-e "s|@POBSYNC_VENV_DIR@|$VENV_DIR|g" \
|
|
-e "s|@POBSYNC_ENV_FILE@|$ENV_FILE|g" \
|
|
-e "s|@POBSYNC_USER@|$SERVICE_USER|g" \
|
|
-e "s|@POBSYNC_GROUP@|$SERVICE_GROUP|g" \
|
|
"$src" > "$dest"
|
|
chmod 0644 "$dest"
|
|
}
|
|
|
|
install_unit "$APP_DIR/deploy/systemd/pobsync-web.service" /etc/systemd/system/pobsync-web.service
|
|
install_unit "$APP_DIR/deploy/systemd/pobsync-worker.service" /etc/systemd/system/pobsync-worker.service
|
|
install_unit "$APP_DIR/deploy/systemd/pobsync-scheduler.service" /etc/systemd/system/pobsync-scheduler.service
|
|
|
|
systemctl daemon-reload
|
|
"$VENV_DIR/bin/python" "$APP_DIR/manage.py" migrate --noinput
|
|
"$VENV_DIR/bin/python" "$APP_DIR/manage.py" collectstatic --noinput --clear
|
|
chown -R "$SERVICE_USER:$SERVICE_GROUP" /var/lib/pobsync /var/log/pobsync
|
|
|
|
superuser_exists=$("$VENV_DIR/bin/python" -c "import os; os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pobsync_server.settings'); import django; django.setup(); from django.contrib.auth import get_user_model; print('yes' if get_user_model().objects.filter(is_superuser=True).exists() else 'no')")
|
|
if [ "$CREATE_SUPERUSER" -eq 1 ]; then
|
|
if [ "$superuser_exists" = "yes" ]; then
|
|
echo "A Django superuser already exists; skipping superuser creation."
|
|
elif [ -n "$SUPERUSER_USERNAME" ] && [ -n "$SUPERUSER_PASSWORD" ]; then
|
|
DJANGO_SUPERUSER_USERNAME=$SUPERUSER_USERNAME \
|
|
DJANGO_SUPERUSER_EMAIL=$SUPERUSER_EMAIL \
|
|
DJANGO_SUPERUSER_PASSWORD=$SUPERUSER_PASSWORD \
|
|
"$VENV_DIR/bin/python" "$APP_DIR/manage.py" createsuperuser --noinput
|
|
echo "Created Django superuser '$SUPERUSER_USERNAME'."
|
|
chown -R "$SERVICE_USER:$SERVICE_GROUP" /var/lib/pobsync /var/log/pobsync
|
|
else
|
|
echo "No superuser password was provided; create one later with:"
|
|
echo " sudo -u $SERVICE_USER $VENV_DIR/bin/python $APP_DIR/manage.py createsuperuser"
|
|
fi
|
|
elif [ "$superuser_exists" != "yes" ]; then
|
|
echo "No Django superuser exists yet. Create one with:"
|
|
echo " sudo -u $SERVICE_USER $VENV_DIR/bin/python $APP_DIR/manage.py createsuperuser"
|
|
fi
|
|
|
|
systemctl enable --now pobsync-web.service pobsync-worker.service pobsync-scheduler.service
|
|
|
|
if [ "$WITH_NGINX" -eq 1 ]; then
|
|
if ! command -v nginx >/dev/null 2>&1; then
|
|
echo "nginx is not installed; skipping nginx config." >&2
|
|
else
|
|
sed "s|@POBSYNC_SERVER_NAME@|$SERVER_NAME|g" "$APP_DIR/deploy/nginx/pobsync.conf" > /etc/nginx/sites-available/pobsync.conf
|
|
ln -sf /etc/nginx/sites-available/pobsync.conf /etc/nginx/sites-enabled/pobsync.conf
|
|
nginx -t
|
|
systemctl reload nginx
|
|
fi
|
|
fi
|
|
|
|
systemctl --no-pager --full status pobsync-web.service pobsync-worker.service pobsync-scheduler.service || true
|
|
|
|
echo
|
|
echo "pobsync installation complete."
|
|
echo
|
|
echo "Open the Django control panel at:"
|
|
echo " http://$WEB_BIND/"
|
|
echo
|
|
echo "If pobsync is behind a reverse proxy, use your public hostname instead."
|
|
echo
|
|
echo "Recommended first steps:"
|
|
echo " 1. Log in to the Django control panel."
|
|
echo " 2. Open Self Check and resolve any warnings."
|
|
echo " 3. Configure global settings and backup storage."
|
|
echo " 4. Add an SSH key under SSH Keys."
|
|
echo " 5. Add a host and queue a dry-run backup."
|
|
echo
|
|
echo "Useful commands:"
|
|
echo " systemctl status pobsync-web pobsync-worker pobsync-scheduler"
|
|
echo " journalctl -u pobsync-worker -f"
|