commit 2626036a21944e6856b49485eda13cd4c710b467 Author: Dillard Blom Date: Tue Jul 7 01:18:01 2026 +0200 Initial commit: Nextcloud bare/Hansson → AIO migration toolkit Scripts 01-05 voor sequentiële migratie: transfer, staging, upgrade-keten, AIO-install en DB-import. PostgreSQL + MySQL/MariaDB bronnen ondersteund. Proxmox/OPNSense VM-builder optioneel in proxmox/. README met 12 gotchas. Co-Authored-By: Claude Sonnet 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8f168e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Lokale config met echte IP's, wachtwoorden en domeinen — nooit committen +config.local.sh +*.local.sh + +# Logs en tijdelijke bestanden +*.log +.DS_Store diff --git a/01-transfer.sh b/01-transfer.sh new file mode 100755 index 0000000..3776738 --- /dev/null +++ b/01-transfer.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# 01-transfer.sh — Rsync bestanden + database-dump van bronserver naar doelserver. +# +# Wat het doet: +# 1. Genereert SSH-key op doel en voegt die toe aan bron (authorized_keys) +# 2. Rsynct NC-app, configs, crontabs → /root/staging/ +# 3. Maakt een database-dump op de bron (PostgreSQL of MySQL/MariaDB) +# 4. Rsynct ncdata → TARGET_DATA_PATH (herstart-veilig via --partial) +# +# Herstart-veilig: rsync --partial; dit script is idempotent (delta op re-run). +# Voortgang volgen: ssh 'tail -f /var/log/pull-source.log' + +set -euo pipefail +source "$(dirname "$0")/config.sh" +log() { echo "[$(date '+%H:%M:%S')] $*"; } + +# ── STAP 1: SSH-key op doel → bron ─────────────────────────────────────────── +log "SSH-key genereren op doel (indien nodig)..." +ssh "${TARGET_SSH}" "test -f /root/.ssh/id_ed25519 || \ + ssh-keygen -t ed25519 -N '' -f /root/.ssh/id_ed25519 -C nc-migration-pull" + +TARGETKEY=$(ssh "${TARGET_SSH}" "cat /root/.ssh/id_ed25519.pub") + +log "Key toevoegen aan bron authorized_keys (indien nodig)..." +ssh "${SOURCE_USER}@${SOURCE_HOST}" \ + "grep -qF '${TARGETKEY}' /root/.ssh/authorized_keys 2>/dev/null || \ + echo '${TARGETKEY}' >> /root/.ssh/authorized_keys" + +log "Hostkey accepteren + verbinding testen van doel naar bron..." +ssh "${TARGET_SSH}" " + ssh-keyscan -H ${SOURCE_HOST} >> /root/.ssh/known_hosts 2>/dev/null + ssh -o ConnectTimeout=10 ${SOURCE_USER}@${SOURCE_HOST} hostname +" + +# ── DB-dump commando samenstellen ──────────────────────────────────────────── +# AIO gebruikt PostgreSQL; als de bron MySQL/MariaDB is, wordt de DB later +# geconverteerd via 'occ db:convert-type pgsql' (na de upgrade-keten, voor AIO-import). +if [[ "${SOURCE_DB_TYPE}" == "mysql" ]]; then + # Wachtwoord op de command line is niet ideaal maar werkt voor eenmalige migratie. + # Alternatief: zet een .my.cnf op de bronserver met [mysqldump] credentials. + DUMP_CMD="mysqldump -u '${SOURCE_DB_USER}' -p'${SOURCE_DB_PASS}' '${SOURCE_DB}' | gzip" +else + DUMP_CMD="sudo -u postgres pg_dump '${SOURCE_DB}' | gzip" +fi + +# ── STAP 2: Pull-script op doel plaatsen en starten ────────────────────────── +log "pull-source.sh plaatsen op doel..." +ssh "${TARGET_SSH}" "cat > /root/pull-source.sh" << EOF +#!/bin/bash +# Draait op doel. Log: /var/log/pull-source.log +set -uo pipefail +SRC="${SOURCE_USER}@${SOURCE_HOST}" +{ +echo "===== START \$(date) =====" +mkdir -p /root/staging/etc + +echo "== 1/4 NC-app ==" +rsync -aHAX --numeric-ids --partial \ + \${SRC}:${SOURCE_NC_PATH}/ /root/staging/nextcloud-app/ + +echo "== 2/4 configs ==" +rsync -aH --numeric-ids \${SRC}:/etc/apache2/ /root/staging/etc/apache2/ 2>/dev/null || true +rsync -aH --numeric-ids \${SRC}:/etc/php/ /root/staging/etc/php/ 2>/dev/null || true +rsync -aH --numeric-ids \${SRC}:/etc/letsencrypt/ /root/staging/etc/letsencrypt/ 2>/dev/null || true +rsync -aH --numeric-ids \${SRC}:/var/scripts/ /root/staging/var-scripts/ 2>/dev/null || true +ssh \${SRC} 'crontab -l' > /root/staging/crontab-root.txt 2>/dev/null || true +ssh \${SRC} 'crontab -u www-data -l' > /root/staging/crontab-www-data.txt 2>/dev/null || true + +echo "== 3/4 database dump (${SOURCE_DB_TYPE}: ${SOURCE_DB}) ==" +ssh \${SRC} '${DUMP_CMD}' > /root/staging/${SOURCE_DB}-\$(date +%F).sql.gz +ls -lh /root/staging/${SOURCE_DB}-*.sql.gz | tail -1 + +echo "== 4/4 ncdata ==" +rsync -aHAX --numeric-ids --partial --info=stats1 \ + \${SRC}:${SOURCE_DATA_PATH}/ ${TARGET_DATA_PATH}/ + +echo "===== KLAAR \$(date) =====" +df -h ${TARGET_DATA_PATH} +} >> /var/log/pull-source.log 2>&1 +EOF +ssh "${TARGET_SSH}" "chmod +x /root/pull-source.sh" + +# ── STAP 3: Starten met nohup ──────────────────────────────────────────────── +log "Transfer starten (nohup, herstart-veilig)..." +ssh "${TARGET_SSH}" "nohup /root/pull-source.sh > /dev/null 2>&1 & echo PID: \$!" + +log "✓ Transfer draait op de achtergrond." +log " Voortgang: ssh 'tail -f /var/log/pull-source.log'" +log " Schijfruimte: ssh 'df -h ${TARGET_DATA_PATH}'" diff --git a/02-build-staging.sh b/02-build-staging.sh new file mode 100755 index 0000000..2ea772e --- /dev/null +++ b/02-build-staging.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +# 02-build-staging.sh — Bouwt de staging NC-stack op de doelserver. +# +# Doel: exact genoeg om 'occ upgrade' te kunnen draaien richting AIO-doelversie. +# Apache luistert alleen op :80 intern; geen SSL. +# Na geslaagde AIO-import: apt purge apache2 php8.3* postgresql mariadb-server +# +# Ondersteunt PostgreSQL en MySQL/MariaDB als brontype (SOURCE_DB_TYPE in config.sh). +# AIO gebruikt altijd PostgreSQL. Bij MySQL-bron: na de upgrade-keten converteren +# met 'occ db:convert-type pgsql' vóór 05-import.sh. +# +# Log op doel: /var/log/build-staging.log + +set -euo pipefail +source "$(dirname "$0")/config.sh" +log() { echo "[$(date '+%H:%M:%S')] $*"; } + +log "Staging stack bouwen op ${TARGET_SSH}..." + +ssh "${TARGET_SSH}" bash -s << REMOTE +set -euo pipefail +log() { echo "[\$(date '+%H:%M:%S')] \$*"; } + +# Config-variabelen (expanded door lokale shell) +STAGING_DB="${STAGING_DB}" +STAGING_DB_USER="${STAGING_DB_USER}" +STAGING_DB_PASS="${STAGING_DB_PASS}" +DB_TYPE="${SOURCE_DB_TYPE}" +TARGET_DATA="${TARGET_DATA_PATH}" +SOURCE_DB_NAME="${SOURCE_DB}" + +{ +# ── 1. PHP 8.3 repo + pakketten ─────────────────────────────────────────────── +log "1/7 Pakketten installeren..." +# Debian 13 levert PHP 8.4; NC v30 vereist max PHP 8.3 → sury.org repo. +# Pas dit aan als je start vanaf NC v32+ (dan werkt PHP 8.4 wel). +apt-get update -qq +apt-get install -y curl ca-certificates lsb-release gnupg2 2>&1 | tail -1 +curl -fsSL https://packages.sury.org/php/apt.gpg \ + -o /etc/apt/keyrings/sury-php.gpg +echo "deb [signed-by=/etc/apt/keyrings/sury-php.gpg] https://packages.sury.org/php/ \$(lsb_release -sc) main" \ + > /etc/apt/sources.list.d/sury-php.list +apt-get update -qq + +# PHP-extensies gemeenschappelijk +PHP_COMMON="php8.3-fpm php8.3-cli php8.3-gd php8.3-curl php8.3-xml php8.3-zip + php8.3-mbstring php8.3-intl php8.3-bcmath php8.3-gmp + php8.3-imagick php8.3-redis php8.3-apcu php8.3-ldap php8.3-bz2" + +if [[ "\$DB_TYPE" == "mysql" ]]; then + apt-get install -y apache2 libapache2-mod-fcgid \ + mariadb-server \ + php8.3-mysql \ + \$PHP_COMMON redis-server 2>&1 | tail -3 +else + apt-get install -y apache2 libapache2-mod-fcgid \ + postgresql \ + php8.3-pgsql \ + \$PHP_COMMON redis-server 2>&1 | tail -3 +fi + +# ── 2. NC-app op zijn plek ──────────────────────────────────────────────────── +log "2/7 NC-app kopiëren naar /var/www/nextcloud..." +rsync -a --delete /root/staging/nextcloud-app/ /var/www/nextcloud/ +chown -R www-data:www-data /var/www/nextcloud +chmod -R 750 /var/www/nextcloud +chown -R www-data:www-data "\$TARGET_DATA" + +# ── 3. Database: aanmaken + dump restoren ──────────────────────────────────── +log "3/7 Database inrichten (\$DB_TYPE)..." + +DUMP_FILE=\$(ls -t /root/staging/\${SOURCE_DB_NAME}-*.sql.gz 2>/dev/null | head -1) +[ -z "\$DUMP_FILE" ] && { echo "FOUT: geen dump gevonden in /root/staging/"; exit 1; } +log "Dump: \$DUMP_FILE (\$(du -sh "\$DUMP_FILE" | cut -f1))" + +if [[ "\$DB_TYPE" == "mysql" ]]; then + systemctl enable --now mariadb + mysql -e "CREATE USER IF NOT EXISTS '\${STAGING_DB_USER}'@'localhost' IDENTIFIED BY '\${STAGING_DB_PASS}';" 2>/dev/null || true + mysql -e "CREATE DATABASE IF NOT EXISTS \${STAGING_DB} CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" 2>/dev/null || true + mysql -e "GRANT ALL PRIVILEGES ON \${STAGING_DB}.* TO '\${STAGING_DB_USER}'@'localhost'; FLUSH PRIVILEGES;" + log "Dump restoren naar MariaDB..." + zcat "\$DUMP_FILE" | mysql -u "\$STAGING_DB_USER" -p"\$STAGING_DB_PASS" "\$STAGING_DB" +else + systemctl enable --now postgresql + sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname='\$STAGING_DB_USER'" | grep -q 1 || \ + sudo -u postgres psql -c "CREATE USER \${STAGING_DB_USER} WITH PASSWORD '\${STAGING_DB_PASS}';" + sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='\$STAGING_DB'" | grep -q 1 || \ + sudo -u postgres createdb -O "\$STAGING_DB_USER" "\$STAGING_DB" + log "Dump restoren naar PostgreSQL..." + # Restore als de DB-user zodat tabeleigenaarschap direct klopt + PGPASSWORD="\$STAGING_DB_PASS" zcat "\$DUMP_FILE" | \ + sudo -u postgres psql -U "\$STAGING_DB_USER" "\$STAGING_DB" +fi + +# ── 4. PHP-FPM pool ────────────────────────────────────────────────────────── +log "4/7 PHP-FPM pool configureren..." +cat > /etc/php/8.3/fpm/pool.d/nextcloud.conf << 'EOF' +[nextcloud] +user = www-data +group = www-data +listen = /run/php/php8.3-fpm.nextcloud.sock +listen.owner = www-data +listen.group = www-data +listen.mode = 0660 +pm = dynamic +pm.max_children = 20 +pm.start_servers = 3 +pm.min_spare_servers = 2 +pm.max_spare_servers = 10 +php_admin_value[memory_limit] = 512M +php_admin_value[upload_max_filesize] = 10G +php_admin_value[post_max_size] = 10G +php_admin_value[max_execution_time] = 3600 +EOF +rm -f /etc/php/8.3/fpm/pool.d/www.conf +sed -i 's/^memory_limit.*/memory_limit = 512M/' /etc/php/8.3/cli/php.ini +# apc.enable_cli=1 is vereist voor occ; zonder dit falen APCu-afhankelijke commando's stil +grep -q "apc.enable_cli" /etc/php/8.3/cli/php.ini || echo "apc.enable_cli=1" >> /etc/php/8.3/cli/php.ini +systemctl enable --now php8.3-fpm + +# ── 5. Apache vhost (HTTP-only, intern) ────────────────────────────────────── +log "5/7 Apache vhost configureren..." +a2enmod proxy_fcgi setenvif rewrite headers env dir mime authz_core 2>/dev/null || true +a2enconf php8.3-fpm 2>/dev/null || true +cat > /etc/apache2/sites-available/nextcloud.conf << 'EOF' + + ServerName _default_ + DocumentRoot /var/www/nextcloud + + + Options +FollowSymlinks + AllowOverride All + Require all granted + + Dav off + + + + + SetHandler "proxy:unix:/run/php/php8.3-fpm.nextcloud.sock|fcgi://localhost" + + + ErrorLog \${APACHE_LOG_DIR}/nextcloud-error.log + CustomLog \${APACHE_LOG_DIR}/nextcloud-access.log combined + +EOF +a2dissite 000-default 2>/dev/null || true +a2ensite nextcloud +systemctl enable --now apache2 +apache2ctl configtest + +# ── 6. config.php patchen voor staging ─────────────────────────────────────── +log "6/7 config.php patchen voor staging (geen SSL, intern)..." +# config.php definieert \$CONFIG maar returnt het niet; gebruik require (niet include). +if [[ "\$DB_TYPE" == "mysql" ]]; then + DB_HOST="localhost" + DB_PORT="3306" +else + DB_HOST="localhost" + DB_PORT="5432" +fi +TARGET_IP=\$(hostname -I | awk '{print \$1}') + +php8.3 -r " +require '/var/www/nextcloud/config/config.php'; +\\\$CONFIG['trusted_domains'] = ['\$TARGET_IP', 'localhost']; +\\\$CONFIG['overwrite.cli.url'] = 'http://\$TARGET_IP'; +\\\$CONFIG['overwriteprotocol'] = 'http'; +\\\$CONFIG['maintenance'] = false; +\\\$CONFIG['dbhost'] = '\$DB_HOST'; +\\\$CONFIG['dbport'] = '\$DB_PORT'; +\\\$CONFIG['dbpassword'] = '\$STAGING_DB_PASS'; +\\\$CONFIG['datadirectory'] = '\$TARGET_DATA'; +\\\$CONFIG['redis'] = ['host' => '127.0.0.1', 'port' => 6379]; +\\\$CONFIG['memcache.local'] = '\\\\OC\\\\Memcache\\\\APCu'; +\\\$CONFIG['memcache.locking'] = '\\\\OC\\\\Memcache\\\\Redis'; +\\\$out = \" /etc/systemd/system/nextcloud-cron.service << 'EOF' +[Unit] +Description=Nextcloud cron + +[Service] +User=www-data +ExecStart=/usr/bin/php -f /var/www/nextcloud/cron.php +EOF +cat > /etc/systemd/system/nextcloud-cron.timer << 'EOF' +[Unit] +Description=Nextcloud cron timer + +[Timer] +OnBootSec=5min +OnUnitActiveSec=5min + +[Install] +WantedBy=timers.target +EOF +systemctl enable --now nextcloud-cron.timer + +sudo -u www-data php8.3 /var/www/nextcloud/occ status + +log "✓ Staging stack klaar." +log " occ: sudo -u www-data php8.3 /var/www/nextcloud/occ " +log " Logs: /var/log/apache2/nextcloud-error.log" + +} 2>&1 | tee /var/log/build-staging.log +REMOTE + +log "✓ Script klaar. Voortgang: ssh 'tail -f /var/log/build-staging.log'" diff --git a/03-upgrade.sh b/03-upgrade.sh new file mode 100755 index 0000000..d6db3e3 --- /dev/null +++ b/03-upgrade.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# 03-upgrade.sh — NC upgrade-keten op de doelserver. +# +# Draait sequentieel door NC_UPGRADE_VERSIONS uit config.sh. +# NC vereist één major per stap: v30 → v31 → v32 → v33. +# Na elke major: optioneel een Proxmox-snapshot als terugkeerput. +# +# Bij MySQL-bron: voeg NA de laatste upgrade-stap het commando toe: +# occ db:convert-type --all-apps pgsql +# zodat de DB PostgreSQL is voor AIO-import (05-import.sh). +# +# Terugzetten naar snapshot: ssh 'qm rollback ' (VM offline) +# Log: elke stap print naar stdout; draai met 'tee upgrade.log' om op te slaan. + +set -euo pipefail +source "$(dirname "$0")/config.sh" +log() { echo "[$(date '+%H:%M:%S')] $*"; } + +OCC="sudo -u www-data php8.3 /var/www/nextcloud/occ" + +# ── Apps uitschakelen die niet draaien op staging ──────────────────────────── +# AIO vervangt deze later; staging heeft geen Docker/HPB. +log "Apps uitschakelen die afhankelijk zijn van Docker/HPB..." +ssh "${TARGET_SSH}" " +for app in fulltextsearch fulltextsearch_elasticsearch recognize spreed \ + whiteboard talk_matterbridge notify_push app_api; do + ${OCC} app:disable \"\$app\" 2>/dev/null && echo \" disabled: \$app\" || true +done +" + +# ── Upgrade-functie ─────────────────────────────────────────────────────────── +upgrade_to() { + local VERSION="$1" + local MAJOR="${VERSION%%.*}" + local ARCHIVE="nextcloud-${VERSION}.tar.bz2" + local URL="https://download.nextcloud.com/server/releases/${ARCHIVE}" + local SNAPSHOT="upgrade-v${MAJOR}-done" + + log "══ NC ${VERSION} ══════════════════════════════════════" + + ssh "${TARGET_SSH}" "${OCC} maintenance:mode --on" + + # Download + bestanden vervangen op de doelserver + ssh "${TARGET_SSH}" bash -s << VMEOF +set -euo pipefail +log() { echo "[\$(date '+%H:%M:%S')] \$*"; } + +cd /tmp +if [ ! -f "${ARCHIVE}" ]; then + log "Downloaden ${ARCHIVE}..." + wget -q --show-progress -O "${ARCHIVE}" "${URL}" 2>&1 | tail -3 +else + log "Archief al aanwezig, overgeslagen." +fi + +log "Bestanden vervangen..." +rm -rf /tmp/nc-new && mkdir /tmp/nc-new +tar -xjf "${ARCHIVE}" -C /tmp/nc-new + +cp -a /var/www/nextcloud/config /tmp/nc-config-bak + +# --exclude='/config/' geankerd op root van de bron: raakt geen config/-mappen in apps. +# Zonder leading slash zou rsync ook app/*/config/ verwijderen (bug). +rsync -a --delete \ + --exclude='/config/' \ + --exclude='/data' \ + /tmp/nc-new/nextcloud/ /var/www/nextcloud/ || true + +# config/ herstellen als rsync die geraakt heeft +[ -f /var/www/nextcloud/config/config.php ] || \ + cp -a /tmp/nc-config-bak/. /var/www/nextcloud/config/ + +# Apps zonder appinfo/info.xml crashen occ upgrade — verwijder ze +for dir in /var/www/nextcloud/apps/*/; do + app=\$(basename "\$dir") + [ -f "\$dir/appinfo/info.xml" ] || { rm -rf "\$dir"; echo " opgeruimd (geen info.xml): \$app"; } +done + +chown -R www-data:www-data /var/www/nextcloud +rm -rf /tmp/nc-new /tmp/nc-config-bak +log "Bestanden klaar." +VMEOF + + log "occ upgrade..." + ssh "${TARGET_SSH}" "${OCC} upgrade 2>&1" + + log "App-updates..." + ssh "${TARGET_SSH}" "${OCC} app:update --all 2>&1 || true" + + ssh "${TARGET_SSH}" "${OCC} status" + ssh "${TARGET_SSH}" "${OCC} maintenance:mode --off" + + if [[ "${USE_PROXMOX}" == "true" ]]; then + log "Snapshot '${SNAPSHOT}' aanmaken..." + ssh "${PROXMOX_HOST}" \ + "qm snapshot ${PROXMOX_VMID} '${SNAPSHOT}' \ + --description 'NC ${VERSION} upgrade klaar' 2>&1" + log "✓ Snapshot klaar." + fi + + log "✓ NC ${VERSION} klaar." +} + +# ── Upgrade-keten ───────────────────────────────────────────────────────────── +log "Start versie:" +ssh "${TARGET_SSH}" "${OCC} status | grep versionstring" + +# App-inventarisatie vóór de keten +log "App-inventarisatie opslaan (voor vergelijking achteraf)..." +ssh "${TARGET_SSH}" " +${OCC} app:list --output=json 2>/dev/null | python3 -c \" +import json, sys +d = json.load(sys.stdin) +for app in sorted(d.get('enabled', {}).keys()): + print(app) +\" > /tmp/nc-apps-before.txt +echo \" \$(wc -l < /tmp/nc-apps-before.txt) apps enabled voor upgrade\" +" + +for VERSION in "${NC_UPGRADE_VERSIONS[@]}"; do + upgrade_to "${VERSION}" +done + +# ── App-rapport na de keten ─────────────────────────────────────────────────── +log "══════════════════════════════════════════" +log "Upgrade-keten klaar. Eindversie:" +ssh "${TARGET_SSH}" "${OCC} status" +log "" +log "App-rapport (vergelijking voor/na upgrade-keten):" + +ssh "${TARGET_SSH}" " +${OCC} app:list --output=json 2>/dev/null | python3 -c \" +import json, sys +d = json.load(sys.stdin) +for app in sorted(d.get('enabled', {}).keys()): + print(app) +\" > /tmp/nc-apps-after.txt + +# Apps die verdwenen of uitgeschakeld zijn na de keten +LOST=\$(comm -23 \ + <(sort /tmp/nc-apps-before.txt) \ + <(sort /tmp/nc-apps-after.txt)) + +# Apps die door AIO vervangen worden (geen actie nodig) +AIO_REPLACES='fulltextsearch fulltextsearch_elasticsearch notify_push + spreed whiteboard recognize files_antivirus' + +echo '' +echo '── Nog steeds enabled ────────────────────────────' +comm -12 <(sort /tmp/nc-apps-before.txt) <(sort /tmp/nc-apps-after.txt) + +echo '' +echo '── Uitgeschakeld/verdwenen na upgrade ────────────' +if [ -z \"\$LOST\" ]; then + echo ' (geen)' +else + for app in \$LOST; do + if echo \"\$AIO_REPLACES\" | grep -qw \"\$app\"; then + echo \" [AIO-native] \$app ← vervangen door AIO-container\" + else + echo \" [ACTIE NODIG] \$app ← handmatig herinstalleren na AIO-import\" + fi + done +fi + +echo '' +echo '── Nieuw disabled in NC app store (incompatibel) ─' +${OCC} app:list --output=json 2>/dev/null | python3 -c \" +import json, sys +d = json.load(sys.stdin) +for app in sorted(d.get('disabled', {}).keys()): + print(' ' + app) +\" +" + +if [[ "${SOURCE_DB_TYPE}" == "mysql" ]]; then + log "VOLGENDE STAP (MySQL-bron): converteer de DB naar PostgreSQL vóór 05-import.sh:" + log " ssh 'sudo -u www-data php8.3 /var/www/nextcloud/occ \\" + log " db:convert-type --all-apps pgsql ${STAGING_DB_USER} ${STAGING_DB_PASS} ${STAGING_DB}'" + log " Daarna een verse pg_dump maken en opslaan als /tmp/nc_v33_aio.sql" +else + log "VOLGENDE STAP: 04-install-aio.sh production" +fi diff --git a/04-install-aio.sh b/04-install-aio.sh new file mode 100755 index 0000000..cead6d6 --- /dev/null +++ b/04-install-aio.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# 04-install-aio.sh — AIO mastercontainer installeren op de doelserver. +# +# Gebruik: +# ./04-install-aio.sh test → NC_STAGING_DOMAIN, poort 11000, skip LE +# ./04-install-aio.sh production → NC_DOMAIN, poort 443, Let's Encrypt cert +# +# Na dit script: handmatige stappen in de AIO-wizard (zie output). +# Wacht tot alle containers groen zijn → dan 05-import.sh draaien. +# +# BELANGRIJK: de mastercontainer bindt poort 443/80/3478 NIET zelf. +# AIO's apache-subcontainer beheert die poorten. Als je die poorten wél aan de +# mastercontainer geeft, faalt de domeincheck omdat beide containers op 443 luisteren. + +set -euo pipefail +source "$(dirname "$0")/config.sh" +MODE="${1:-test}" +log() { echo "[$(date '+%H:%M:%S')] $*"; } + +if [[ "$MODE" == "production" ]]; then + DOMAIN="${NC_DOMAIN}" + APACHE_PORT=443 + SKIP_VALIDATION="" +else + DOMAIN="${NC_STAGING_DOMAIN}" + APACHE_PORT=11000 + SKIP_VALIDATION="-e SKIP_DOMAIN_VALIDATION=true" +fi + +log "Modus: ${MODE} — domein: ${DOMAIN} — apache-poort: ${APACHE_PORT}" + +# ── Snapshot vóór AIO ──────────────────────────────────────────────────────── +if [[ "${USE_PROXMOX}" == "true" ]]; then + SNAPSHOT="pre-aio-import-${MODE}" + log "Snapshot '${SNAPSHOT}' aanmaken..." + ssh "${PROXMOX_HOST}" \ + "qm snapshot ${PROXMOX_VMID} '${SNAPSHOT}' \ + --description 'Voor AIO start (${MODE})' 2>&1" + log "✓ Snapshot klaar." +fi + +# ── Staging NC stoppen ─────────────────────────────────────────────────────── +log "Staging stack stoppen (indien actief)..." +ssh "${TARGET_SSH}" " + sudo -u www-data php8.3 /var/www/nextcloud/occ maintenance:mode --on 2>/dev/null || true + systemctl stop apache2 php8.3-fpm nextcloud-cron.timer 2>/dev/null || true + echo 'Staging gestopt.' +" + +# ── Docker installeren ─────────────────────────────────────────────────────── +log "Docker controleren / installeren..." +ssh "${TARGET_SSH}" bash -s << 'REMOTE' +if command -v docker &>/dev/null; then + echo "Docker al aanwezig: $(docker --version)" + exit 0 +fi +apt-get update -qq +apt-get install -y ca-certificates curl gnupg lsb-release 2>&1 | tail -1 +install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/debian/gpg \ + -o /etc/apt/keyrings/docker.asc +chmod a+r /etc/apt/keyrings/docker.asc +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \ + https://download.docker.com/linux/debian \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + > /etc/apt/sources.list.d/docker.list +apt-get update -qq +apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin 2>&1 | tail -3 +systemctl enable --now docker +echo "Docker geïnstalleerd: $(docker --version)" +REMOTE + +# ── AIO mastercontainer starten ────────────────────────────────────────────── +log "AIO mastercontainer starten..." +ssh "${TARGET_SSH}" bash -s << REMOTE +set -e +docker rm -f nextcloud-aio-mastercontainer 2>/dev/null || true + +docker run -d \ + --name nextcloud-aio-mastercontainer \ + --restart always \ + -p 8080:8080 \ + -e APACHE_PORT=${APACHE_PORT} \ + -e APACHE_IP_BINDING=0.0.0.0 \ + -e NEXTCLOUD_DATADIR=${TARGET_DATA_PATH} \ + ${SKIP_VALIDATION} \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + -v nextcloud_aio_mastercontainer:/mnt/docker-aio-config \ + nextcloud/all-in-one:latest + +echo "" +echo "Wachten op AIO start (~15s)..." +sleep 15 + +echo "" +echo "══ AIO PASSPHRASE ══════════════════════════════" +docker logs nextcloud-aio-mastercontainer 2>&1 \ + | grep -i "passphrase\|initial passphrase" | tail -3 \ + || docker logs nextcloud-aio-mastercontainer 2>&1 | tail -15 +echo "════════════════════════════════════════════════" +REMOTE + +# ── App-rapport uit stap 03 printen ───────────────────────────────────────── +log "" +log "Apps die na AIO-import handmatig herinstalleerd moeten worden:" +ssh "${TARGET_SSH}" " +if [ -f /tmp/nc-apps-before.txt ] && [ -f /tmp/nc-apps-after.txt ]; then + AIO_REPLACES='fulltextsearch fulltextsearch_elasticsearch notify_push + spreed whiteboard recognize files_antivirus' + comm -23 \ + <(sort /tmp/nc-apps-before.txt) \ + <(sort /tmp/nc-apps-after.txt) | while read app; do + echo \"\$AIO_REPLACES\" | grep -qw \"\$app\" || echo \" occ app:install \$app\" + done +else + echo ' (voer eerst 03-upgrade.sh uit voor een volledige lijst)' +fi +" 2>/dev/null || true + +# ── Instructies ────────────────────────────────────────────────────────────── +TARGET_IP=$(ssh "${TARGET_SSH}" "hostname -I | awk '{print \$1}'" 2>/dev/null || echo "") + +echo "" +log "══════════════════════════════════════════════════════════════" +log "AIO mastercontainer draait. Handmatige stappen in de browser:" +log "" +log " 1. Open: https://${TARGET_IP}:8080" +log " (self-signed cert → klik door de waarschuwing)" +log " 2. Voer de passphrase in (zie output hierboven)" +log " 3. Stel domein in: ${DOMAIN}" +if [[ "$MODE" == "test" ]]; then +log " 4. 'Skip domain validation' is al geconfigureerd" +fi +log " 5. Selecteer optionele containers:" +log " Minimaal (test): Imaginary" +log " Volledig (prod): Talk, Imaginary, ClamAV, Fulltextsearch, Whiteboard" +log " Office-suite: kies één — Collabora, OnlyOffice, Euro-Office, of geen" +log " 6. Klik 'Save and start containers'" +log " 7. Wacht tot ALLE containers groen zijn (~5-15 min)" +log " Let op: eerste start na import geeft zware CPU-piek door" +log " Elasticsearch-indexering + ClamAV DB-update + Recognize." +log " Plan dit buiten kantooruren." +log "" +log " Daarna: ./05-import.sh" +log "══════════════════════════════════════════════════════════════" diff --git a/05-import.sh b/05-import.sh new file mode 100755 index 0000000..471b53a --- /dev/null +++ b/05-import.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# 05-import.sh — Bestaande NC-database + data importeren in AIO. +# +# Vereiste: AIO-wizard afgerond, alle containers groen (via https://:8080). +# Draait op de doelserver zelf (gesourcet via SSH). +# +# Wat het doet: +# 1. Dump van de staging-postgres (NC-versie gelijk aan AIO-doelversie) +# 2. NC + notify-push stoppen (DB-verbindingen verbreken) +# 3. AIO-database vervangen door de staging-dump +# 4. NC starten + instanceid/passwordsalt/secret overnemen van staging +# 5. maintenance:repair + maintenance:mode --off +# 6. groupfolders herinstalleren (AIO start kaal) +# +# AIO v13+ containernamen (wijken af van oudere docs): +# DB-container : nextcloud-aio-database +# Database : nextcloud_database +# NC-user : oc_nextcloud +# Superuser : nextcloud +# +# Log: /var/log/aio-import.log op de doelserver + +set -euo pipefail +source "$(dirname "$0")/config.sh" +log() { echo "[$(date '+%H:%M:%S')] $*"; } + +log "05-import.sh starten op ${TARGET_SSH}..." + +ssh "${TARGET_SSH}" bash -s << REMOTE +set -euo pipefail +log() { echo "[\$(date '+%H:%M:%S')] \$*"; } + +STAGING_DB="${STAGING_DB}" +STAGING_DB_USER="${STAGING_DB_USER}" +STAGING_DB_PASS="${STAGING_DB_PASS}" +TARGET_DATA="${TARGET_DATA_PATH}" + +NC_CONTAINER=nextcloud-aio-nextcloud +DB_CONTAINER=nextcloud-aio-database +AIO_DB=nextcloud_database +AIO_SUPER=nextcloud +AIO_NCUSER=oc_nextcloud +DUMP=/tmp/nc_aio_import.sql + +{ +log "═══ AIO import start ═══" + +# ── Sanity checks ──────────────────────────────────────────────────────────── +for c in "\$NC_CONTAINER" "\$DB_CONTAINER"; do + docker ps --format '{{.Names}}' | grep -q "^\${c}$" || { + log "FOUT: container \${c} niet actief." + log "Zorg dat alle AIO-containers groen zijn in de wizard (https://:8080)." + exit 1 + } +done + +# ── Staging-waarden uitlezen ───────────────────────────────────────────────── +# Deze drie waarden koppelen de NC-sessies aan de database. +# Ze MOETEN overeenkomen met de waarden in de te importeren DB. +OCC_STAGING="sudo -u www-data php8.3 /var/www/nextcloud/occ" +if command -v php8.3 &>/dev/null && [ -f /var/www/nextcloud/occ ]; then + INSTANCEID=\$(\$OCC_STAGING config:system:get instanceid 2>/dev/null) + PASSWORDSALT=\$(\$OCC_STAGING config:system:get passwordsalt 2>/dev/null) + SECRET=\$(\$OCC_STAGING config:system:get secret 2>/dev/null) + log "instanceid/passwordsalt/secret uitgelezen uit staging-stack." +else + log "FOUT: staging-stack (php8.3 / occ) niet beschikbaar." + log "Zet INSTANCEID, PASSWORDSALT en SECRET handmatig bovenaan dit script," + log "of herstart 02-build-staging.sh zodat de staging-stack aanwezig is." + exit 1 +fi + +# AIO DB-wachtwoord voor oc_nextcloud (staat in AIO's config.php) +NCPASS=\$(docker exec "\$NC_CONTAINER" \ + php /var/www/html/occ config:system:get dbpassword 2>/dev/null || true) +[ -z "\$NCPASS" ] && { log "FOUT: kon dbpassword niet uitlezen uit AIO-container."; exit 1; } +log "AIO dbpassword aanwezig." + +# ── 1. Staging-postgres dumpen ─────────────────────────────────────────────── +log "1/6 Staging-postgres dumpen (--no-owner --no-acl)..." +sudo -u postgres pg_dump --no-owner --no-acl -Fp "\$STAGING_DB" > "\$DUMP" +log "Dump klaar: \$(du -sh \$DUMP | cut -f1)" + +# ── 2. NC + notify-push stoppen ────────────────────────────────────────────── +log "2/6 NC + notify-push stoppen..." +docker stop "\$NC_CONTAINER" nextcloud-aio-notify-push 2>/dev/null || true +docker exec "\$DB_CONTAINER" psql -U "\$AIO_SUPER" -d postgres \ + -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity + WHERE datname = '\$AIO_DB';" 2>/dev/null || true +log "Containers gestopt." + +# ── 3. AIO-database vervangen ──────────────────────────────────────────────── +log "3/6 AIO DB droppen en opnieuw aanmaken..." +docker exec "\$DB_CONTAINER" psql -U "\$AIO_SUPER" -d postgres \ + -c "DROP DATABASE IF EXISTS \${AIO_DB};" +docker exec "\$DB_CONTAINER" psql -U "\$AIO_SUPER" -d postgres \ + -c "CREATE DATABASE \${AIO_DB} OWNER \${AIO_NCUSER} + ENCODING 'UTF8' + LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8' + TEMPLATE template0;" + +log "4/6 Dump restoren als \$AIO_NCUSER..." +docker exec -e PGPASSWORD="\$NCPASS" -i "\$DB_CONTAINER" \ + psql -U "\$AIO_NCUSER" "\$AIO_DB" < "\$DUMP" + +# Verifieer eigenaarschap — alle tabellen moeten van oc_nextcloud zijn +OWNERS=\$(docker exec "\$DB_CONTAINER" psql -U "\$AIO_SUPER" "\$AIO_DB" \ + -tAc "SELECT tableowner, count(*) FROM pg_tables + WHERE schemaname='public' GROUP BY tableowner;") +log "Tabeleigenaarschap na restore: \$OWNERS" + +# ── 5. NC starten + config bijwerken ──────────────────────────────────────── +log "5/6 NC starten + instanceid/passwordsalt/secret bijwerken..." +docker start "\$NC_CONTAINER" +for i in \$(seq 1 30); do + docker exec "\$NC_CONTAINER" php /var/www/html/occ status &>/dev/null && break || true + sleep 3 +done + +docker exec "\$NC_CONTAINER" php /var/www/html/occ \ + config:system:set instanceid --value="\$INSTANCEID" +docker exec "\$NC_CONTAINER" php /var/www/html/occ \ + config:system:set passwordsalt --value="\$PASSWORDSALT" +docker exec "\$NC_CONTAINER" php /var/www/html/occ \ + config:system:set secret --value="\$SECRET" +docker exec "\$NC_CONTAINER" php /var/www/html/occ \ + config:system:set datadirectory --value="/mnt/ncdata" +log "config.php bijgewerkt." + +# ── 6. Repair + maintenance uit ───────────────────────────────────────────── +log "6/6 maintenance:repair + maintenance:mode --off..." +docker exec "\$NC_CONTAINER" php /var/www/html/occ \ + maintenance:repair --include-expensive 2>&1 | tail -20 +docker exec "\$NC_CONTAINER" php /var/www/html/occ maintenance:mode --off + +# groupfolders herinstalleren (AIO start kaal, app zit in de DB maar niet in de container) +log "groupfolders installeren..." +docker exec "\$NC_CONTAINER" php /var/www/html/occ app:install groupfolders 2>&1 | tail -2 || \ +docker exec "\$NC_CONTAINER" php /var/www/html/occ app:enable groupfolders 2>&1 | tail -2 + +docker exec "\$NC_CONTAINER" php /var/www/html/occ status + +log "═══ AIO import klaar ═══" +log "" +log "Controleer NC via: https://${NC_DOMAIN}" +log "" +log "Volgende stappen:" +log " • Snapshot aanmaken: ssh 'qm snapshot aio-import-ok'" +log " • Apps herinstalleren (zie output van 03-upgrade.sh / 04-install-aio.sh)" +log " • DNS-cutover naar productie-IP (als dat nog niet gebeurd is)" +log " • Contabo / bron uit maintenance halen na verificatie" + +} 2>&1 | tee /var/log/aio-import.log +REMOTE diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe67781 --- /dev/null +++ b/README.md @@ -0,0 +1,252 @@ +# nextcloud-to-aio + +Migration toolkit: Nextcloud bare/Hansson install → Nextcloud All-in-One (AIO). + +Tested path: NC v30 (Debian 12, Apache + PHP-FPM + PostgreSQL) → AIO v13 (NC v33) on Debian 13. +Works for any NC version that can be upgraded sequentially to the AIO target version. + +--- + +## What it does + +1. **Transfer** — rsync files + database dump from the old server to the new VM +2. **Build staging** — minimal Apache + PHP + DB stack to run the upgrade chain +3. **Upgrade** — sequential major-version upgrades (e.g. v30 → v31 → v32 → v33) +4. **Install AIO** — Docker + AIO mastercontainer; guided wizard +5. **Import** — restore the upgraded DB into AIO, carry over instanceid/passwordsalt/secret + +The data directory (`/mnt/ncdata`) is rsynced once upfront and delta-synced at cutover. +Downtime window is only the final delta-sync + DNS cutover — typically under an hour. + +--- + +## Prerequisites + +- New VM with Debian 12/13, SSH root access, enough disk for data +- Old server accessible via SSH from the new VM (key-based) +- DNS control for your NC domain +- `~/.ssh/config` aliases set up for all hosts + +Optional: +- Proxmox for live snapshots between upgrade steps (`USE_PROXMOX=true`) +- OPNSense with public VIP routing (`proxmox/build-vm.sh`) + +--- + +## Quick start + +```bash +cp config.sh config.local.sh # never commit this file +# edit config.local.sh — fill in all variables +source config.local.sh + +./01-transfer.sh # start background rsync (can take hours) +./02-build-staging.sh # build Apache+PHP+DB on target +./03-upgrade.sh # sequential NC upgrade chain +./04-install-aio.sh test # AIO on staging domain first +# → complete wizard in browser at https://:8080 +./05-import.sh # import DB + data into AIO + +# After verification: +./04-install-aio.sh production # switch to production domain + LE cert +./05-import.sh # re-run import on production AIO +``` + +--- + +## Configuration reference (`config.sh`) + +| Variable | Description | +|---|---| +| `SOURCE_HOST` | IP/hostname of the existing NC server | +| `SOURCE_NC_PATH` | Path to NC app directory (default `/var/www/nextcloud`) | +| `SOURCE_DATA_PATH` | Path to ncdata (default `/mnt/ncdata`) | +| `SOURCE_DB` | Database name on source | +| `SOURCE_DB_TYPE` | `postgres` or `mysql` | +| `SOURCE_DB_PASS` | DB password — only needed for MySQL | +| `TARGET_SSH` | SSH target for new VM (`root@10.x.x.x` or alias) | +| `TARGET_DATA_PATH` | Where ncdata lives on target | +| `NC_DOMAIN` | Production domain for AIO + Let's Encrypt | +| `NC_STAGING_DOMAIN` | Staging domain (HTTP only, internal) | +| `STAGING_DB_PASS` | Password for staging DB (created in step 02) | +| `NC_UPGRADE_VERSIONS` | Array of NC versions to upgrade through | +| `USE_PROXMOX` | `true`/`false` — enable snapshot after each upgrade step | +| `PROXMOX_HOST` | SSH alias for PVE node | +| `PROXMOX_VMID` | VM ID for snapshots | + +--- + +## MySQL → PostgreSQL + +AIO uses PostgreSQL exclusively. If your source uses MySQL/MariaDB, you need one +extra step after the upgrade chain and before `05-import.sh`: + +```bash +# On the target VM, after 03-upgrade.sh completes: +sudo -u www-data php8.3 /var/www/nextcloud/occ \ + db:convert-type --all-apps pgsql \ + +``` + +This converts the staging MariaDB to PostgreSQL in-place. After conversion, +`05-import.sh` takes a `pg_dump` of that PostgreSQL DB and imports it into AIO. + +--- + +## Upgrade path + +NC requires sequential major-version upgrades — you cannot skip a major. +Set `NC_UPGRADE_VERSIONS` in `config.sh` based on your source version: + +| Source | Example path | +|---|---| +| v28/v29 | `("29.0.x" "30.0.x" "31.0.x" "32.0.x" "33.0.x")` | +| v30 | `("31.0.14" "32.0.12" "33.0.6")` | +| v32 | `("33.0.6")` | + +Check the [NC release archive](https://nextcloud.com/changelog/) for the latest patch version per major. +AIO ships a specific NC version — check the AIO release notes to know which final version to target. + +--- + +## Proxmox: creating the VM + +If you use Proxmox, `proxmox/build-vm.sh` creates the target VM automatically: +Debian 13 cloud image, separate LVM data disk, loopback VIP (optional), OPNSense routing (optional). + +Fill in the `Proxmox VM` section of `config.sh`, then: + +```bash +./proxmox/build-vm.sh +``` + +After the VM is up, set `TARGET_SSH` to match `VM_PRIVATE_IP` and continue with `01-transfer.sh`. + +--- + +## Known issues & gotchas + +These caused real failures during development. Read before running. + +### PHP version + +Debian 13 ships PHP 8.4. NC v30 requires PHP ≤ 8.3. +`02-build-staging.sh` adds the [sury.org](https://packages.sury.org) repo for PHP 8.3 automatically. +If your source is NC v32+, you can remove the sury.org step and use the system PHP. + +### `rsync --exclude` anchoring + +`--exclude='config/'` matches **any** directory named `config` in the tree, +including `apps/someapp/config/`. Use `--exclude='/config/'` (leading slash) to +anchor it to the root of the source — this is what the scripts use. + +### `apc.enable_cli` + +APCu is disabled in CLI by default (`apc.enable_cli=0`). `occ` commands that touch +APCu caches silently fail or produce wrong results. `02-build-staging.sh` sets +`apc.enable_cli=1` in `/etc/php/8.3/cli/php.ini` automatically. + +### `require` vs `include` for config.php + +NC's `config.php` defines `$CONFIG` but does not `return` it. +`include('/path/to/config.php')` returns `1` (bool), not the config array. +Use `require` instead — the scripts do this. + +### Apps without `appinfo/info.xml` + +Third-party apps that are present in `/var/www/nextcloud/apps/` but lack +`appinfo/info.xml` (abandoned, partially deleted, or leftover) cause `occ upgrade` +to crash. `03-upgrade.sh` removes them automatically before each upgrade step. + +### AIO mastercontainer port binding + +The AIO mastercontainer must **not** bind ports 80, 443, or 3478. +AIO's apache sub-container manages those ports itself. +Binding them on the mastercontainer causes the domain check to fail with a +conflict — two processes listening on port 443. +`04-install-aio.sh` does not bind those ports. + +### AIO container names (v13+) + +Older AIO docs refer to `nextcloud-aio-postgresql` — this was renamed. +Current names used by `05-import.sh`: + +| Role | Container/resource name | +|---|---| +| DB container | `nextcloud-aio-database` | +| Database | `nextcloud_database` | +| NC DB user | `oc_nextcloud` | +| DB superuser | `nextcloud` | + +### CPU spike on first AIO start after migration + +When AIO starts for the first time with existing data, Elasticsearch indexes all +files, ClamAV downloads its virus database, and Recognize scans all photos. +On a large installation (500K+ files) this can take 1–2 hours at high CPU load. + +**Plan this for late evening** — do not run the first AIO start during business +hours or when the server is serving other workloads. + +### instanceid / passwordsalt / secret + +These three values in `config.php` are the identity of the NC instance. They must +match what is in the database. `05-import.sh` reads them from the staging stack +and writes them into the AIO container's config. If the staging stack has been +removed before running `05-import.sh`, note these values from the source +`config.php` and set them manually. + +### systemd-resolved blocking DNS + +On Debian 12/13, `systemd-resolved` can intercept DNS and cause resolution +failures inside Docker containers. If containers cannot resolve hostnames: + +```bash +systemctl disable --now systemd-resolved +echo "nameserver 1.1.1.1" > /etc/resolv.conf +``` + +### Docker DNS cache after IP change + +Docker caches the upstream DNS from `/etc/resolv.conf` at daemon start. +After a VM IP change or network reconfiguration, containers may get DNS +timeouts even though the host resolves correctly. Fix: + +```bash +systemctl restart docker +docker start $(docker ps -aq) +``` + +### notify-push after import + +`05-import.sh` stops `nextcloud-aio-notify-push` to release DB connections. +After import, restart it via the AIO admin UI or: + +```bash +docker start nextcloud-aio-notify-push +``` + +### OPNSense: fw01 root shell is csh + +If you use OPNSense, its root shell is `csh`. Shell redirects like `2>/dev/null` +are interpreted differently by csh and can corrupt commands. +Always wrap non-trivial commands in `sh -c '...'` when SSHing to OPNSense. +`proxmox/build-vm.sh` does this for all fw01 commands. + +--- + +## After import: apps to reinstall + +`03-upgrade.sh` prints a list of apps that need manual reinstall after AIO import. +These are apps that were enabled in your source but are not included in AIO. +Common examples: + +```bash +docker exec nextcloud-aio-nextcloud php /var/www/html/occ app:install occweb +docker exec nextcloud-aio-nextcloud php /var/www/html/occ app:install drawio +docker exec nextcloud-aio-nextcloud php /var/www/html/occ app:install maps +docker exec nextcloud-aio-nextcloud php /var/www/html/occ app:install extract +docker exec nextcloud-aio-nextcloud php /var/www/html/occ app:enable notes +``` + +AIO includes natively (no action needed): fulltextsearch, notify_push, Talk/spreed, +whiteboard, recognize, files_antivirus (ClamAV), imaginary. diff --git a/config.sh b/config.sh new file mode 100755 index 0000000..93bfbc1 --- /dev/null +++ b/config.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# config.sh — Vul dit in voordat je de scripts draait. +# Wordt gesourcet door alle andere scripts: source "$(dirname "$0")/config.sh" + +# ── Bronserver (bestaande Nextcloud-installatie) ────────────────────────────── +SOURCE_HOST="192.0.2.10" # IP van de oude NC-server +SOURCE_USER="root" # SSH-gebruiker (key-based, passwordless) +SOURCE_NC_PATH="/var/www/nextcloud" +SOURCE_DATA_PATH="/mnt/ncdata" +SOURCE_DB="nextcloud_db" +SOURCE_DB_USER="nextcloud_db_user" +SOURCE_DB_PASS="" # alleen nodig voor MySQL/MariaDB; leeglaten voor PostgreSQL +SOURCE_DB_TYPE="postgres" # "postgres" of "mysql" + +# ── Doelserver (nieuwe VM waar AIO komt te draaien) ─────────────────────────── +TARGET_SSH="root@10.0.0.10" # SSH naar doel (of ~/.ssh/config alias) +TARGET_DATA_PATH="/mnt/ncdata" # pad waar ncdata gemount is + +# ── Domeinen ────────────────────────────────────────────────────────────────── +NC_DOMAIN="next.example.com" # productiedomein (voor AIO + LE-cert) +NC_STAGING_DOMAIN="nc.internal" # staging-domein (HTTP-only, intern) + +# ── Staging PostgreSQL ──────────────────────────────────────────────────────── +# Wachtwoord voor de staging-DB (wordt aangemaakt in 02-build-staging.sh) +STAGING_DB="nextcloud_db" +STAGING_DB_USER="nextcloud_db_user" +STAGING_DB_PASS="changeme-strong-password" + +# ── NC upgrade-pad ──────────────────────────────────────────────────────────── +# Pas aan op basis van je bronversie. NC vereist sequentiële upgrades per major. +# Eindversie moet overeenkomen met de AIO-versie die je installeert (zie stap 4). +# Voorbeeld: bron = v30, AIO target = v33 +NC_UPGRADE_VERSIONS=("31.0.14" "32.0.12" "33.0.6") + +# ── Proxmox snapshots (optioneel) ───────────────────────────────────────────── +# Zet op false als je geen Proxmox hebt of geen snapshots wil maken. +USE_PROXMOX=true +PROXMOX_HOST="pve01" # SSH-alias voor de PVE-node +PROXMOX_VMID=100 # VM ID van de doel-VM + +# ── Proxmox VM aanmaken (alleen voor proxmox/build-vm.sh) ───────────────────── +# Sla deze sectie over als je de VM handmatig aanmaakt. +VM_NAME="nc-aio" +VM_CORES=4 +VM_MEMORY=8192 # MB +VM_DISK_SIZE=50 # GB systeemdisk +VM_DATA_SIZE=500 # GB datadisk (/mnt/ncdata) +VM_VLAN=50 # VLAN-tag; leeglaten voor untagged +VM_BRIDGE=vmbr20 +VM_PRIVATE_IP=10.0.0.10 # moet overeenkomen met TARGET_SSH +VM_PRIVATE_GW=10.0.0.1 +VM_PRIVATE_PREFIX=24 +VM_PUBLIC_IP="" # publiek VIP op loopback; leeglaten indien niet van toepassing +VM_DNS=10.0.0.1 +VM_SEARCH_DOMAIN=example.com +CLOUD_IMAGE="/var/lib/vz/template/iso/debian-13-genericcloud-amd64.qcow2" diff --git a/proxmox/build-vm.sh b/proxmox/build-vm.sh new file mode 100755 index 0000000..4f6e990 --- /dev/null +++ b/proxmox/build-vm.sh @@ -0,0 +1,210 @@ +#!/usr/bin/env bash +# proxmox/build-vm.sh — Maakt de doel-VM aan op een Proxmox-node. +# +# Optioneel script: sla dit over als je de VM handmatig aanmaakt of een +# andere hypervisor gebruikt. De overige scripts (01–05) werken op elke +# Linux-VM met SSH-toegang. +# +# Wat het doet: +# - VM aanmaken (Debian 13 cloud image, qcow2) +# - Systeemdisk + aparte datadisk (LVM, ext4, /mnt/ncdata) +# - Netplan vervangen door /etc/network/interfaces +# - Loopback VIP (publiek IP) via systemd oneshot (indien VM_PUBLIC_IP gezet) +# - OPNSense: gateway + static route voor het publieke VIP (indien VM_PUBLIC_IP gezet) +# +# Vereisten: +# - SSH-alias voor PROXMOX_HOST en (indien VIP) fw01 werken +# - Debian 13 genericcloud qcow2 beschikbaar op de PVE-node (zie CLOUD_IMAGE) +# - config.sh ingevuld + +set -euo pipefail +source "$(dirname "$0")/../config.sh" +log() { echo "[$(date '+%H:%M:%S')] $*"; } +SCRATCHDIR=$(mktemp -d) +trap 'rm -rf "$SCRATCHDIR"' EXIT + +# ── STAP 1: VM aanmaken ─────────────────────────────────────────────────────── +log "VM ${PROXMOX_VMID} (${VM_NAME}) aanmaken op ${PROXMOX_HOST}..." +ssh "${PROXMOX_HOST}" " + qm create ${PROXMOX_VMID} \ + --name ${VM_NAME} \ + --memory ${VM_MEMORY} \ + --cores ${VM_CORES} \ + --sockets 1 \ + --cpu host \ + --net0 virtio,bridge=${VM_BRIDGE}$([ -n '${VM_VLAN}' ] && echo ',tag=${VM_VLAN}' || true) \ + --scsihw virtio-scsi-single \ + --ostype l26 \ + --serial0 socket \ + --vga serial0 \ + --agent enabled=1 \ + --onboot 1 +" + +log "Systeemdisk importeren als qcow2..." +ssh "${PROXMOX_HOST}" \ + "qm importdisk ${PROXMOX_VMID} ${CLOUD_IMAGE} local --format qcow2 2>&1 | tail -1" + +PUBKEY_PVE=$(ssh "${PROXMOX_HOST}" \ + "cat /root/.ssh/id_ed25519.pub 2>/dev/null || cat /root/.ssh/id_rsa.pub") +PUBKEY_WS=$(cat ~/.ssh/id_ed25519.pub 2>/dev/null || cat ~/.ssh/id_rsa.pub) + +log "Disks koppelen + cloud-init configureren..." +ssh "${PROXMOX_HOST}" " + qm set ${PROXMOX_VMID} \ + --scsi0 local:${PROXMOX_VMID}/vm-${PROXMOX_VMID}-disk-0.qcow2,discard=on,ssd=1,iothread=1 \ + --scsi1 local:${VM_DATA_SIZE},format=qcow2,discard=on,iothread=1 \ + --boot order=scsi0 \ + --ide2 local:cloudinit \ + --ciuser root \ + --sshkeys <(printf '%s\n%s\n' '${PUBKEY_PVE}' '${PUBKEY_WS}') \ + --ipconfig0 ip=${VM_PRIVATE_IP}/${VM_PRIVATE_PREFIX},gw=${VM_PRIVATE_GW} \ + --nameserver ${VM_DNS} \ + --searchdomain ${VM_SEARCH_DOMAIN} + qm resize ${PROXMOX_VMID} scsi0 ${VM_DISK_SIZE}G +" + +# ── STAP 2: VM starten en wachten op SSH ───────────────────────────────────── +log "VM starten..." +ssh "${PROXMOX_HOST}" "qm start ${PROXMOX_VMID}" +log "Wachten op SSH (${VM_PRIVATE_IP})..." +for i in $(seq 1 60); do + ssh "${PROXMOX_HOST}" \ + "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=4 \ + root@${VM_PRIVATE_IP} hostname 2>/dev/null" && break || true + sleep 5 +done + +# ── STAP 3: Netplan → ifupdown ─────────────────────────────────────────────── +log "Netplan vervangen door ifupdown..." +ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} bash -s" << EOF +set -e +apt-get install -y ifupdown 2>&1 | tail -1 +cat > /etc/network/interfaces << 'IFEOF' +source /etc/network/interfaces.d/* + +auto lo +iface lo inet loopback + +auto ens18 +iface ens18 inet static + address ${VM_PRIVATE_IP}/${VM_PRIVATE_PREFIX} + gateway ${VM_PRIVATE_GW} + dns-nameservers ${VM_DNS} 1.1.1.1 + dns-search ${VM_SEARCH_DOMAIN} +IFEOF +systemctl disable systemd-networkd systemd-networkd-wait-online 2>/dev/null || true +mkdir -p /etc/cloud/cloud.cfg.d +echo "network: {config: disabled}" > /etc/cloud/cloud.cfg.d/99-disable-network.cfg +apt-get purge -y netplan.io 2>&1 | tail -1 +rm -rf /etc/netplan /usr/share/netplan +EOF + +# ── STAP 4: Loopback VIP (optioneel) ───────────────────────────────────────── +if [[ -n "${VM_PUBLIC_IP}" ]]; then + log "Loopback VIP ${VM_PUBLIC_IP} instellen..." + ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} bash -s" << EOF +set -e +cat > /etc/systemd/system/loopback-vip.service << 'VIPEOF' +[Unit] +Description=Loopback VIP +After=network.target + +[Service] +Type=oneshot +ExecStart=/sbin/ip addr add ${VM_PUBLIC_IP}/32 dev lo label lo:vip +ExecStop=/sbin/ip addr del ${VM_PUBLIC_IP}/32 dev lo label lo:vip +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +VIPEOF +systemctl enable loopback-vip.service +EOF +fi + +# ── STAP 5: Datadisk — LVM + ext4 ─────────────────────────────────────────── +log "Datadisk inrichten (LVM vg_data/ncdata, ext4, /mnt/ncdata)..." +ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} bash -s" << 'EOF' +set -e +apt-get install -y lvm2 2>&1 | tail -1 +DISK=/dev/sdb +[ -b "$DISK" ] || { echo "FOUT: $DISK niet gevonden"; exit 1; } +[ -z "$(lsblk -dn -o FSTYPE $DISK 2>/dev/null)" ] || { echo "FOUT: $DISK is niet leeg"; exit 1; } +pvcreate "$DISK" +vgcreate vg_data "$DISK" +lvcreate -n ncdata -l 100%FREE vg_data +mkfs.ext4 -L ncdata /dev/vg_data/ncdata +mkdir -p /mnt/ncdata +echo "/dev/vg_data/ncdata /mnt/ncdata ext4 defaults,noatime 0 2" >> /etc/fstab +mount -a +df -h /mnt/ncdata +EOF + +# ── STAP 6: Basis-pakketten + reboot ───────────────────────────────────────── +log "Basis-pakketten installeren..." +ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} bash -s" << 'EOF' +set -e +apt-get update -qq +apt-get install -y qemu-guest-agent rsync curl ca-certificates gnupg htop 2>&1 | tail -1 +systemctl enable --now qemu-guest-agent +EOF + +log "Reboot..." +ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} reboot" || true +sleep 10 +log "Wachten op herstart..." +for i in $(seq 1 60); do + ssh "${PROXMOX_HOST}" \ + "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=4 \ + root@${VM_PRIVATE_IP} hostname 2>/dev/null" && break || true + sleep 5 +done + +# ── STAP 7: OPNSense gateway + static route (optioneel) ────────────────────── +if [[ -n "${VM_PUBLIC_IP}" ]]; then + log "OPNSense: gateway + static route voor ${VM_PUBLIC_IP}..." + GW_NAME="GW_$(echo ${VM_NAME} | tr '[:lower:]' '[:upper:]')" + cat > "${SCRATCHDIR}/add_route.py" << PYEOF +import xml.etree.ElementTree as ET, uuid, sys +tree = ET.parse('/conf/config.xml') +root = tree.getroot() +name, gw_ip, pub_ip, iface = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] + +gws = root.find('OPNsense/Gateways') +for old in gws.findall('gateway_item'): + if old.findtext('name') == name: + gws.remove(old) +gw = ET.SubElement(gws, 'gateway_item') +gw.set('uuid', str(uuid.uuid4())) +for tag, val in [ + ('disabled','0'),('name',name),('descr',name), + ('interface',iface),('ipprotocol','inet'),('gateway',gw_ip), + ('defaultgw','0'),('fargw','0'),('monitor_disable','1'), + ('force_down','0'),('nosync','0'),('priority','255'),('weight','1') +]: + ET.SubElement(gw, tag).text = val + +sr = root.find('staticroutes') +for old in sr.findall('route'): + if old.findtext('network') == pub_ip: + sr.remove(old) +route = ET.SubElement(sr, 'route') +route.set('uuid', str(uuid.uuid4())) +for tag, val in [('network',pub_ip),('gateway',name),('descr',name),('enabled','1')]: + ET.SubElement(route, tag).text = val +tree.write('/conf/config.xml') +print('done') +PYEOF + # fw01 root shell is csh — altijd wrappen in sh -c, anders verminkt csh + # fd-redirects (2>/dev/null) en kan een bogus 0.0.0.0/1 route aanmaken. + scp "${SCRATCHDIR}/add_route.py" fw01:/tmp/add_route.py + ssh fw01 "sh -c 'python3 /tmp/add_route.py \ + ${GW_NAME} ${VM_PRIVATE_IP} ${VM_PUBLIC_IP}/32 opt5 \ + && rm /tmp/add_route.py'" + ssh fw01 "sh -c 'configctl filter reload'" + log "✓ OPNSense route actief." +fi + +log "✓ VM klaar: ${VM_PRIVATE_IP}$([ -n '${VM_PUBLIC_IP}' ] && echo " / VIP ${VM_PUBLIC_IP}" || true)" +log " Pas TARGET_SSH aan in config.sh en ga verder met 01-transfer.sh"