(refactor) Quiet native installer output by default

Add step-based installer logging that reports pobsync actions as OK,
FAILED, or SKIPPED while suppressing noisy apt, pip, Django, and
systemd output during successful runs.

Show the captured output for the failed step when something breaks,
and add a --verbose flag to restore full command output for debugging.

Document the quieter installer behavior and verbose mode in the README
and development notes.
This commit is contained in:
2026-05-19 18:52:31 +02:00
parent 98d152da06
commit 96b91b2a69
3 changed files with 93 additions and 31 deletions

View File

@@ -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 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. 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: For MariaDB support, add:

View File

@@ -72,11 +72,13 @@ Useful modes:
``` ```
sudo scripts/install-systemd sudo scripts/install-systemd
sudo scripts/install-systemd --non-interactive sudo scripts/install-systemd --non-interactive
sudo scripts/install-systemd --verbose
sudo scripts/install-systemd --create-superuser --superuser-username admin 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 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 ## Migration Helpers

View File

@@ -16,6 +16,7 @@ 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
VERBOSE=0
INTERACTIVE=0 INTERACTIVE=0
CREATE_SUPERUSER=ask CREATE_SUPERUSER=ask
SUPERUSER_USERNAME=${POBSYNC_SUPERUSER_USERNAME:-} SUPERUSER_USERNAME=${POBSYNC_SUPERUSER_USERNAME:-}
@@ -72,6 +73,10 @@ while [ "$#" -gt 0 ]; do
FORCE_ENV=1 FORCE_ENV=1
shift shift
;; ;;
--verbose)
VERBOSE=1
shift
;;
--interactive) --interactive)
INTERACTIVE=1 INTERACTIVE=1
shift shift
@@ -128,6 +133,39 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1 exit 1
fi 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_value() {
prompt=$1 prompt=$1
default=$2 default=$2
@@ -252,6 +290,7 @@ fi
install_os_packages() { install_os_packages() {
if [ "$INSTALL_OS_PACKAGES" -ne 1 ]; then if [ "$INSTALL_OS_PACKAGES" -ne 1 ]; then
note_step "Install OS packages" "SKIPPED"
return return
fi fi
@@ -263,8 +302,7 @@ install_os_packages() {
if [ "$INSTALL_EXTRAS" = "mariadb" ] || [ "$INSTALL_EXTRAS" = "[mariadb]" ] || [ "$INSTALL_EXTRAS" = ".[mariadb]" ]; then if [ "$INSTALL_EXTRAS" = "mariadb" ] || [ "$INSTALL_EXTRAS" = "[mariadb]" ] || [ "$INSTALL_EXTRAS" = ".[mariadb]" ]; then
packages="$packages default-libmysqlclient-dev build-essential pkg-config" packages="$packages default-libmysqlclient-dev build-essential pkg-config"
fi fi
apt-get update run_step "Install OS packages" sh -c "apt-get update && apt-get install -y --no-install-recommends $packages"
apt-get install -y --no-install-recommends $packages
return return
fi fi
@@ -294,19 +332,23 @@ if [ ! -f "$SOURCE_DIR/manage.py" ]; then
fi fi
if ! getent group "$SERVICE_GROUP" >/dev/null 2>&1; then 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 fi
if ! id "$SERVICE_USER" >/dev/null 2>&1; then 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 fi
mkdir -p /etc/pobsync /var/lib/pobsync /var/log/pobsync "$(dirname "$VENV_DIR")" "$APP_DIR" "$BACKUP_ROOT" run_step "Prepare directories" 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 run_step "Set state directory permissions" chown "$SERVICE_USER:$SERVICE_GROUP" /var/lib/pobsync /var/log/pobsync
chmod 0750 /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 if [ "$SOURCE_DIR" != "$APP_DIR" ]; then
rsync -a --delete \ run_step "Sync application files" rsync -a --delete \
--exclude .git \ --exclude .git \
--exclude .venv \ --exclude .venv \
--exclude __pycache__ \ --exclude __pycache__ \
@@ -314,10 +356,12 @@ if [ "$SOURCE_DIR" != "$APP_DIR" ]; then
--exclude .mypy_cache \ --exclude .mypy_cache \
--exclude var \ --exclude var \
"$SOURCE_DIR"/ "$APP_DIR"/ "$SOURCE_DIR"/ "$APP_DIR"/
else
note_step "Sync application files" "SKIPPED"
fi fi
python3 -m venv "$VENV_DIR" run_step "Create Python virtualenv" python3 -m venv "$VENV_DIR"
"$VENV_DIR/bin/python" -m pip install --upgrade pip run_step "Upgrade pip" "$VENV_DIR/bin/python" -m pip install --upgrade pip
case "$INSTALL_EXTRAS" in case "$INSTALL_EXTRAS" in
"") "")
pip_target=$APP_DIR pip_target=$APP_DIR
@@ -336,7 +380,7 @@ case "$INSTALL_EXTRAS" in
exit 2 exit 2
;; ;;
esac 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 if [ ! -f "$ENV_FILE" ] || [ "$FORCE_ENV" -eq 1 ]; then
secret=$("$VENV_DIR/bin/python" -c "import secrets; print(secrets.token_urlsafe(48))") secret=$("$VENV_DIR/bin/python" -c "import secrets; print(secrets.token_urlsafe(48))")
@@ -359,8 +403,9 @@ POBSYNC_SCHEDULER_INTERVAL=60
EOF EOF
chmod 0640 "$ENV_FILE" chmod 0640 "$ENV_FILE"
chown "root:$SERVICE_GROUP" "$ENV_FILE" chown "root:$SERVICE_GROUP" "$ENV_FILE"
echo "Wrote $ENV_FILE." note_step "Write environment file" "OK"
else else
note_step "Write environment file" "SKIPPED"
echo "Keeping existing $ENV_FILE. Use --force-env to rewrite it." echo "Keeping existing $ENV_FILE. Use --force-env to rewrite it."
fi fi
@@ -382,49 +427,63 @@ install_unit() {
chmod 0644 "$dest" chmod 0644 "$dest"
} }
install_unit "$APP_DIR/deploy/systemd/pobsync-web.service" /etc/systemd/system/pobsync-web.service install_units() {
install_unit "$APP_DIR/deploy/systemd/pobsync-worker.service" /etc/systemd/system/pobsync-worker.service install_unit "$APP_DIR/deploy/systemd/pobsync-web.service" /etc/systemd/system/pobsync-web.service
install_unit "$APP_DIR/deploy/systemd/pobsync-scheduler.service" /etc/systemd/system/pobsync-scheduler.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 run_step "Install systemd units" install_units
"$VENV_DIR/bin/python" "$APP_DIR/manage.py" migrate --noinput
"$VENV_DIR/bin/python" "$APP_DIR/manage.py" collectstatic --noinput --clear run_step "Reload systemd" systemctl daemon-reload
chown -R "$SERVICE_USER:$SERVICE_GROUP" /var/lib/pobsync /var/log/pobsync 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')") 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 [ "$CREATE_SUPERUSER" -eq 1 ]; then
if [ "$superuser_exists" = "yes" ]; 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 elif [ -n "$SUPERUSER_USERNAME" ] && [ -n "$SUPERUSER_PASSWORD" ]; then
DJANGO_SUPERUSER_USERNAME=$SUPERUSER_USERNAME \ run_step "Create Django superuser" env \
DJANGO_SUPERUSER_EMAIL=$SUPERUSER_EMAIL \ DJANGO_SUPERUSER_USERNAME="$SUPERUSER_USERNAME" \
DJANGO_SUPERUSER_PASSWORD=$SUPERUSER_PASSWORD \ DJANGO_SUPERUSER_EMAIL="$SUPERUSER_EMAIL" \
DJANGO_SUPERUSER_PASSWORD="$SUPERUSER_PASSWORD" \
"$VENV_DIR/bin/python" "$APP_DIR/manage.py" createsuperuser --noinput "$VENV_DIR/bin/python" "$APP_DIR/manage.py" createsuperuser --noinput
echo "Created Django superuser '$SUPERUSER_USERNAME'." run_step "Finalize superuser permissions" chown -R "$SERVICE_USER:$SERVICE_GROUP" /var/lib/pobsync /var/log/pobsync
chown -R "$SERVICE_USER:$SERVICE_GROUP" /var/lib/pobsync /var/log/pobsync
else else
note_step "Create Django superuser" "SKIPPED"
echo "No superuser password was provided; create one later with:" echo "No superuser password was provided; create one later with:"
echo " sudo -u $SERVICE_USER $VENV_DIR/bin/python $APP_DIR/manage.py createsuperuser" echo " sudo -u $SERVICE_USER $VENV_DIR/bin/python $APP_DIR/manage.py createsuperuser"
fi fi
elif [ "$superuser_exists" != "yes" ]; then elif [ "$superuser_exists" != "yes" ]; then
note_step "Create Django superuser" "SKIPPED"
echo "No Django superuser exists yet. Create one with:" echo "No Django superuser exists yet. Create one with:"
echo " sudo -u $SERVICE_USER $VENV_DIR/bin/python $APP_DIR/manage.py createsuperuser" echo " sudo -u $SERVICE_USER $VENV_DIR/bin/python $APP_DIR/manage.py createsuperuser"
else
note_step "Create Django superuser" "SKIPPED"
fi 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 [ "$WITH_NGINX" -eq 1 ]; then
if ! command -v nginx >/dev/null 2>&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 echo "nginx is not installed; skipping nginx config." >&2
else else
sed "s|@POBSYNC_SERVER_NAME@|$SERVER_NAME|g" "$APP_DIR/deploy/nginx/pobsync.conf" > /etc/nginx/sites-available/pobsync.conf 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 ln -sf /etc/nginx/sites-available/pobsync.conf /etc/nginx/sites-enabled/pobsync.conf
nginx -t note_step "Install nginx config" "OK"
systemctl reload nginx run_step "Validate nginx config" nginx -t
run_step "Reload nginx" systemctl reload nginx
fi fi
else
note_step "Install nginx config" "SKIPPED"
fi 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
echo "pobsync installation complete." echo "pobsync installation complete."