diff --git a/README.md b/README.md index 3c5bb58..340f058 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,8 @@ 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. +Use `--non-interactive` for scripted installs. Use `--verbose` when you want to see the underlying apt, pip, Django, and +systemd output. For MariaDB support, add: diff --git a/docs/development.md b/docs/development.md index 0916782..99e2069 100644 --- a/docs/development.md +++ b/docs/development.md @@ -72,11 +72,13 @@ Useful modes: ``` sudo scripts/install-systemd sudo scripts/install-systemd --non-interactive +sudo scripts/install-systemd --verbose 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. +commands. Keep normal output user-facing: pobsync step names with OK, FAILED, or SKIPPED. Full apt, pip, Django, and +systemd output belongs behind `--verbose` or in the failed step output. ## Migration Helpers diff --git a/scripts/install-systemd b/scripts/install-systemd index b932b91..ef844d2 100755 --- a/scripts/install-systemd +++ b/scripts/install-systemd @@ -16,6 +16,7 @@ WEB_BIND=${POBSYNC_WEB_BIND:-127.0.0.1:8010} FORCE_ENV=0 INSTALL_OS_PACKAGES=1 WITH_NGINX=0 +VERBOSE=0 INTERACTIVE=0 CREATE_SUPERUSER=ask SUPERUSER_USERNAME=${POBSYNC_SUPERUSER_USERNAME:-} @@ -72,6 +73,10 @@ while [ "$#" -gt 0 ]; do FORCE_ENV=1 shift ;; + --verbose) + VERBOSE=1 + shift + ;; --interactive) INTERACTIVE=1 shift @@ -128,6 +133,39 @@ if [ "$(id -u)" -ne 0 ]; then exit 1 fi +run_step() { + label=$1 + shift + + if [ "$VERBOSE" -eq 1 ]; then + echo "==> $label" + "$@" + echo "OK: $label" + return + fi + + printf '%-48s' "$label" + log_file=$(mktemp) + if "$@" >"$log_file" 2>&1; then + rm -f "$log_file" + echo "OK" + return + fi + + echo "FAILED" + echo + echo "Output from failed step '$label':" >&2 + cat "$log_file" >&2 + rm -f "$log_file" + exit 1 +} + +note_step() { + label=$1 + status=$2 + printf '%-48s%s\n' "$label" "$status" +} + prompt_value() { prompt=$1 default=$2 @@ -252,6 +290,7 @@ fi install_os_packages() { if [ "$INSTALL_OS_PACKAGES" -ne 1 ]; then + note_step "Install OS packages" "SKIPPED" return fi @@ -263,8 +302,7 @@ install_os_packages() { 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 + run_step "Install OS packages" sh -c "apt-get update && apt-get install -y --no-install-recommends $packages" return fi @@ -294,19 +332,23 @@ if [ ! -f "$SOURCE_DIR/manage.py" ]; then fi if ! getent group "$SERVICE_GROUP" >/dev/null 2>&1; then - groupadd --system "$SERVICE_GROUP" + run_step "Create service group" groupadd --system "$SERVICE_GROUP" +else + note_step "Create service group" "OK" 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" + run_step "Create service user" useradd --system --home /var/lib/pobsync --shell /usr/sbin/nologin --gid "$SERVICE_GROUP" "$SERVICE_USER" +else + note_step "Create service user" "OK" 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 +run_step "Prepare directories" mkdir -p /etc/pobsync /var/lib/pobsync /var/log/pobsync "$(dirname "$VENV_DIR")" "$APP_DIR" "$BACKUP_ROOT" +run_step "Set state directory permissions" chown "$SERVICE_USER:$SERVICE_GROUP" /var/lib/pobsync /var/log/pobsync +run_step "Set private directory modes" chmod 0750 /var/lib/pobsync /var/log/pobsync if [ "$SOURCE_DIR" != "$APP_DIR" ]; then - rsync -a --delete \ + run_step "Sync application files" rsync -a --delete \ --exclude .git \ --exclude .venv \ --exclude __pycache__ \ @@ -314,10 +356,12 @@ if [ "$SOURCE_DIR" != "$APP_DIR" ]; then --exclude .mypy_cache \ --exclude var \ "$SOURCE_DIR"/ "$APP_DIR"/ +else + note_step "Sync application files" "SKIPPED" fi -python3 -m venv "$VENV_DIR" -"$VENV_DIR/bin/python" -m pip install --upgrade pip +run_step "Create Python virtualenv" python3 -m venv "$VENV_DIR" +run_step "Upgrade pip" "$VENV_DIR/bin/python" -m pip install --upgrade pip case "$INSTALL_EXTRAS" in "") pip_target=$APP_DIR @@ -336,7 +380,7 @@ case "$INSTALL_EXTRAS" in exit 2 ;; esac -"$VENV_DIR/bin/python" -m pip install -e "$pip_target" +run_step "Install Python package" "$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))") @@ -359,8 +403,9 @@ POBSYNC_SCHEDULER_INTERVAL=60 EOF chmod 0640 "$ENV_FILE" chown "root:$SERVICE_GROUP" "$ENV_FILE" - echo "Wrote $ENV_FILE." + note_step "Write environment file" "OK" else + note_step "Write environment file" "SKIPPED" echo "Keeping existing $ENV_FILE. Use --force-env to rewrite it." fi @@ -382,49 +427,63 @@ install_unit() { 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 +install_units() { + 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 +run_step "Install systemd units" install_units + +run_step "Reload systemd" systemctl daemon-reload +run_step "Run database migrations" "$VENV_DIR/bin/python" "$APP_DIR/manage.py" migrate --noinput +run_step "Collect static files" "$VENV_DIR/bin/python" "$APP_DIR/manage.py" collectstatic --noinput --clear +run_step "Finalize state permissions" 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." + note_step "Create Django superuser" "SKIPPED" elif [ -n "$SUPERUSER_USERNAME" ] && [ -n "$SUPERUSER_PASSWORD" ]; then - DJANGO_SUPERUSER_USERNAME=$SUPERUSER_USERNAME \ - DJANGO_SUPERUSER_EMAIL=$SUPERUSER_EMAIL \ - DJANGO_SUPERUSER_PASSWORD=$SUPERUSER_PASSWORD \ + run_step "Create Django superuser" env \ + 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 + run_step "Finalize superuser permissions" chown -R "$SERVICE_USER:$SERVICE_GROUP" /var/lib/pobsync /var/log/pobsync else + note_step "Create Django superuser" "SKIPPED" 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 + note_step "Create Django superuser" "SKIPPED" echo "No Django superuser exists yet. Create one with:" echo " sudo -u $SERVICE_USER $VENV_DIR/bin/python $APP_DIR/manage.py createsuperuser" +else + note_step "Create Django superuser" "SKIPPED" fi -systemctl enable --now pobsync-web.service pobsync-worker.service pobsync-scheduler.service +run_step "Enable and start services" 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 + note_step "Install nginx config" "SKIPPED" 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 + note_step "Install nginx config" "OK" + run_step "Validate nginx config" nginx -t + run_step "Reload nginx" systemctl reload nginx fi +else + note_step "Install nginx config" "SKIPPED" fi -systemctl --no-pager --full status pobsync-web.service pobsync-worker.service pobsync-scheduler.service || true +if [ "$VERBOSE" -eq 1 ]; then + systemctl --no-pager --full status pobsync-web.service pobsync-worker.service pobsync-scheduler.service || true +fi echo echo "pobsync installation complete."