diff --git a/README.md b/README.md index 274a284..2409d94 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ From a checked-out copy of this repository, run: 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: - install required Debian/Ubuntu OS packages with `apt-get` @@ -44,6 +47,7 @@ The installer will, by default: - install Python dependencies - run migrations and collect static files - install and start `pobsync-web`, `pobsync-worker`, and `pobsync-scheduler` +- guide you through the first login and setup steps 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 installer to rewrite an existing `/etc/pobsync/pobsync.env`. +Use `--non-interactive` for scripted installs. For MariaDB support, add: diff --git a/docs/development.md b/docs/development.md index 9162b99..0916782 100644 --- a/docs/development.md +++ b/docs/development.md @@ -62,6 +62,22 @@ pobsync discover-snapshots --host pobsync retention ``` +## 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 Import existing legacy YAML configs: diff --git a/scripts/install-systemd b/scripts/install-systemd index 21ca52d..b932b91 100755 --- a/scripts/install-systemd +++ b/scripts/install-systemd @@ -16,6 +16,15 @@ 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 @@ -63,6 +72,14 @@ while [ "$#" -gt 0 ]; do FORCE_ENV=1 shift ;; + --interactive) + INTERACTIVE=1 + shift + ;; + --non-interactive) + INTERACTIVE=0 + shift + ;; --no-install-os-packages) INSTALL_OS_PACKAGES=0 shift @@ -79,6 +96,26 @@ while [ "$#" -gt 0 ]; do 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 @@ -91,6 +128,128 @@ if [ "$(id -u)" -ne 0 ]; then 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 @@ -205,6 +364,11 @@ 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 @@ -225,6 +389,28 @@ install_unit "$APP_DIR/deploy/systemd/pobsync-scheduler.service" /etc/systemd/sy 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 @@ -239,3 +425,22 @@ if [ "$WITH_NGINX" -eq 1 ]; then 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"