Enable web updater and automate Hub updates
Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
# Local runtime configuration
|
# Local runtime configuration
|
||||||
.env
|
.env
|
||||||
.tls-renewal.env
|
.tls-renewal.env
|
||||||
|
.hub-update.env
|
||||||
|
|
||||||
# Runtime TLS artifacts (never commit private keys/certs)
|
# Runtime TLS artifacts (never commit private keys/certs)
|
||||||
nginx/ssl/*.crt
|
nginx/ssl/*.crt
|
||||||
|
|||||||
4
.hub-update.env.example
Normal file
4
.hub-update.env.example
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
RUN_APP_UPDATES=1
|
||||||
|
STRICT_TLS=0
|
||||||
|
ENABLE_WEB_UPDATER=1
|
||||||
|
UPDATER_RELEASE_CHANNEL=stable
|
||||||
61
README.md
61
README.md
@ -1,8 +1,10 @@
|
|||||||
# Nextcloud on Ubuntu via Docker for nxt.bhatfamily.in
|
# Nextcloud on Ubuntu via Docker for nxt.bhatfamily.in
|
||||||
This repository deploys Nextcloud behind Nginx using Docker Compose.
|
This repository deploys Nextcloud behind Nginx using Docker Compose.
|
||||||
|
|
||||||
Exposed ports:
|
Exposed ports:
|
||||||
- HTTP: `8082`
|
- HTTP: `8082`
|
||||||
- HTTPS: `8446`
|
- HTTPS: `8446`
|
||||||
|
|
||||||
Target hostname:
|
Target hostname:
|
||||||
- `nxt.bhatfamily.in`
|
- `nxt.bhatfamily.in`
|
||||||
|
|
||||||
@ -14,10 +16,12 @@ The stack now includes:
|
|||||||
- Production TLS provisioning using Let's Encrypt DNS-01 with Cloudflare
|
- Production TLS provisioning using Let's Encrypt DNS-01 with Cloudflare
|
||||||
- Automated TLS renewal job support (cron)
|
- Automated TLS renewal job support (cron)
|
||||||
- Nextcloud app startup fixes for Apache `ServerName` and writable Fontconfig cache
|
- 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`)
|
- 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)
|
- Nginx hardening (`server_tokens off`, stronger HSTS, hide `X-Powered-By`, TLS session hardening)
|
||||||
- Brute-force protection explicitly enabled and maintenance window configured
|
- 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
|
## Prerequisites
|
||||||
- Ubuntu host with Docker + Docker Compose plugin (or `docker-compose`)
|
- Ubuntu host with Docker + Docker Compose plugin (or `docker-compose`)
|
||||||
@ -40,6 +44,24 @@ cp .env.example .env
|
|||||||
./scripts/test.sh
|
./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)
|
## Update Nextcloud Hub (scripted)
|
||||||
Use the upgrade helper script to pull new images, apply the upgrade, run post-upgrade repairs, and validate endpoints.
|
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:
|
What the script does:
|
||||||
- pulls latest `db`, `app`, and `web` images
|
- pulls latest `db`, `app`, and `web` images
|
||||||
- recreates services via Compose
|
- 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 `occ app:update --all` (unless disabled)
|
||||||
- runs schema and repair commands (`db:add-missing-*`, `maintenance:repair`)
|
- runs schema and repair commands (`db:add-missing-*`, `maintenance:repair`)
|
||||||
- disables maintenance mode
|
|
||||||
- runs `occ status`, `occ setupchecks`, and `scripts/test.sh`
|
- runs `occ status`, `occ setupchecks`, and `scripts/test.sh`
|
||||||
|
|
||||||
> Recommendation: take a filesystem/database backup before major Hub upgrades.
|
> 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
|
## 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.
|
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
|
```bash
|
||||||
docker exec --user www-data nextcloud-app php occ user:list
|
docker exec --user www-data nextcloud-app php occ user:list
|
||||||
```
|
```
|
||||||
|
|
||||||
Reset password using helper script (interactive prompt):
|
Reset password using helper script (interactive prompt):
|
||||||
```bash
|
```bash
|
||||||
./scripts/reset-admin-password.sh admin
|
./scripts/reset-admin-password.sh admin
|
||||||
```
|
```
|
||||||
|
|
||||||
Reset password non-interactively (for automation):
|
Reset password non-interactively (for automation):
|
||||||
```bash
|
```bash
|
||||||
NEW_NEXTCLOUD_PASSWORD={{NEW_NEXTCLOUD_PASSWORD}} ./scripts/reset-admin-password.sh admin
|
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.
|
You can target a different username by passing it as the first argument.
|
||||||
|
|
||||||
## Operational/security changes applied (Apr 2026)
|
## Operational/security changes applied (Apr 2026)
|
||||||
Applied and validated in this deployment:
|
Applied and validated in this deployment:
|
||||||
|
|
||||||
- `docker-compose.yml` (`app` service):
|
- `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 Apache `ServerName nxt.bhatfamily.in`
|
||||||
- sets `XDG_CACHE_HOME=/tmp/.cache`
|
- sets `XDG_CACHE_HOME=/tmp/.cache`
|
||||||
|
- forces `config/upgrade-disable-web.config.php` to `upgrade.disable-web=false`
|
||||||
- `nginx/nginx.conf`:
|
- `nginx/nginx.conf`:
|
||||||
- `server_tokens off`
|
- `server_tokens off`
|
||||||
- HSTS set to `max-age=63072000; includeSubDomains; preload`
|
- 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`
|
- `forwarded_for_headers` set to `HTTP_X_FORWARDED_FOR`
|
||||||
- `auth.bruteforce.protection.enabled=true`
|
- `auth.bruteforce.protection.enabled=true`
|
||||||
- `maintenance_window_start=1`
|
- `maintenance_window_start=1`
|
||||||
|
- `updater.release.channel=stable`
|
||||||
- `weather_status` app disabled to remove repeated PHP warning noise
|
- `weather_status` app disabled to remove repeated PHP warning noise
|
||||||
- Host security hygiene:
|
- Host security hygiene:
|
||||||
- `.env` permission reduced to `600`
|
- `.env` permission reduced to `600`
|
||||||
@ -185,6 +236,6 @@ Stop and remove containers/volumes:
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Security notes
|
## 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`).
|
- Keep `.env` mode restricted (`chmod 600 .env`).
|
||||||
- If secrets were ever committed earlier, rotate them.
|
- If secrets were ever committed earlier, rotate them.
|
||||||
|
|||||||
@ -33,6 +33,14 @@ services:
|
|||||||
mkdir -p /var/cache/fontconfig
|
mkdir -p /var/cache/fontconfig
|
||||||
chown -R www-data:www-data /var/cache/fontconfig
|
chown -R www-data:www-data /var/cache/fontconfig
|
||||||
printf "ServerName nxt.bhatfamily.in\n" > /etc/apache2/conf-enabled/servername.conf
|
printf "ServerName nxt.bhatfamily.in\n" > /etc/apache2/conf-enabled/servername.conf
|
||||||
|
cat > /var/www/html/config/upgrade-disable-web.config.php <<'PHP'
|
||||||
|
<?php
|
||||||
|
$$CONFIG = array (
|
||||||
|
'upgrade.disable-web' => 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
|
exec apache2-foreground
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|||||||
48
scripts/setup-hub-update-cron.sh
Executable file
48
scripts/setup-hub-update-cron.sh
Executable file
@ -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}" <<ENVFILE
|
||||||
|
RUN_APP_UPDATES=${RUN_APP_UPDATES}
|
||||||
|
STRICT_TLS=${STRICT_TLS}
|
||||||
|
ENABLE_WEB_UPDATER=${ENABLE_WEB_UPDATER}
|
||||||
|
UPDATER_RELEASE_CHANNEL=${UPDATER_RELEASE_CHANNEL}
|
||||||
|
ENVFILE
|
||||||
|
chmod 600 "${HUB_ENV_FILE}"
|
||||||
|
|
||||||
|
CRON_COMMAND="cd ${REPO_DIR} && /usr/bin/env bash ${REPO_DIR}/scripts/update-nextcloud-hub.sh >> ${LOG_FILE} 2>&1"
|
||||||
|
CRON_LINE="${CRON_SCHEDULE} ${CRON_COMMAND} ${CRON_MARKER}"
|
||||||
|
|
||||||
|
{
|
||||||
|
crontab -l 2>/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}"
|
||||||
@ -9,6 +9,17 @@ REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|||||||
COMPOSE_FILE="${REPO_DIR}/docker-compose.yml"
|
COMPOSE_FILE="${REPO_DIR}/docker-compose.yml"
|
||||||
APP_CONTAINER="${NEXTCLOUD_APP_CONTAINER:-nextcloud-app}"
|
APP_CONTAINER="${NEXTCLOUD_APP_CONTAINER:-nextcloud-app}"
|
||||||
RUN_APP_UPDATES="${RUN_APP_UPDATES:-1}"
|
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() {
|
compose() {
|
||||||
if docker compose version >/dev/null 2>&1; then
|
if docker compose version >/dev/null 2>&1; then
|
||||||
@ -26,12 +37,16 @@ require_command() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
occ_cmd() {
|
||||||
|
docker exec --user www-data "${APP_CONTAINER}" php occ "$@"
|
||||||
|
}
|
||||||
|
|
||||||
wait_for_occ() {
|
wait_for_occ() {
|
||||||
local max_attempts=45
|
local max_attempts=45
|
||||||
local attempt=1
|
local attempt=1
|
||||||
|
|
||||||
while [ "${attempt}" -le "${max_attempts}" ]; do
|
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
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -43,6 +58,20 @@ wait_for_occ() {
|
|||||||
return 1
|
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'
|
||||||
|
<?php
|
||||||
|
\$CONFIG = array (
|
||||||
|
'upgrade.disable-web' => 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 docker
|
||||||
require_command curl
|
require_command curl
|
||||||
|
|
||||||
@ -67,47 +96,40 @@ if ! wait_for_occ; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
maintenance_enabled=0
|
if [ "${ENABLE_WEB_UPDATER}" = "1" ]; then
|
||||||
cleanup() {
|
enable_web_updater
|
||||||
if [ "${maintenance_enabled}" -eq 1 ]; then
|
else
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ maintenance:mode --off >/dev/null 2>&1 || true
|
echo "==> Skipping web-updater enablement (ENABLE_WEB_UPDATER=${ENABLE_WEB_UPDATER})"
|
||||||
fi
|
fi
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
echo "==> Enabling maintenance mode"
|
echo "==> Ensuring maintenance mode is off before upgrade"
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ maintenance:mode --on
|
occ_cmd maintenance:mode --off >/dev/null 2>&1 || true
|
||||||
maintenance_enabled=1
|
|
||||||
|
|
||||||
echo "==> Running Nextcloud upgrade"
|
echo "==> Running Nextcloud upgrade"
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ upgrade
|
occ_cmd upgrade
|
||||||
|
|
||||||
if [ "${RUN_APP_UPDATES}" = "1" ]; then
|
if [ "${RUN_APP_UPDATES}" = "1" ]; then
|
||||||
echo "==> Updating installed apps"
|
echo "==> Updating installed apps"
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ app:update --all
|
occ_cmd app:update --all
|
||||||
else
|
else
|
||||||
echo "==> Skipping app:update --all (RUN_APP_UPDATES=${RUN_APP_UPDATES})"
|
echo "==> Skipping app:update --all (RUN_APP_UPDATES=${RUN_APP_UPDATES})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> Running database/schema remediation commands"
|
echo "==> Running database/schema remediation commands"
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ db:add-missing-columns || true
|
occ_cmd db:add-missing-columns || true
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ db:add-missing-indices || true
|
occ_cmd 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-primary-keys || true
|
||||||
|
|
||||||
echo "==> Running maintenance repair"
|
echo "==> Running maintenance repair"
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ maintenance:repair
|
occ_cmd maintenance:repair
|
||||||
|
|
||||||
echo "==> Disabling maintenance mode"
|
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ maintenance:mode --off
|
|
||||||
maintenance_enabled=0
|
|
||||||
|
|
||||||
echo "==> Running post-update checks"
|
echo "==> Running post-update checks"
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ status
|
occ_cmd status
|
||||||
docker exec --user www-data "${APP_CONTAINER}" php occ setupchecks || true
|
occ_cmd setupchecks || true
|
||||||
|
|
||||||
if [ -x "${REPO_DIR}/scripts/test.sh" ]; then
|
if [ -x "${REPO_DIR}/scripts/test.sh" ]; then
|
||||||
echo "==> Running endpoint smoke tests"
|
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
|
else
|
||||||
echo "WARN: scripts/test.sh is not executable; skipping smoke tests."
|
echo "WARN: scripts/test.sh is not executable; skipping smoke tests."
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user