(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:
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user