(feature) Make the native installer interactive

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.
This commit is contained in:
2026-05-19 18:22:18 +02:00
parent 44d821c638
commit 38f946d1c4
3 changed files with 226 additions and 0 deletions

View File

@@ -34,6 +34,9 @@ From a checked-out copy of this repository, run:
sudo scripts/install-systemd sudo scripts/install-systemd
``` ```
When run from a terminal, the installer asks for the important paths and settings with sensible defaults already filled
in. It can also create the first Django superuser and prints the next steps when installation is complete.
The installer will, by default: The installer will, by default:
- install required Debian/Ubuntu OS packages with `apt-get` - install required Debian/Ubuntu OS packages with `apt-get`
@@ -44,6 +47,7 @@ The installer will, by default:
- install Python dependencies - install Python dependencies
- run migrations and collect static files - run migrations and collect static files
- install and start `pobsync-web`, `pobsync-worker`, and `pobsync-scheduler` - install and start `pobsync-web`, `pobsync-worker`, and `pobsync-scheduler`
- guide you through the first login and setup steps
Common overrides: Common overrides:
@@ -56,6 +60,7 @@ sudo scripts/install-systemd \
Use `--no-install-os-packages` if you want to manage system packages yourself. Use `--force-env` only when you want the Use `--no-install-os-packages` if you want to manage system packages yourself. Use `--force-env` only when you want the
installer to rewrite an existing `/etc/pobsync/pobsync.env`. installer to rewrite an existing `/etc/pobsync/pobsync.env`.
Use `--non-interactive` for scripted installs.
For MariaDB support, add: For MariaDB support, add:

View File

@@ -62,6 +62,22 @@ pobsync discover-snapshots --host <host>
pobsync retention <host> pobsync retention <host>
``` ```
## Installer Development
The native installer is interactive by default when stdin is a terminal. It should keep every prompt backed by a command
line flag or environment variable so production installs remain scriptable.
Useful modes:
```
sudo scripts/install-systemd
sudo scripts/install-systemd --non-interactive
sudo scripts/install-systemd --create-superuser --superuser-username admin
```
The installer should print a short completion summary with the control panel URL, Self Check reminder, and service log
commands. Keep that output user-facing rather than developer-facing.
## Migration Helpers ## Migration Helpers
Import existing legacy YAML configs: Import existing legacy YAML configs:

View File

@@ -16,6 +16,15 @@ WEB_BIND=${POBSYNC_WEB_BIND:-127.0.0.1:8010}
FORCE_ENV=0 FORCE_ENV=0
INSTALL_OS_PACKAGES=1 INSTALL_OS_PACKAGES=1
WITH_NGINX=0 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 while [ "$#" -gt 0 ]; do
case "$1" in case "$1" in
@@ -63,6 +72,14 @@ while [ "$#" -gt 0 ]; do
FORCE_ENV=1 FORCE_ENV=1
shift shift
;; ;;
--interactive)
INTERACTIVE=1
shift
;;
--non-interactive)
INTERACTIVE=0
shift
;;
--no-install-os-packages) --no-install-os-packages)
INSTALL_OS_PACKAGES=0 INSTALL_OS_PACKAGES=0
shift shift
@@ -79,6 +96,26 @@ while [ "$#" -gt 0 ]; do
SERVER_NAME=$2 SERVER_NAME=$2
shift 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 echo "Unknown argument: $1" >&2
exit 2 exit 2
@@ -91,6 +128,128 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1 exit 1
fi 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() { install_os_packages() {
if [ "$INSTALL_OS_PACKAGES" -ne 1 ]; then if [ "$INSTALL_OS_PACKAGES" -ne 1 ]; then
return return
@@ -205,6 +364,11 @@ else
echo "Keeping existing $ENV_FILE. Use --force-env to rewrite it." echo "Keeping existing $ENV_FILE. Use --force-env to rewrite it."
fi fi
set -a
# shellcheck disable=SC1090
. "$ENV_FILE"
set +a
install_unit() { install_unit() {
src=$1 src=$1
dest=$2 dest=$2
@@ -225,6 +389,28 @@ install_unit "$APP_DIR/deploy/systemd/pobsync-scheduler.service" /etc/systemd/sy
systemctl daemon-reload systemctl daemon-reload
"$VENV_DIR/bin/python" "$APP_DIR/manage.py" migrate --noinput "$VENV_DIR/bin/python" "$APP_DIR/manage.py" migrate --noinput
"$VENV_DIR/bin/python" "$APP_DIR/manage.py" collectstatic --noinput --clear "$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 systemctl enable --now pobsync-web.service pobsync-worker.service pobsync-scheduler.service
if [ "$WITH_NGINX" -eq 1 ]; then if [ "$WITH_NGINX" -eq 1 ]; then
@@ -239,3 +425,22 @@ if [ "$WITH_NGINX" -eq 1 ]; then
fi fi
systemctl --no-pager --full status pobsync-web.service pobsync-worker.service pobsync-scheduler.service || true 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"