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 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 01:18:01 +02:00
commit 2626036a21
9 changed files with 1311 additions and 0 deletions
+7
View File
@@ -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
Executable
+89
View File
@@ -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 <doel> '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 <doel> 'tail -f /var/log/pull-source.log'"
log " Schijfruimte: ssh <doel> 'df -h ${TARGET_DATA_PATH}'"
+215
View File
@@ -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'
<VirtualHost *:80>
ServerName _default_
DocumentRoot /var/www/nextcloud
<Directory /var/www/nextcloud>
Options +FollowSymlinks
AllowOverride All
Require all granted
<IfModule mod_dav.c>
Dav off
</IfModule>
</Directory>
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.3-fpm.nextcloud.sock|fcgi://localhost"
</FilesMatch>
ErrorLog \${APACHE_LOG_DIR}/nextcloud-error.log
CustomLog \${APACHE_LOG_DIR}/nextcloud-access.log combined
</VirtualHost>
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 = \"<?php\n\\\\\\\$CONFIG = \" . var_export(\\\$CONFIG, true) . \";\n\";
file_put_contents('/var/www/nextcloud/config/config.php', \\\$out);
echo 'config.php geschreven' . PHP_EOL;
"
chown www-data:www-data /var/www/nextcloud/config/config.php
# ── 7. Cron + verificatie ─────────────────────────────────────────────────────
log "7/7 Cron instellen + occ status controleren..."
cat > /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 <commando>"
log " Logs: /var/log/apache2/nextcloud-error.log"
} 2>&1 | tee /var/log/build-staging.log
REMOTE
log "✓ Script klaar. Voortgang: ssh <doel> 'tail -f /var/log/build-staging.log'"
Executable
+183
View File
@@ -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 <user> <pass> <db>
# zodat de DB PostgreSQL is voor AIO-import (05-import.sh).
#
# Terugzetten naar snapshot: ssh <pve> 'qm rollback <vmid> <naam>' (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 <doel> '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
+145
View File
@@ -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 "<doel-ip>")
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 "══════════════════════════════════════════════════════════════"
Executable
+154
View File
@@ -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://<doel>: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://<doel>: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 <pve> 'qm snapshot <vmid> 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
+252
View File
@@ -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://<target>: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 \
<staging_db_user> <staging_db_pass> <staging_db_name>
```
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 12 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.
Executable
+56
View File
@@ -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"
+210
View File
@@ -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 (0105) 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"