From bb2136aad533a266ab25817c0d480f92a09a4197 Mon Sep 17 00:00:00 2001 From: Raghav <1858838+quantumrag@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:33:46 +0530 Subject: [PATCH] Enable web updater and automate Hub updates Co-Authored-By: Oz --- .gitignore | 1 + .hub-update.env.example | 4 ++ README.md | 61 +++++++++++++++++++++++++--- docker-compose.yml | 8 ++++ scripts/setup-hub-update-cron.sh | 48 ++++++++++++++++++++++ scripts/update-nextcloud-hub.sh | 70 +++++++++++++++++++++----------- 6 files changed, 163 insertions(+), 29 deletions(-) create mode 100644 .hub-update.env.example create mode 100755 scripts/setup-hub-update-cron.sh diff --git a/.gitignore b/.gitignore index f41e0ed..6ef420e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Local runtime configuration .env .tls-renewal.env +.hub-update.env # Runtime TLS artifacts (never commit private keys/certs) nginx/ssl/*.crt diff --git a/.hub-update.env.example b/.hub-update.env.example new file mode 100644 index 0000000..7d87ca5 --- /dev/null +++ b/.hub-update.env.example @@ -0,0 +1,4 @@ +RUN_APP_UPDATES=1 +STRICT_TLS=0 +ENABLE_WEB_UPDATER=1 +UPDATER_RELEASE_CHANNEL=stable diff --git a/README.md b/README.md index 08ba4ff..1fe95fc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Nextcloud on Ubuntu via Docker for nxt.bhatfamily.in This repository deploys Nextcloud behind Nginx using Docker Compose. + Exposed ports: - HTTP: `8082` - HTTPS: `8446` + Target hostname: - `nxt.bhatfamily.in` @@ -14,10 +16,12 @@ The stack now includes: - Production TLS provisioning using Let's Encrypt DNS-01 with Cloudflare - Automated TLS renewal job support (cron) - Nextcloud app startup fixes for Apache `ServerName` and writable Fontconfig cache +- Persistent web-updater enablement (`upgrade-disable-web.config.php` forced to `false` on app startup) - Reverse-proxy trust configuration in Nextcloud (`trusted_proxies`, `forwarded_for_headers`) - Nginx hardening (`server_tokens off`, stronger HSTS, hide `X-Powered-By`, TLS session hardening) - Brute-force protection explicitly enabled and maintenance window configured -- New scripted Nextcloud Hub upgrade workflow: `scripts/update-nextcloud-hub.sh` +- Scripted Nextcloud Hub upgrade workflow: `scripts/update-nextcloud-hub.sh` +- Automated Hub update scheduler: `scripts/setup-hub-update-cron.sh` ## Prerequisites - Ubuntu host with Docker + Docker Compose plugin (or `docker-compose`) @@ -40,6 +44,24 @@ cp .env.example .env ./scripts/test.sh ``` +## Enable web updater (one-time verification) +Web updater is now enabled by design for this deployment. + +Check values: +```bash +docker exec --user www-data nextcloud-app php occ config:list system | grep -E "upgrade.disable-web|updater.release.channel" +``` + +Expected: +- `upgrade.disable-web: false` +- `updater.release.channel: stable` + +If you need to enforce immediately without restart, run: +```bash +./scripts/update-nextcloud-hub.sh +``` +This script also rewrites `config/upgrade-disable-web.config.php` inside the app container volume. + ## Update Nextcloud Hub (scripted) Use the upgrade helper script to pull new images, apply the upgrade, run post-upgrade repairs, and validate endpoints. @@ -61,14 +83,37 @@ STRICT_TLS=1 ./scripts/update-nextcloud-hub.sh What the script does: - pulls latest `db`, `app`, and `web` images - recreates services via Compose -- enables maintenance mode and runs `occ upgrade` +- ensures web updater is enabled and release channel is configured +- ensures maintenance mode is off before `occ upgrade` (fixes prior deadlock) +- runs `occ upgrade` - runs `occ app:update --all` (unless disabled) - runs schema and repair commands (`db:add-missing-*`, `maintenance:repair`) -- disables maintenance mode - runs `occ status`, `occ setupchecks`, and `scripts/test.sh` > Recommendation: take a filesystem/database backup before major Hub upgrades. +## Automate Hub updates (cron) +Install/update a weekly cron job (default: Sunday 02:30): +```bash +./scripts/setup-hub-update-cron.sh +``` + +Optional custom schedule: +```bash +HUB_UPDATE_CRON_SCHEDULE="15 2 * * 6" ./scripts/setup-hub-update-cron.sh +``` + +This setup script will: +- create/update `.hub-update.env` (local only, not committed) +- install/refresh cron entry for `scripts/update-nextcloud-hub.sh` +- write logs to `logs/hub-update.log` + +Defaults are tracked in `.hub-update.env.example`: +- `RUN_APP_UPDATES=1` +- `STRICT_TLS=0` +- `ENABLE_WEB_UPDATER=1` +- `UPDATER_RELEASE_CHANNEL=stable` + ## Move Nextcloud data directory to external storage Use the migration helper to move existing data to a host path and switch the app to a bind mount. @@ -136,22 +181,27 @@ List existing users: ```bash docker exec --user www-data nextcloud-app php occ user:list ``` + Reset password using helper script (interactive prompt): ```bash ./scripts/reset-admin-password.sh admin ``` + Reset password non-interactively (for automation): ```bash NEW_NEXTCLOUD_PASSWORD={{NEW_NEXTCLOUD_PASSWORD}} ./scripts/reset-admin-password.sh admin ``` + You can target a different username by passing it as the first argument. ## Operational/security changes applied (Apr 2026) Applied and validated in this deployment: + - `docker-compose.yml` (`app` service): - - startup command now ensures writable `/var/cache/fontconfig` + - startup command ensures writable `/var/cache/fontconfig` - sets Apache `ServerName nxt.bhatfamily.in` - sets `XDG_CACHE_HOME=/tmp/.cache` + - forces `config/upgrade-disable-web.config.php` to `upgrade.disable-web=false` - `nginx/nginx.conf`: - `server_tokens off` - HSTS set to `max-age=63072000; includeSubDomains; preload` @@ -162,6 +212,7 @@ Applied and validated in this deployment: - `forwarded_for_headers` set to `HTTP_X_FORWARDED_FOR` - `auth.bruteforce.protection.enabled=true` - `maintenance_window_start=1` + - `updater.release.channel=stable` - `weather_status` app disabled to remove repeated PHP warning noise - Host security hygiene: - `.env` permission reduced to `600` @@ -185,6 +236,6 @@ Stop and remove containers/volumes: ``` ## Security notes -- `.env`, `.tls-renewal.env`, and runtime cert material under `nginx/ssl` are intentionally ignored by Git. +- `.env`, `.tls-renewal.env`, `.hub-update.env`, and runtime cert material under `nginx/ssl` are intentionally ignored by Git. - Keep `.env` mode restricted (`chmod 600 .env`). - If secrets were ever committed earlier, rotate them. diff --git a/docker-compose.yml b/docker-compose.yml index 517eb6c..ec0a5bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,14 @@ services: mkdir -p /var/cache/fontconfig chown -R www-data:www-data /var/cache/fontconfig printf "ServerName nxt.bhatfamily.in\n" > /etc/apache2/conf-enabled/servername.conf + cat > /var/www/html/config/upgrade-disable-web.config.php <<'PHP' + false, + ); + PHP + chown www-data:www-data /var/www/html/config/upgrade-disable-web.config.php + chmod 640 /var/www/html/config/upgrade-disable-web.config.php exec apache2-foreground env_file: - .env diff --git a/scripts/setup-hub-update-cron.sh b/scripts/setup-hub-update-cron.sh new file mode 100755 index 0000000..4b1b5b3 --- /dev/null +++ b/scripts/setup-hub-update-cron.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# scripts/setup-hub-update-cron.sh +# Installs/updates a cron entry for automated Nextcloud Hub updates. + +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +HUB_ENV_FILE="${REPO_DIR}/.hub-update.env" +LOG_DIR="${REPO_DIR}/logs" +LOG_FILE="${LOG_DIR}/hub-update.log" +CRON_MARKER="# nextcloud-docker hub update" +CRON_SCHEDULE="${HUB_UPDATE_CRON_SCHEDULE:-30 2 * * 0}" + +if [ -f "${HUB_ENV_FILE}" ]; then + # shellcheck disable=SC1090 + set -a + source "${HUB_ENV_FILE}" + set +a +fi + +RUN_APP_UPDATES="${RUN_APP_UPDATES:-1}" +STRICT_TLS="${STRICT_TLS:-0}" +ENABLE_WEB_UPDATER="${ENABLE_WEB_UPDATER:-1}" +UPDATER_RELEASE_CHANNEL="${UPDATER_RELEASE_CHANNEL:-stable}" + +mkdir -p "${LOG_DIR}" +chmod 700 "${LOG_DIR}" + +cat > "${HUB_ENV_FILE}" </dev/null | grep -v "${CRON_MARKER}" || true + echo "${CRON_LINE}" +} | crontab - + +echo "==> Installed Hub update cron job:" +echo " ${CRON_LINE}" +echo "==> Stored update defaults in ${HUB_ENV_FILE}" +echo "==> Logs will be written to ${LOG_FILE}" diff --git a/scripts/update-nextcloud-hub.sh b/scripts/update-nextcloud-hub.sh index fcc7647..82bdb8f 100755 --- a/scripts/update-nextcloud-hub.sh +++ b/scripts/update-nextcloud-hub.sh @@ -9,6 +9,17 @@ REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" COMPOSE_FILE="${REPO_DIR}/docker-compose.yml" APP_CONTAINER="${NEXTCLOUD_APP_CONTAINER:-nextcloud-app}" RUN_APP_UPDATES="${RUN_APP_UPDATES:-1}" +STRICT_TLS="${STRICT_TLS:-0}" +ENABLE_WEB_UPDATER="${ENABLE_WEB_UPDATER:-1}" +UPDATER_RELEASE_CHANNEL="${UPDATER_RELEASE_CHANNEL:-stable}" +HUB_ENV_FILE="${HUB_ENV_FILE:-${REPO_DIR}/.hub-update.env}" + +if [ -f "${HUB_ENV_FILE}" ]; then + # shellcheck disable=SC1090 + set -a + source "${HUB_ENV_FILE}" + set +a +fi compose() { if docker compose version >/dev/null 2>&1; then @@ -26,12 +37,16 @@ require_command() { fi } +occ_cmd() { + docker exec --user www-data "${APP_CONTAINER}" php occ "$@" +} + wait_for_occ() { local max_attempts=45 local attempt=1 while [ "${attempt}" -le "${max_attempts}" ]; do - if docker exec --user www-data "${APP_CONTAINER}" php occ status >/dev/null 2>&1; then + if occ_cmd status >/dev/null 2>&1; then return 0 fi @@ -43,6 +58,20 @@ wait_for_occ() { return 1 } +enable_web_updater() { + echo "==> Enabling Nextcloud web updater" + docker exec "${APP_CONTAINER}" sh -lc "cat > /var/www/html/config/upgrade-disable-web.config.php <<'PHP' + false, +); +PHP +chown www-data:www-data /var/www/html/config/upgrade-disable-web.config.php +chmod 640 /var/www/html/config/upgrade-disable-web.config.php" + + occ_cmd config:system:set updater.release.channel --value="${UPDATER_RELEASE_CHANNEL}" +} + require_command docker require_command curl @@ -67,47 +96,40 @@ if ! wait_for_occ; then exit 1 fi -maintenance_enabled=0 -cleanup() { - if [ "${maintenance_enabled}" -eq 1 ]; then - docker exec --user www-data "${APP_CONTAINER}" php occ maintenance:mode --off >/dev/null 2>&1 || true - fi -} -trap cleanup EXIT +if [ "${ENABLE_WEB_UPDATER}" = "1" ]; then + enable_web_updater +else + echo "==> Skipping web-updater enablement (ENABLE_WEB_UPDATER=${ENABLE_WEB_UPDATER})" +fi -echo "==> Enabling maintenance mode" -docker exec --user www-data "${APP_CONTAINER}" php occ maintenance:mode --on -maintenance_enabled=1 +echo "==> Ensuring maintenance mode is off before upgrade" +occ_cmd maintenance:mode --off >/dev/null 2>&1 || true echo "==> Running Nextcloud upgrade" -docker exec --user www-data "${APP_CONTAINER}" php occ upgrade +occ_cmd upgrade if [ "${RUN_APP_UPDATES}" = "1" ]; then echo "==> Updating installed apps" - docker exec --user www-data "${APP_CONTAINER}" php occ app:update --all + occ_cmd app:update --all else echo "==> Skipping app:update --all (RUN_APP_UPDATES=${RUN_APP_UPDATES})" fi echo "==> Running database/schema remediation commands" -docker exec --user www-data "${APP_CONTAINER}" php occ db:add-missing-columns || true -docker exec --user www-data "${APP_CONTAINER}" php occ db:add-missing-indices || true -docker exec --user www-data "${APP_CONTAINER}" php occ db:add-missing-primary-keys || true +occ_cmd db:add-missing-columns || true +occ_cmd db:add-missing-indices || true +occ_cmd db:add-missing-primary-keys || true echo "==> Running maintenance repair" -docker exec --user www-data "${APP_CONTAINER}" php occ maintenance:repair - -echo "==> Disabling maintenance mode" -docker exec --user www-data "${APP_CONTAINER}" php occ maintenance:mode --off -maintenance_enabled=0 +occ_cmd maintenance:repair echo "==> Running post-update checks" -docker exec --user www-data "${APP_CONTAINER}" php occ status -docker exec --user www-data "${APP_CONTAINER}" php occ setupchecks || true +occ_cmd status +occ_cmd setupchecks || true if [ -x "${REPO_DIR}/scripts/test.sh" ]; then echo "==> Running endpoint smoke tests" - STRICT_TLS="${STRICT_TLS:-0}" "${REPO_DIR}/scripts/test.sh" + STRICT_TLS="${STRICT_TLS}" "${REPO_DIR}/scripts/test.sh" else echo "WARN: scripts/test.sh is not executable; skipping smoke tests." fi