From e6d12ab481a3bda65d3b6c513d8d7c80e4f4b8f6 Mon Sep 17 00:00:00 2001 From: Matt Hill <9935159+MattDHill@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:16:01 -0600 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20verify=20the=20cluster=20before=20tr?= =?UTF-8?q?eating=20the=200.3.5x=20relocation=20as=20done;=2033.0.6:2=20?= =?UTF-8?q?=E2=86=92=2033.0.6:3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Postgres entrypoint runs `mkdir -p "$PGDATA"` in docker_create_db_directories *before* docker_verify_minimum_env decides the database is uninitialized and exits 1. So a Postgres start against a volume whose cluster has not yet been relocated leaves an empty `data/` behind — and both setupMain and the init upgrade start Postgres. Both guards in this migration tested for that directory rather than for a database: - relocatePostgres: `test -d $PGDATA` -> skip the move - isNeverStarted: stat() on the three candidate paths An empty `data/` therefore reads as "already relocated". The migration skips the move, runs the config import and the permissions walk, and deletes start9/config.yaml on its way out — the marker gating the whole block. From that point it is disarmed: it completes in ~3 seconds doing nothing, the cluster is never moved, and every Postgres start finds an empty PGDATA, recreates it and exits 1. Fixes: - findCluster() locates the cluster by PG_VERSION across data, 17/main and 15/main, and returns null when there isn't one. Replaces both directory-existence guards. - The migration throws before touching anything when no cluster is found, so it cannot delete the marker on a run that would migrate nothing. The two failure cases are now distinguishable: an instance with app data is told to restore from a backup and explicitly not to uninstall; only a genuinely never-started instance gets the uninstall-and-reinstall advice, which for a populated instance was wrong. - The move rmdir's the empty PGDATA shell first. rmdir, not rm -rf: it refuses a non-empty directory, so an unexpected state fails loudly rather than being deleted. Without it, `mv src $PGDATA` moves the source *inside* the existing directory (data/docker) rather than into place. - Same PG_VERSION test and rmdir applied to the 0.4.0-beta relocation (17/docker -> data), which carried the identical latent bug. Also reports progress during the permissions walk. The SDK hands every migration a FullProgressTracker for exactly this and this one destructured only `effects`. The walk corrects permissions on every file under data/ one directory at a time (a single recursive chmod OOMs in the migration subcontainer), so its cost scales with file count and on a large instance it runs for hours with the update UI showing a bar that never moves. Now reports a running count of directories processed, on the same 100-directory cadence as the log line. No total: establishing one costs a second full metadata walk, about as expensive as the work itself. And untangles the naming, which made the above hard to reason about — both run during init and the names were near-synonyms for different systems: migrateNextcloud -> repairPermissionsFrom035x (chmods, doesn't migrate) relocatePostgres -> relocatePostgresFrom035x migrateConfig -> importConfigFrom035x upgradeNextcloud -> runUpstreamUpgrade The 0.3.5x body moves out of current.ts into versions/from035x.ts, leaving current.ts as version + release notes + a one-line call (388 -> 106 lines). Each file's header names the other and states the ordering: the StartOS layout migration is driven by the version graph, the upstream app upgrade by the bundled Nextcloud release, and versionGraph precedes bootstrapNextcloud in sdk.setupInit. --- startos/i18n/dictionaries/default.ts | 3 + startos/i18n/dictionaries/translations.ts | 4 + startos/init/bootstrapNextcloud.ts | 12 +- startos/utils.ts | 2 +- startos/versions/current.ts | 272 +--------------- startos/versions/from035x.ts | 374 ++++++++++++++++++++++ 6 files changed, 406 insertions(+), 261 deletions(-) create mode 100644 startos/versions/from035x.ts diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts index d0216e9..c9e55c3 100644 --- a/startos/i18n/dictionaries/default.ts +++ b/startos/i18n/dictionaries/default.ts @@ -192,6 +192,9 @@ const dict = { // bootstrapNextcloud.ts: init progress phases 'Installing Nextcloud': 132, 'Upgrading Nextcloud': 133, + + // versions/current.ts: 0.3.5x migration progress + 'Updating file permissions': 134, } as const /** diff --git a/startos/i18n/dictionaries/translations.ts b/startos/i18n/dictionaries/translations.ts index 2d2a7b8..3578940 100644 --- a/startos/i18n/dictionaries/translations.ts +++ b/startos/i18n/dictionaries/translations.ts @@ -136,6 +136,7 @@ export default { 131: 'Usuarios', 132: 'Instalando Nextcloud', 133: 'Actualizando Nextcloud', + 134: 'Actualizando permisos de archivos', }, de_DE: { 0: 'Starte Nextcloud...', @@ -272,6 +273,7 @@ export default { 131: 'Benutzer', 132: 'Nextcloud wird installiert', 133: 'Nextcloud wird aktualisiert', + 134: 'Dateiberechtigungen werden aktualisiert', }, pl_PL: { 0: 'Uruchamianie Nextcloud...', @@ -408,6 +410,7 @@ export default { 131: 'Użytkownicy', 132: 'Instalowanie Nextcloud', 133: 'Aktualizowanie Nextcloud', + 134: 'Aktualizowanie uprawnień plików', }, fr_FR: { 0: 'Démarrage de Nextcloud...', @@ -544,5 +547,6 @@ export default { 131: 'Utilisateurs', 132: 'Installation de Nextcloud', 133: 'Mise à niveau de Nextcloud', + 134: 'Mise à jour des permissions des fichiers', }, } satisfies Record diff --git a/startos/init/bootstrapNextcloud.ts b/startos/init/bootstrapNextcloud.ts index fad88d3..30433c0 100644 --- a/startos/init/bootstrapNextcloud.ts +++ b/startos/init/bootstrapNextcloud.ts @@ -96,7 +96,7 @@ export const bootstrapNextcloud = sdk.setupOnInit( ), }) } else if (kind === 'update') { - await upgradeNextcloud(effects, progress) + await runUpstreamUpgrade(effects, progress) } }, ) @@ -108,13 +108,21 @@ export const bootstrapNextcloud = sdk.setupOnInit( * daemon start via the entrypoint, where an interrupted run stranded the * instance on "Update needed — use the command line updater". * + * This is the **upstream application** upgrade, triggered by the bundled + * Nextcloud release being newer than the deployed one — not to be confused with + * the one-time **StartOS layout** migration in + * [`../versions/from035x.ts`](../versions/from035x.ts), which is driven by the + * package version graph. Both run during init; `versionGraph` precedes + * `bootstrapNextcloud` in `sdk.setupInit`, so the 0.3.5x migration has always + * finished before this starts. + * * `NEXTCLOUD_UPDATE=1` makes the stock entrypoint perform the upgrade with a * no-op command (`true`) and exit, so it never binds a port. `runUntilSuccess` * brings up Postgres + Valkey (occ upgrade talks to both), runs the upgrade to * completion, then tears everything down. On failure or timeout it throws, * which fails init and triggers the snapshot rollback. */ -async function upgradeNextcloud(effects: T.Effects, progress: InitProgress) { +async function runUpstreamUpgrade(effects: T.Effects, progress: InitProgress) { // Read the installed (on-volume) and image Nextcloud versions first. // version.php on the volume is still the installed version — the entrypoint // syncs new code only once the upgrade runs. diff --git a/startos/utils.ts b/startos/utils.ts index 7e604de..ed26913 100644 --- a/startos/utils.ts +++ b/startos/utils.ts @@ -5,7 +5,7 @@ export const uiPort = 80 as const export const NEXTCLOUD_PATH = '/var/www/html' as const export const POSTGRES_PATH = '/var/lib/postgresql' as const -const NEXTCLOUD_VOLUME_HOST = '/media/startos/volumes/nextcloud' as const +export const NEXTCLOUD_VOLUME_HOST = '/media/startos/volumes/nextcloud' as const /** * Throws `errorMessage` if a Nextcloud app's files are not present on the diff --git a/startos/versions/current.ts b/startos/versions/current.ts index 548add3..12cd7b3 100644 --- a/startos/versions/current.ts +++ b/startos/versions/current.ts @@ -1,208 +1,8 @@ -import { IMPOSSIBLE, T, VersionInfo, YAML } from '@start9labs/start-sdk' -import { readFile, rm, stat } from 'fs/promises' -import { cp } from 'node:fs/promises' -import { resetAdmin } from '../actions/maintenance/resetAdmin' -import { configPhp } from '../fileModels/config.php' -import { storeJson } from '../fileModels/store.json' -import { i18n } from '../i18n' -import { sdk } from '../sdk' -import { NEXTCLOUD_PATH, PGDATA, POSTGRES_PATH, nextcloudMount } from '../utils' - -const POSTGRES_VOLUME_HOST = '/media/startos/volumes/db' as const - -// True when 0.3.5x left config.yaml on the main volume but no postgres cluster -// was ever written — the user configured Nextcloud but never started it. In -// that state relocatePostgres fails on the missing source (the "mv 17/main" -// error) and migrateNextcloud fails on the empty app volume; there's nothing -// to migrate, so we surface a clear "uninstall and reinstall" message instead. -const isNeverStarted = async (): Promise => { - for (const p of [ - `${POSTGRES_VOLUME_HOST}/17/main`, - `${POSTGRES_VOLUME_HOST}/15/main`, - `${POSTGRES_VOLUME_HOST}/data`, - ]) { - if ( - await stat(p).then( - () => true, - () => false, - ) - ) - return false - } - return true -} - -const relocatePostgres = async (effects: T.Effects) => { - const pgMounts = sdk.Mounts.of().mountVolume({ - volumeId: 'db', - mountpoint: POSTGRES_PATH, - readonly: false, - subpath: null, - }) - - await sdk.SubContainer.withTemp( - effects, - { imageId: 'postgres' }, - pgMounts, - 'pg-migrate', - async (sub) => { - // Relocate PG data from 0.3.5x Debian path (17/main) to Docker path (data). - // If a previous migration attempt succeeded here but failed later, - // data/ already exists and 17/main is gone. Skip the move in that case. - const { exitCode } = await sub.exec(['test', '-d', PGDATA]) - if (exitCode !== 0) { - await sub.execFail(['mv', `${POSTGRES_PATH}/17/main`, PGDATA], { - user: 'root', - }) - await sub.execFail(['rm', '-rf', `${POSTGRES_PATH}/17`], { - user: 'root', - }) - } - await sub.execFail(['chown', '-R', 'postgres:postgres', POSTGRES_PATH], { - user: 'root', - }) - await sub.exec(['rm', '-f', `${PGDATA}/postmaster.pid`], { - user: 'postgres', - }) - }, - ) -} - -type OldConfig = { - 'default-locale': string - 'default-phone-region': string - maintenance_window_start: number -} - -const migrateConfig = async (effects: T.Effects, config: OldConfig) => { - await cp(configPhp.path, `${configPhp.path}.bak`) - - await configPhp.merge(effects, { - default_locale: config['default-locale'], - default_phone_region: config['default-phone-region'], - maintenance_window_start: config.maintenance_window_start, - 'overwrite.cli.url': undefined, - 'htaccess.RewriteBase': undefined, - }) - - const adminPassword: string | undefined = ( - await readFile( - '/media/startos/volumes/main/start9/password.dat', - 'utf-8', - ).catch(() => undefined) - )?.trim() - if (adminPassword) { - await storeJson.merge(effects, { adminPassword }) - } else { - await sdk.action.createOwnTask(effects, resetAdmin, 'critical', { - reason: i18n( - 'Admin password could not be recovered from migration. Please reset it.', - ), - }) - } -} - -const migrateNextcloud = async (effects: T.Effects) => { - await sdk.SubContainer.withTemp( - effects, - { imageId: 'nextcloud' }, - nextcloudMount, - 'upgrade-sub', - async (sub) => { - // Fix permissions on Nextcloud app files (everything except data/). - // In 0.3.5.1, the upstream Docker entrypoint set group=root. In 0.4.0, - // the group is www-data. We need ug+rw so the owner and group can - // read/write, and o-rwx so other users (including dependent services - // not in the www-data group) cannot access app internals. - // The data/ directory is excluded here and handled separately below. - await sub.execFail( - [ - 'find', - NEXTCLOUD_PATH, - '-path', - `${NEXTCLOUD_PATH}/data`, - '-prune', - '-o', - '-exec', - 'chmod', - 'ug+rw,o-rwx', - '{}', - '+', - ], - { user: 'root' }, - ) - // occ must be executable for Nextcloud CLI operations - await sub.execFail(['chmod', 'u+x', `${NEXTCLOUD_PATH}/occ`], { - user: 'root', - }) - - // Fix permissions on user data files (data/). - // - // The data directory can be enormous (2TB+), so we cannot use a single - // recursive find or chmod -R — both accumulate inode metadata for the - // entire tree in memory and get OOM-killed (SIGKILL) in the - // memory-constrained migration subcontainer. - // - // Strategy: walk the directory tree from TypeScript, processing one - // directory at a time. For each directory: - // 1. find -maxdepth 1 -print0 | xargs -0 -n 5000 chmod ... - // Streams the immediate children through xargs in batches of 5000, - // so neither find nor chmod ever holds more than one directory's - // listing in memory. - // 2. find -maxdepth 1 -mindepth 1 -type d -print0 - // Lists only the immediate subdirectories so we can recurse into - // them one at a time. Uses -print0 / split('\0') to handle - // filenames with spaces or special characters. - // - // This keeps peak memory proportional to the largest single directory, - // not the total file count. - let dirCount = 0 - const chmodDir = async (dir: string) => { - dirCount++ - if (dirCount % 100 === 0) { - console.info( - `chmod migration: processed ${dirCount} directories, current: ${dir}`, - ) - } - await sub.execFail( - [ - 'sh', - '-c', - `find "$1" -maxdepth 1 -print0 | xargs -0 -n 5000 chmod ug+rw,o-rwx`, - '_', - dir, - ], - { user: 'root' }, - ) - const { stdout } = await sub.execFail( - [ - 'find', - dir, - '-maxdepth', - '1', - '-mindepth', - '1', - '-type', - 'd', - '-print0', - ], - { user: 'root' }, - ) - const subdirs = stdout - .toString() - .split('\0') - .filter((s) => s.length > 0) - for (const subdir of subdirs) { - await chmodDir(subdir) - } - } - await chmodDir(`${NEXTCLOUD_PATH}/data`) - }, - ) -} +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' +import { migrateFrom035x } from './from035x' export const current = VersionInfo.of({ - version: '33.0.6:2', + version: '33.0.6:3', releaseNotes: { en_US: `Adds File Browser External Storage integration and repackages Nextcloud on start-sdk 2.0 (bundled image updated to Nextcloud 33.0.6 — upstream security and bug fixes). @@ -217,6 +17,8 @@ export const current = VersionInfo.of({ **Fixes** - Fixed a bug where background network changes on the server could put Nextcloud into a restart loop. +- Fixed a bug in the StartOS 0.3.5.x migration that could skip relocating the PostgreSQL database while still reporting success — leaving Nextcloud unable to start, and the migration unable to run again. It now verifies the database before changing anything, and stops with a clear explanation if it cannot find one. +- The update now reports progress while migrating an instance from StartOS 0.3.5.x. On a large instance that step walks every file to correct its permissions and can run for hours; it previously showed no movement at all, which looked like a hung update. Internal updates (start-sdk 2.0). @@ -234,6 +36,8 @@ Full changelog: https://github.com/nextcloud-releases/server/releases/tag/v33.0. **Correcciones** - Corregido un error por el que cambios de red en segundo plano en el servidor podían poner Nextcloud en un bucle de reinicios. +- Corregido un error en la migración desde StartOS 0.3.5.x que podía omitir el traslado de la base de datos PostgreSQL informando aun así de que había funcionado, dejando Nextcloud sin poder arrancar y la migración sin poder volver a ejecutarse. Ahora se verifica la base de datos antes de modificar nada y se detiene con una explicación clara si no la encuentra. +- La actualización ahora informa del progreso al migrar una instancia desde StartOS 0.3.5.x. En una instancia grande, ese paso recorre todos los archivos para corregir sus permisos y puede tardar horas; antes no mostraba ningún avance, lo que parecía una actualización bloqueada. Actualizaciones internas (start-sdk 2.0). @@ -251,6 +55,8 @@ Registro de cambios completo: https://github.com/nextcloud-releases/server/relea **Fehlerkorrekturen** - Ein Fehler wurde behoben, durch den Netzwerkänderungen im Hintergrund auf dem Server Nextcloud in eine Neustart-Schleife versetzen konnten. +- Ein Fehler in der Migration von StartOS 0.3.5.x wurde behoben, durch den das Verschieben der PostgreSQL-Datenbank übersprungen werden konnte, während trotzdem Erfolg gemeldet wurde — sodass Nextcloud nicht mehr starten konnte und die Migration nicht erneut lief. Sie prüft die Datenbank jetzt, bevor etwas geändert wird, und bricht mit einer klaren Erklärung ab, wenn keine gefunden wird. +- Die Aktualisierung meldet jetzt den Fortschritt, während eine Instanz von StartOS 0.3.5.x migriert wird. Bei einer großen Instanz durchläuft dieser Schritt jede Datei, um ihre Berechtigungen zu korrigieren, und kann Stunden dauern; zuvor war überhaupt kein Fortschritt sichtbar, was wie eine hängende Aktualisierung wirkte. Interne Aktualisierungen (start-sdk 2.0). @@ -268,6 +74,8 @@ Vollständige Änderungsliste: https://github.com/nextcloud-releases/server/rele **Poprawki** - Naprawiono błąd, przez który zmiany sieci w tle na serwerze mogły wprowadzić Nextcloud w pętlę restartów. +- Naprawiono błąd w migracji ze StartOS 0.3.5.x, który mógł pominąć przeniesienie bazy danych PostgreSQL, mimo to zgłaszając powodzenie — przez co Nextcloud nie mógł się uruchomić, a migracja nie mogła zostać powtórzona. Teraz baza danych jest weryfikowana przed jakąkolwiek zmianą, a w razie jej braku migracja zatrzymuje się z jasnym wyjaśnieniem. +- Aktualizacja pokazuje teraz postęp podczas migracji instancji ze StartOS 0.3.5.x. W dużej instancji ten krok przechodzi przez każdy plik, aby poprawić jego uprawnienia, i może trwać godzinami; wcześniej nie pokazywał żadnego postępu, co wyglądało jak zawieszona aktualizacja. Aktualizacje wewnętrzne (start-sdk 2.0). @@ -285,67 +93,15 @@ Pełny dziennik zmian: https://github.com/nextcloud-releases/server/releases/tag **Correctifs** - Correction d'un bogue où des changements réseau en arrière-plan sur le serveur pouvaient placer Nextcloud dans une boucle de redémarrages. +- Correction d'un bogue dans la migration depuis StartOS 0.3.5.x qui pouvait ignorer le déplacement de la base de données PostgreSQL tout en signalant une réussite — laissant Nextcloud incapable de démarrer et la migration incapable de s'exécuter à nouveau. Elle vérifie désormais la base de données avant toute modification et s'arrête avec une explication claire si elle n'en trouve pas. +- La mise à jour indique désormais la progression lors de la migration d'une instance depuis StartOS 0.3.5.x. Sur une grande instance, cette étape parcourt chaque fichier pour corriger ses permissions et peut durer des heures ; auparavant elle n'affichait aucune progression, ce qui ressemblait à une mise à jour bloquée. Mises à jour internes (start-sdk 2.0). Journal des modifications complet : https://github.com/nextcloud-releases/server/releases/tag/v33.0.6`, }, migrations: { - up: async ({ effects }) => { - const start9Path = '/media/startos/volumes/main/start9' - - // Only run 0.3.5x → 0.4.0 migration if config.yaml exists (0.3.5x marker) - const configYaml: OldConfig | undefined = await readFile( - `${start9Path}/config.yaml`, - 'utf-8', - ).then(YAML.parse, () => undefined) - - if (configYaml) { - if (await isNeverStarted()) { - throw new Error( - 'This Nextcloud package was configured on StartOS 0.3.5x but never started, so there is no data to migrate to 0.4.0. Please uninstall the Nextcloud package and reinstall it to set up a fresh 0.4.0 install.', - ) - } - await relocatePostgres(effects) - await migrateConfig(effects, configYaml) - await migrateNextcloud(effects) - await rm(start9Path, { recursive: true }) - // Remove stale config.php keys from 0.3.5.1 - await configPhp.merge(effects, { - 'overwrite.cli.url': undefined, - 'htaccess.RewriteBase': undefined, - }) - } - - // Previous 0.4.0 beta: relocate PGDATA (17/docker → data) - const OLD_PGDATA_HOST = '/media/startos/volumes/db/17/docker' - const oldPgdataExists = await stat(OLD_PGDATA_HOST).then( - () => true, - () => false, - ) - if (oldPgdataExists) { - const pgMounts = sdk.Mounts.of().mountVolume({ - volumeId: 'db', - subpath: null, - mountpoint: POSTGRES_PATH, - readonly: false, - }) - await sdk.SubContainer.withTemp( - effects, - { imageId: 'postgres' }, - pgMounts, - 'pg-relocate', - async (sub) => { - await sub.execFail(['mv', `${POSTGRES_PATH}/17/docker`, PGDATA], { - user: 'root', - }) - await sub.execFail(['rm', '-rf', `${POSTGRES_PATH}/17`], { - user: 'root', - }) - }, - ) - } - }, + up: ({ effects, progress }) => migrateFrom035x(effects, progress), down: IMPOSSIBLE, }, }) diff --git a/startos/versions/from035x.ts b/startos/versions/from035x.ts new file mode 100644 index 0000000..e8b11c9 --- /dev/null +++ b/startos/versions/from035x.ts @@ -0,0 +1,374 @@ +/** + * The one-time StartOS 0.3.5x → 0.4.0 data migration. + * + * This is the **StartOS layout** migration: it converts what 0.3.5.1 left on + * disk into the layout the 0.4.0 package expects — Postgres cluster location, + * `config.yaml` → `config.php`, and file permissions. It is driven by the + * package version graph and runs at most once, on the first update away from + * `32.0.11:0`. + * + * Do not confuse it with the **upstream application** upgrade in + * [`../init/bootstrapNextcloud.ts`](../init/bootstrapNextcloud.ts), which runs + * Nextcloud's own `occ upgrade` whenever the bundled Nextcloud release is newer + * than the deployed one. The two are independent and answer to different + * triggers, but both run during init, in that order — `versionGraph` precedes + * `bootstrapNextcloud` in `sdk.setupInit`, so everything here has finished + * before the upstream upgrade starts. + */ + +import { T, YAML } from '@start9labs/start-sdk' +import { readFile, rm, stat } from 'fs/promises' +import { cp } from 'node:fs/promises' +import { resetAdmin } from '../actions/maintenance/resetAdmin' +import { configPhp } from '../fileModels/config.php' +import { storeJson } from '../fileModels/store.json' +import { i18n } from '../i18n' +import { sdk } from '../sdk' +import { + NEXTCLOUD_PATH, + NEXTCLOUD_VOLUME_HOST, + PGDATA, + POSTGRES_PATH, + nextcloudMount, +} from '../utils' + +const POSTGRES_VOLUME_HOST = '/media/startos/volumes/db' as const +const START9_PATH = '/media/startos/volumes/main/start9' as const + +/** + * Structural view of the `PhaseHandle` returned by a migration's + * `progress.addPhase` — the SDK bundles `@start9labs/start-core` as a nested + * dependency, so the type itself isn't importable from a package. + */ +type ProgressPhase = { + setUnits(units: 'steps'): void + setDone(done: number): void +} + +/** + * Progress tracker handed to `migrations.up`. Only `addPhase` is used here. + */ +type MigrationProgress = { + addPhase( + name: string, + contribution?: number | null, + ): ProgressPhase & { start(): void; complete(): void } +} + +const exists = (p: string) => + stat(p).then( + () => true, + () => false, + ) + +/** + * Where a Postgres cluster may be found, relative to the `db` volume root: + * `data` if a previous run already relocated it, `17/main` or `15/main` if it + * is still in the 0.3.5x Debian layout. + */ +const PG_LOCATIONS = ['data', '17/main', '15/main'] as const + +/** + * The location of the real cluster, or `null` if there isn't one. + * + * Identified by `PG_VERSION`, never by the directory existing. The Postgres + * entrypoint runs `mkdir -p "$PGDATA"` in `docker_create_db_directories` + * *before* it checks whether the database is initialized and bails, so every + * start against an unmigrated volume leaves an empty `data/` behind. Both + * guards here used to test for the directory, so that empty directory read as + * "already relocated": the move was skipped, the migration reported success, + * and it deleted the 0.3.5x marker on its way out — disarming itself while the + * cluster was never moved. + */ +const findCluster = async (): Promise<(typeof PG_LOCATIONS)[number] | null> => { + for (const loc of PG_LOCATIONS) { + if (await exists(`${POSTGRES_VOLUME_HOST}/${loc}/PG_VERSION`)) return loc + } + return null +} + +/** True if this instance holds Nextcloud application data worth preserving. */ +const hasNextcloudData = () => + exists(`${NEXTCLOUD_VOLUME_HOST}/config/config.php`) + +const relocatePostgresFrom035x = async ( + effects: T.Effects, + from: (typeof PG_LOCATIONS)[number], +) => { + if (from === 'data') return // already in the canonical location + + const pgMounts = sdk.Mounts.of().mountVolume({ + volumeId: 'db', + mountpoint: POSTGRES_PATH, + readonly: false, + subpath: null, + }) + + await sdk.SubContainer.withTemp( + effects, + { imageId: 'postgres' }, + pgMounts, + 'pg-migrate', + async (sub) => { + // Move the cluster from the 0.3.5x Debian layout to the canonical Docker + // path. `from` is known to hold a real cluster, and PGDATA is known not + // to — the caller established both via PG_VERSION. + // + // rmdir, not rm -rf: PGDATA here is the empty shell the Postgres + // entrypoint leaves behind, and rmdir refuses to remove a directory with + // anything in it. If some future state puts real content there, this + // fails loudly rather than deleting a database. + await sub.exec(['rmdir', PGDATA], { user: 'root' }) + await sub.execFail(['mv', `${POSTGRES_PATH}/${from}`, PGDATA], { + user: 'root', + }) + await sub.execFail( + ['rm', '-rf', `${POSTGRES_PATH}/${from.split('/')[0]}`], + { + user: 'root', + }, + ) + await sub.execFail(['chown', '-R', 'postgres:postgres', POSTGRES_PATH], { + user: 'root', + }) + await sub.exec(['rm', '-f', `${PGDATA}/postmaster.pid`], { + user: 'postgres', + }) + }, + ) +} + +type OldConfig = { + 'default-locale': string + 'default-phone-region': string + maintenance_window_start: number +} + +const importConfigFrom035x = async (effects: T.Effects, config: OldConfig) => { + await cp(configPhp.path, `${configPhp.path}.bak`) + + await configPhp.merge(effects, { + default_locale: config['default-locale'], + default_phone_region: config['default-phone-region'], + maintenance_window_start: config.maintenance_window_start, + 'overwrite.cli.url': undefined, + 'htaccess.RewriteBase': undefined, + }) + + const adminPassword: string | undefined = ( + await readFile(`${START9_PATH}/password.dat`, 'utf-8').catch( + () => undefined, + ) + )?.trim() + if (adminPassword) { + await storeJson.merge(effects, { adminPassword }) + } else { + await sdk.action.createOwnTask(effects, resetAdmin, 'critical', { + reason: i18n( + 'Admin password could not be recovered from migration. Please reset it.', + ), + }) + } +} + +const repairPermissionsFrom035x = async ( + effects: T.Effects, + phase: ProgressPhase, +) => { + await sdk.SubContainer.withTemp( + effects, + { imageId: 'nextcloud' }, + nextcloudMount, + 'upgrade-sub', + async (sub) => { + // Fix permissions on Nextcloud app files (everything except data/). + // In 0.3.5.1, the upstream Docker entrypoint set group=root. In 0.4.0, + // the group is www-data. We need ug+rw so the owner and group can + // read/write, and o-rwx so other users (including dependent services + // not in the www-data group) cannot access app internals. + // The data/ directory is excluded here and handled separately below. + await sub.execFail( + [ + 'find', + NEXTCLOUD_PATH, + '-path', + `${NEXTCLOUD_PATH}/data`, + '-prune', + '-o', + '-exec', + 'chmod', + 'ug+rw,o-rwx', + '{}', + '+', + ], + { user: 'root' }, + ) + // occ must be executable for Nextcloud CLI operations + await sub.execFail(['chmod', 'u+x', `${NEXTCLOUD_PATH}/occ`], { + user: 'root', + }) + + // Fix permissions on user data files (data/). + // + // The data directory can be enormous (2TB+), so we cannot use a single + // recursive find or chmod -R — both accumulate inode metadata for the + // entire tree in memory and get OOM-killed (SIGKILL) in the + // memory-constrained migration subcontainer. + // + // Strategy: walk the directory tree from TypeScript, processing one + // directory at a time. For each directory: + // 1. find -maxdepth 1 -print0 | xargs -0 -n 5000 chmod ... + // Streams the immediate children through xargs in batches of 5000, + // so neither find nor chmod ever holds more than one directory's + // listing in memory. + // 2. find -maxdepth 1 -mindepth 1 -type d -print0 + // Lists only the immediate subdirectories so we can recurse into + // them one at a time. Uses -print0 / split('\0') to handle + // filenames with spaces or special characters. + // + // This keeps peak memory proportional to the largest single directory, + // not the total file count. + // + // Progress is a bare count of directories processed, with no total: + // establishing a total means a second full metadata walk of the tree, + // which on a multi-terabyte instance costs about as much as the work + // itself. A count that keeps climbing answers the question the user + // actually has — is this alive — without paying for a percentage. + // Reported on the same 100-directory cadence as the log line, since every + // update pushes to the OS and this loop runs many times a second. + let dirCount = 0 + phase.setUnits('steps') + const chmodDir = async (dir: string) => { + dirCount++ + if (dirCount % 100 === 0) { + console.info( + `chmod migration: processed ${dirCount} directories, current: ${dir}`, + ) + phase.setDone(dirCount) + } + await sub.execFail( + [ + 'sh', + '-c', + `find "$1" -maxdepth 1 -print0 | xargs -0 -n 5000 chmod ug+rw,o-rwx`, + '_', + dir, + ], + { user: 'root' }, + ) + const { stdout } = await sub.execFail( + [ + 'find', + dir, + '-maxdepth', + '1', + '-mindepth', + '1', + '-type', + 'd', + '-print0', + ], + { user: 'root' }, + ) + const subdirs = stdout + .toString() + .split('\0') + .filter((s) => s.length > 0) + for (const subdir of subdirs) { + await chmodDir(subdir) + } + } + await chmodDir(`${NEXTCLOUD_PATH}/data`) + phase.setDone(dirCount) + }, + ) +} + +/** + * Relocate PGDATA from a previous 0.4.0 beta's path (`17/docker` → `data`). + * Independent of the 0.3.5x work above — a beta tester has no `config.yaml`. + */ +const relocatePostgresFromBeta = async (effects: T.Effects) => { + // Same PG_VERSION test as findCluster, and for the same reason: keying off + // the directory would both miss the real cluster and let `mv` run against an + // existing PGDATA, which moves the source *inside* it (data/docker) rather + // than into place. + if (!(await exists(`${POSTGRES_VOLUME_HOST}/17/docker/PG_VERSION`))) return + if (await exists(`${POSTGRES_VOLUME_HOST}/data/PG_VERSION`)) return + + const pgMounts = sdk.Mounts.of().mountVolume({ + volumeId: 'db', + subpath: null, + mountpoint: POSTGRES_PATH, + readonly: false, + }) + await sdk.SubContainer.withTemp( + effects, + { imageId: 'postgres' }, + pgMounts, + 'pg-relocate', + async (sub) => { + await sub.exec(['rmdir', PGDATA], { user: 'root' }) + await sub.execFail(['mv', `${POSTGRES_PATH}/17/docker`, PGDATA], { + user: 'root', + }) + await sub.execFail(['rm', '-rf', `${POSTGRES_PATH}/17`], { + user: 'root', + }) + }, + ) +} + +/** + * The migration body for `current`'s `migrations.up`. A no-op on an instance + * that never ran on 0.3.5x, apart from the beta PGDATA relocation. + */ +export const migrateFrom035x = async ( + effects: T.Effects, + progress: MigrationProgress, +) => { + // config.yaml on the main volume is the 0.3.5x marker. + const configYaml: OldConfig | undefined = await readFile( + `${START9_PATH}/config.yaml`, + 'utf-8', + ).then(YAML.parse, () => undefined) + + if (configYaml) { + // Refuse to go any further without a real cluster to migrate. Everything + // below this point is destructive-by-omission: it rewrites config, walks + // the whole data tree, and finally deletes the 0.3.5x marker, after which + // this migration can never run again. Completing any of that without + // having moved a database is how an instance ends up permanently + // un-migratable. + const cluster = await findCluster() + if (!cluster) { + throw new Error( + (await hasNextcloudData()) + ? 'Nextcloud could not find its PostgreSQL database. Your files are still on disk and are not affected, but without the database Nextcloud cannot start, and the update cannot continue. Restore this service from a StartOS backup. Do NOT uninstall the package — that would delete your files as well.' + : 'This Nextcloud package was configured on StartOS 0.3.5x but never started, so there is no data to migrate to 0.4.0. Please uninstall the Nextcloud package and reinstall it to set up a fresh 0.4.0 install.', + ) + } + await relocatePostgresFrom035x(effects, cluster) + await importConfigFrom035x(effects, configYaml) + // Weighted far above the steps around it: on a large instance this walk + // runs for hours while everything else here takes seconds. Without a phase + // the update UI sat on an unmoving bar for the whole run, which reads as a + // hang — and users cancelled, which is how an instance ends up + // half-migrated. + const permissions = progress.addPhase( + i18n('Updating file permissions'), + 100, + ) + permissions.start() + await repairPermissionsFrom035x(effects, permissions) + permissions.complete() + await rm(START9_PATH, { recursive: true }) + // Remove stale config.php keys from 0.3.5.1 + await configPhp.merge(effects, { + 'overwrite.cli.url': undefined, + 'htaccess.RewriteBase': undefined, + }) + } + + await relocatePostgresFromBeta(effects) +} From 4fe67fa07838e347371195680ed0daf275abb4d8 Mon Sep 17 00:00:00 2001 From: Matt Hill <9935159+MattDHill@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:56:14 -0600 Subject: [PATCH 2/4] fix: clear a stale postmaster.pid before starting postgres MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hardening. No confirmed occurrence — kept because the mechanism is real, and in its own commit so it can be dropped independently of the preceding one. PostgreSQL writes postmaster.pid into PGDATA while running and removes it on a clean exit. SIGTERM to postgres is a *smart* shutdown — it waits for clients to disconnect — and the SDK escalates to SIGKILL after 60s, so a stop that outruns that budget leaves the file behind. On the next start postgres reads the PID it names and aborts with FATAL: lock file "postmaster.pid" already exists if that PID is alive. Each chain build runs in a fresh PID namespace with a fresh, low PID assignment, so the recorded PID can be live and owned by an unrelated process. getBaseDaemons is the only place in the stack that starts postgres without this prelude. The SDK's Backups.withPgDump does it before every pg_ctl start (lib/backup/Backups.ts), and the 0.3.5x relocation does it too — the daemon path was simply missed. Removal is unconditional, which is safe because nothing else can hold the data directory: backups only run once the service is stopped, both init paths run with the service stopped, and the chain reconciler fully terms an entry before starting its replacement. Runs as root so ownership can never block the unlink and wedge the chain on the new oneshot. Placing it in getBaseDaemons covers setupMain, install init and update init in one place. It does not re-run if postgres crash-restarts within a single container lifetime, but that case self-heals — same PID namespace, so postgres correctly identifies its own stale file. --- README.md | 4 +++- startos/utils.ts | 14 +++++++++++++- startos/versions/current.ts | 5 +++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0cdba3b..82f8e48 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,9 @@ This package runs **four containers** as subcontainers: Architectures: x86_64, aarch64. -**Startup order:** A `chown` one-shot runs first alongside `postgres` and `valkey`. The `nextcloud` container waits until all three are ready before starting. The `cron` container waits for `nextcloud` to be ready. Version upgrades run earlier, during init (see [Installation and First-Run Flow](#installation-and-first-run-flow)); after `nextcloud` is ready, a `finish-upgrade` one-shot completes any interrupted upstream upgrade as a fallback (see [Health Checks](#health-checks)), and the `long-running-tasks` one-shot runs after it. +**Startup order:** Two one-shots run first — `chown` and `pg-recover`. `postgres` waits on `pg-recover`; `valkey` has no prerequisites. The `nextcloud` container waits until `chown`, `postgres` and `valkey` are all ready before starting. The `cron` container waits for `nextcloud` to be ready. Version upgrades run earlier, during init (see [Installation and First-Run Flow](#installation-and-first-run-flow)); after `nextcloud` is ready, a `finish-upgrade` one-shot completes any interrupted upstream upgrade as a fallback (see [Health Checks](#health-checks)), and the `long-running-tasks` one-shot runs after it. + +**Unclean shutdown recovery (`pg-recover`):** PostgreSQL writes `postmaster.pid` into its data directory while running and removes it on a clean exit. A stop that does not complete — a power loss, a forced stop, or an update that fails and rolls back — can leave that file behind. On the next start PostgreSQL reads the PID it names and aborts with `FATAL: lock file "postmaster.pid" already exists` if that PID is alive. Each chain build runs in a fresh PID namespace with a fresh, low PID assignment, so the recorded PID is quite likely to be live and to belong to some unrelated process — the guard misfires. The daemon then crash-loops indefinitely, since `pg_isready` reports `loading` rather than a failure. The `pg-recover` one-shot removes the stale file before `postgres` starts, so the database proceeds to normal WAL crash recovery instead. Removal is unconditional and safe because nothing else can hold the data directory: init and backups both run with the service stopped, and the chain reconciler terminates a daemon before starting its replacement. The SDK's own `Backups.withPgDump` does the same before each `pg_ctl start`. **ffmpeg:** The nextcloud image is built locally (extends `nextcloud:-apache`) to install `ffmpeg`, which Nextcloud's preview providers shell out to for video thumbnails. diff --git a/startos/utils.ts b/startos/utils.ts index ed26913..7bc2789 100644 --- a/startos/utils.ts +++ b/startos/utils.ts @@ -138,6 +138,18 @@ export function getBaseDaemons( }, requires: [], }) + .addOneshot('pg-recover', { + subcontainer: postgresSub, + exec: { + // An unclean stop strands postmaster.pid, and Postgres aborts if the + // PID it names is alive — which, in a fresh PID namespace, is usually + // an unrelated process. As root, so ownership can never block the + // removal and wedge the chain on this oneshot. + command: ['rm', '-f', `${PGDATA}/postmaster.pid`], + user: 'root', + }, + requires: [], + }) .addDaemon('postgres', { subcontainer: postgresSub, exec: { @@ -167,7 +179,7 @@ export function getBaseDaemons( } }, }, - requires: [], + requires: ['pg-recover'], }) .addDaemon('valkey', { subcontainer: valkeySub, diff --git a/startos/versions/current.ts b/startos/versions/current.ts index 12cd7b3..934f394 100644 --- a/startos/versions/current.ts +++ b/startos/versions/current.ts @@ -19,6 +19,7 @@ export const current = VersionInfo.of({ - Fixed a bug where background network changes on the server could put Nextcloud into a restart loop. - Fixed a bug in the StartOS 0.3.5.x migration that could skip relocating the PostgreSQL database while still reporting success — leaving Nextcloud unable to start, and the migration unable to run again. It now verifies the database before changing anything, and stops with a clear explanation if it cannot find one. - The update now reports progress while migrating an instance from StartOS 0.3.5.x. On a large instance that step walks every file to correct its permissions and can run for hours; it previously showed no movement at all, which looked like a hung update. +- Fixed a bug where PostgreSQL could refuse to start after an unclean shutdown — a power loss, a forced stop, or a failed update — leaving Nextcloud stuck on "starting" or an update failing with a timeout. A stale database lock file is now cleared before PostgreSQL starts. Internal updates (start-sdk 2.0). @@ -38,6 +39,7 @@ Full changelog: https://github.com/nextcloud-releases/server/releases/tag/v33.0. - Corregido un error por el que cambios de red en segundo plano en el servidor podían poner Nextcloud en un bucle de reinicios. - Corregido un error en la migración desde StartOS 0.3.5.x que podía omitir el traslado de la base de datos PostgreSQL informando aun así de que había funcionado, dejando Nextcloud sin poder arrancar y la migración sin poder volver a ejecutarse. Ahora se verifica la base de datos antes de modificar nada y se detiene con una explicación clara si no la encuentra. - La actualización ahora informa del progreso al migrar una instancia desde StartOS 0.3.5.x. En una instancia grande, ese paso recorre todos los archivos para corregir sus permisos y puede tardar horas; antes no mostraba ningún avance, lo que parecía una actualización bloqueada. +- Corregido un error por el que PostgreSQL podía negarse a arrancar tras un apagado no limpio —un corte de energía, una parada forzada o una actualización fallida—, dejando Nextcloud atascado en «iniciando» o provocando que una actualización fallara por tiempo de espera agotado. Ahora se elimina el archivo de bloqueo obsoleto de la base de datos antes de iniciar PostgreSQL. Actualizaciones internas (start-sdk 2.0). @@ -57,6 +59,7 @@ Registro de cambios completo: https://github.com/nextcloud-releases/server/relea - Ein Fehler wurde behoben, durch den Netzwerkänderungen im Hintergrund auf dem Server Nextcloud in eine Neustart-Schleife versetzen konnten. - Ein Fehler in der Migration von StartOS 0.3.5.x wurde behoben, durch den das Verschieben der PostgreSQL-Datenbank übersprungen werden konnte, während trotzdem Erfolg gemeldet wurde — sodass Nextcloud nicht mehr starten konnte und die Migration nicht erneut lief. Sie prüft die Datenbank jetzt, bevor etwas geändert wird, und bricht mit einer klaren Erklärung ab, wenn keine gefunden wird. - Die Aktualisierung meldet jetzt den Fortschritt, während eine Instanz von StartOS 0.3.5.x migriert wird. Bei einer großen Instanz durchläuft dieser Schritt jede Datei, um ihre Berechtigungen zu korrigieren, und kann Stunden dauern; zuvor war überhaupt kein Fortschritt sichtbar, was wie eine hängende Aktualisierung wirkte. +- Ein Fehler wurde behoben, durch den PostgreSQL nach einem unsauberen Herunterfahren — einem Stromausfall, einem erzwungenen Stopp oder einer fehlgeschlagenen Aktualisierung — den Start verweigern konnte, sodass Nextcloud im Zustand „wird gestartet" hängen blieb oder eine Aktualisierung mit einer Zeitüberschreitung fehlschlug. Eine veraltete Sperrdatei der Datenbank wird jetzt vor dem Start von PostgreSQL entfernt. Interne Aktualisierungen (start-sdk 2.0). @@ -76,6 +79,7 @@ Vollständige Änderungsliste: https://github.com/nextcloud-releases/server/rele - Naprawiono błąd, przez który zmiany sieci w tle na serwerze mogły wprowadzić Nextcloud w pętlę restartów. - Naprawiono błąd w migracji ze StartOS 0.3.5.x, który mógł pominąć przeniesienie bazy danych PostgreSQL, mimo to zgłaszając powodzenie — przez co Nextcloud nie mógł się uruchomić, a migracja nie mogła zostać powtórzona. Teraz baza danych jest weryfikowana przed jakąkolwiek zmianą, a w razie jej braku migracja zatrzymuje się z jasnym wyjaśnieniem. - Aktualizacja pokazuje teraz postęp podczas migracji instancji ze StartOS 0.3.5.x. W dużej instancji ten krok przechodzi przez każdy plik, aby poprawić jego uprawnienia, i może trwać godzinami; wcześniej nie pokazywał żadnego postępu, co wyglądało jak zawieszona aktualizacja. +- Naprawiono błąd, przez który PostgreSQL mógł odmówić uruchomienia po nieczystym zamknięciu — awarii zasilania, wymuszonym zatrzymaniu lub nieudanej aktualizacji — pozostawiając Nextcloud w stanie „uruchamianie" lub powodując niepowodzenie aktualizacji z powodu przekroczenia limitu czasu. Nieaktualny plik blokady bazy danych jest teraz usuwany przed uruchomieniem PostgreSQL. Aktualizacje wewnętrzne (start-sdk 2.0). @@ -95,6 +99,7 @@ Pełny dziennik zmian: https://github.com/nextcloud-releases/server/releases/tag - Correction d'un bogue où des changements réseau en arrière-plan sur le serveur pouvaient placer Nextcloud dans une boucle de redémarrages. - Correction d'un bogue dans la migration depuis StartOS 0.3.5.x qui pouvait ignorer le déplacement de la base de données PostgreSQL tout en signalant une réussite — laissant Nextcloud incapable de démarrer et la migration incapable de s'exécuter à nouveau. Elle vérifie désormais la base de données avant toute modification et s'arrête avec une explication claire si elle n'en trouve pas. - La mise à jour indique désormais la progression lors de la migration d'une instance depuis StartOS 0.3.5.x. Sur une grande instance, cette étape parcourt chaque fichier pour corriger ses permissions et peut durer des heures ; auparavant elle n'affichait aucune progression, ce qui ressemblait à une mise à jour bloquée. +- Correction d'un bogue où PostgreSQL pouvait refuser de démarrer après un arrêt brutal — une coupure de courant, un arrêt forcé ou une mise à jour échouée —, laissant Nextcloud bloqué sur « démarrage » ou faisant échouer une mise à jour par dépassement de délai. Un fichier de verrou de base de données obsolète est désormais supprimé avant le démarrage de PostgreSQL. Mises à jour internes (start-sdk 2.0). From 33fd8b22dbd9fceb7ee771851094c0b71739f2b0 Mon Sep 17 00:00:00 2001 From: Helix <267227783+helix-nine@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:27:13 +0000 Subject: [PATCH 3/4] fix: refuse the 0.3.5x relocation unless the cluster is a completed PG 17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `findCluster` accepted a location on the mere existence of `PG_VERSION`, and `PG_LOCATIONS` includes `15/main`. 0.3.5x ran its own `pg_upgrade` 15 -> 17 in copy mode and reaped `15/main` on the *next* launch, gated on `.pg17_upgrade_complete`, so a surviving `15/main` means one of two things: - marker present: the upgrade finished, `17/main` is authoritative and `15/main` is a fallback that was never reaped. Migrating is correct. - marker absent: the upgrade never finished. `15/main` holds the real data and any `17/main` beside it is the empty initdb stub — which `findCluster` prefers, since `17/main` sorts first. Both were relocated regardless, completing the migration and deleting the 0.3.5x marker over a cluster PostgreSQL 17 cannot open. Measured on a 0.4.0 VM: `15/main` alone crash-looped `database files are incompatible with server` with the migration gone. master failed safe here — its `mv 17/main` threw — so this was a regression. Now `clusterVersion` reads `PG_VERSION` rather than stat()ing it, the marker decides whether the 15 -> 17 upgrade completed, and a non-17 cluster is refused as a backstop. The beta relocation uses the same read, so its comment claiming parity with `findCluster` is true again. `rmdir $PGDATA` used non-throwing `exec` with the result discarded, so the comment promising it "fails loudly rather than deleting a database" described a branch that did not exist: with a non-empty `data/`, `rmdir` failed silently and `mv` nested the cluster at `data/main`, reporting success. `execFail` alone is wrong — on the happy path `$PGDATA` is absent and `rmdir` legitimately fails — so `clearPgdataShell` checks the postcondition instead. Also: the file header claimed the migration "runs at most once, on the first update away from 32.0.11:0", but the auto-generated `< current` vertex runs `up` on every update into current; the i18n comment for key 134 pointed at `versions/current.ts` after this branch moved the call site; and AGENTS.md named subcontainers that do not exist (`nextcloud`, `cron`, `postgres` for `nextcloud-sub`, `nextcloud-cron`, `postgres-sub`). --- AGENTS.md | 2 +- README.md | 2 +- UPDATING.md | 2 +- startos/i18n/dictionaries/default.ts | 2 +- startos/versions/current.ts | 10 ++-- startos/versions/from035x.ts | 90 +++++++++++++++++++++------- 6 files changed, 76 insertions(+), 32 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 280d94c..7154680 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,4 +15,4 @@ Work this package's `TODO.md` from top to bottom. Keep `README.md` (architecture ## Inspecting a running install -To run a command inside the service's container (read its generated config, grep app logs), use `start-cli package attach nextcloud -n -- `. This package has several subcontainers (`nextcloud`, `cron`, `postgres`, `valkey`), so a selector is **required** — select by **name** with `-n` (the name passed to `SubContainer.of` in `main.ts`, e.g. `-n nextcloud`) or by image with `-i`. Note: `-s/--subcontainer` matches the internal **Guid**, not the name. +To run a command inside the service's container (read its generated config, grep app logs), use `start-cli package attach nextcloud -n -- `. This package has several subcontainers (`nextcloud-sub`, `nextcloud-cron`, `postgres-sub`, `valkey`), so a selector is **required** — select by **name** with `-n` (the name passed to `SubContainer.of`, e.g. `-n nextcloud-sub`) or by image with `-i`. Note the two nextcloud subcontainers share one image, so `-i nextcloud` is still ambiguous. Note: `-s/--subcontainer` matches the internal **Guid**, not the name. diff --git a/README.md b/README.md index 82f8e48..ad487b9 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Valkey runs without a mounted volume — its cache is ephemeral and rebuilds on **Nextcloud version upgrades:** When the package is updated to a newer Nextcloud release, the upstream upgrade (sync new code → `occ upgrade` → app bookkeeping) runs during init in `setupOnInit`'s `update` branch, before the service starts. It invokes the stock image's entrypoint in headless `NEXTCLOUD_UPDATE=1` mode with a no-op command, alongside temporary `postgres` and `valkey` daemons, via `runUntilSuccess`. Because init runs inside StartOS's update snapshot, a failed upgrade rolls back cleanly instead of stranding the instance. Nextcloud only supports upgrading one major version at a time; a larger jump is detected up front and rejected with a clear error before any change is made. The `finish-upgrade` one-shot (see [Health Checks](#health-checks)) remains as a fallback for an upgrade triggered by restoring an older backup. -**Upgrade from StartOS 0.3.x:** The migration handles PostgreSQL data directory relocation (Debian path to Docker canonical path), `config.yaml` to `config.php` migration, and admin password migration to the new store format. Users must have run the previous Nextcloud version on 0.3.5x at least once (to complete the PG 15 to 17 upgrade) before upgrading. +**Upgrade from StartOS 0.3.x:** The migration handles PostgreSQL data directory relocation (Debian path to Docker canonical path), `config.yaml` to `config.php` migration, and admin password migration to the new store format. Users must have run the previous Nextcloud version on 0.3.5x at least once (to complete the PG 15 to 17 upgrade) before upgrading. The migration locates the cluster by `PG_VERSION` and refuses to start work — leaving the volume untouched and the migration able to run again — if there is no cluster, if that PG 15 to 17 upgrade did not complete (`15/main` present without 0.3.5x's `.pg17_upgrade_complete` marker), if the cluster is not PostgreSQL 17, or if the destination directory is not empty. --- diff --git a/UPDATING.md b/UPDATING.md index 1d158af..dcc9048 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -22,7 +22,7 @@ curl -fsSL "https://hub.docker.com/v2/repositories/library/postgres/tags?page_si | jq -r '.results[].name' | grep -E '^17.*alpine' | head ``` -Current pin: `postgres:17-alpine` in `startos/manifest/index.ts` (`images.postgres.source.dockerTag`). +Current pin: `postgres:17-alpine` in `startos/manifest/index.ts` (`images.postgres.source.dockerTag`). Changing the major also means changing `PG_MAJOR` in `startos/versions/from035x.ts`, which the 0.3.5x migration compares against the cluster's `PG_VERSION`. **Valkey** ([valkey/valkey](https://hub.docker.com/r/valkey/valkey) on Docker Hub): diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts index c9e55c3..d0feba9 100644 --- a/startos/i18n/dictionaries/default.ts +++ b/startos/i18n/dictionaries/default.ts @@ -193,7 +193,7 @@ const dict = { 'Installing Nextcloud': 132, 'Upgrading Nextcloud': 133, - // versions/current.ts: 0.3.5x migration progress + // versions/from035x.ts: 0.3.5x migration progress 'Updating file permissions': 134, } as const diff --git a/startos/versions/current.ts b/startos/versions/current.ts index 934f394..e9ca020 100644 --- a/startos/versions/current.ts +++ b/startos/versions/current.ts @@ -17,7 +17,7 @@ export const current = VersionInfo.of({ **Fixes** - Fixed a bug where background network changes on the server could put Nextcloud into a restart loop. -- Fixed a bug in the StartOS 0.3.5.x migration that could skip relocating the PostgreSQL database while still reporting success — leaving Nextcloud unable to start, and the migration unable to run again. It now verifies the database before changing anything, and stops with a clear explanation if it cannot find one. +- Fixed a bug in the StartOS 0.3.5.x migration that could skip relocating the PostgreSQL database while still reporting success — leaving Nextcloud unable to start, and the migration unable to run again. It now verifies the database before changing anything, and stops with a clear explanation if it cannot safely continue. - The update now reports progress while migrating an instance from StartOS 0.3.5.x. On a large instance that step walks every file to correct its permissions and can run for hours; it previously showed no movement at all, which looked like a hung update. - Fixed a bug where PostgreSQL could refuse to start after an unclean shutdown — a power loss, a forced stop, or a failed update — leaving Nextcloud stuck on "starting" or an update failing with a timeout. A stale database lock file is now cleared before PostgreSQL starts. @@ -37,7 +37,7 @@ Full changelog: https://github.com/nextcloud-releases/server/releases/tag/v33.0. **Correcciones** - Corregido un error por el que cambios de red en segundo plano en el servidor podían poner Nextcloud en un bucle de reinicios. -- Corregido un error en la migración desde StartOS 0.3.5.x que podía omitir el traslado de la base de datos PostgreSQL informando aun así de que había funcionado, dejando Nextcloud sin poder arrancar y la migración sin poder volver a ejecutarse. Ahora se verifica la base de datos antes de modificar nada y se detiene con una explicación clara si no la encuentra. +- Corregido un error en la migración desde StartOS 0.3.5.x que podía omitir el traslado de la base de datos PostgreSQL informando aun así de que había funcionado, dejando Nextcloud sin poder arrancar y la migración sin poder volver a ejecutarse. Ahora se verifica la base de datos antes de modificar nada y se detiene con una explicación clara si no puede continuar de forma segura. - La actualización ahora informa del progreso al migrar una instancia desde StartOS 0.3.5.x. En una instancia grande, ese paso recorre todos los archivos para corregir sus permisos y puede tardar horas; antes no mostraba ningún avance, lo que parecía una actualización bloqueada. - Corregido un error por el que PostgreSQL podía negarse a arrancar tras un apagado no limpio —un corte de energía, una parada forzada o una actualización fallida—, dejando Nextcloud atascado en «iniciando» o provocando que una actualización fallara por tiempo de espera agotado. Ahora se elimina el archivo de bloqueo obsoleto de la base de datos antes de iniciar PostgreSQL. @@ -57,7 +57,7 @@ Registro de cambios completo: https://github.com/nextcloud-releases/server/relea **Fehlerkorrekturen** - Ein Fehler wurde behoben, durch den Netzwerkänderungen im Hintergrund auf dem Server Nextcloud in eine Neustart-Schleife versetzen konnten. -- Ein Fehler in der Migration von StartOS 0.3.5.x wurde behoben, durch den das Verschieben der PostgreSQL-Datenbank übersprungen werden konnte, während trotzdem Erfolg gemeldet wurde — sodass Nextcloud nicht mehr starten konnte und die Migration nicht erneut lief. Sie prüft die Datenbank jetzt, bevor etwas geändert wird, und bricht mit einer klaren Erklärung ab, wenn keine gefunden wird. +- Ein Fehler in der Migration von StartOS 0.3.5.x wurde behoben, durch den das Verschieben der PostgreSQL-Datenbank übersprungen werden konnte, während trotzdem Erfolg gemeldet wurde — sodass Nextcloud nicht mehr starten konnte und die Migration nicht erneut lief. Sie prüft die Datenbank jetzt, bevor etwas geändert wird, und bricht mit einer klaren Erklärung ab, wenn sie nicht sicher fortfahren kann. - Die Aktualisierung meldet jetzt den Fortschritt, während eine Instanz von StartOS 0.3.5.x migriert wird. Bei einer großen Instanz durchläuft dieser Schritt jede Datei, um ihre Berechtigungen zu korrigieren, und kann Stunden dauern; zuvor war überhaupt kein Fortschritt sichtbar, was wie eine hängende Aktualisierung wirkte. - Ein Fehler wurde behoben, durch den PostgreSQL nach einem unsauberen Herunterfahren — einem Stromausfall, einem erzwungenen Stopp oder einer fehlgeschlagenen Aktualisierung — den Start verweigern konnte, sodass Nextcloud im Zustand „wird gestartet" hängen blieb oder eine Aktualisierung mit einer Zeitüberschreitung fehlschlug. Eine veraltete Sperrdatei der Datenbank wird jetzt vor dem Start von PostgreSQL entfernt. @@ -77,7 +77,7 @@ Vollständige Änderungsliste: https://github.com/nextcloud-releases/server/rele **Poprawki** - Naprawiono błąd, przez który zmiany sieci w tle na serwerze mogły wprowadzić Nextcloud w pętlę restartów. -- Naprawiono błąd w migracji ze StartOS 0.3.5.x, który mógł pominąć przeniesienie bazy danych PostgreSQL, mimo to zgłaszając powodzenie — przez co Nextcloud nie mógł się uruchomić, a migracja nie mogła zostać powtórzona. Teraz baza danych jest weryfikowana przed jakąkolwiek zmianą, a w razie jej braku migracja zatrzymuje się z jasnym wyjaśnieniem. +- Naprawiono błąd w migracji ze StartOS 0.3.5.x, który mógł pominąć przeniesienie bazy danych PostgreSQL, mimo to zgłaszając powodzenie — przez co Nextcloud nie mógł się uruchomić, a migracja nie mogła zostać powtórzona. Teraz baza danych jest weryfikowana przed jakąkolwiek zmianą, a jeśli migracja nie może bezpiecznie kontynuować, zatrzymuje się z jasnym wyjaśnieniem. - Aktualizacja pokazuje teraz postęp podczas migracji instancji ze StartOS 0.3.5.x. W dużej instancji ten krok przechodzi przez każdy plik, aby poprawić jego uprawnienia, i może trwać godzinami; wcześniej nie pokazywał żadnego postępu, co wyglądało jak zawieszona aktualizacja. - Naprawiono błąd, przez który PostgreSQL mógł odmówić uruchomienia po nieczystym zamknięciu — awarii zasilania, wymuszonym zatrzymaniu lub nieudanej aktualizacji — pozostawiając Nextcloud w stanie „uruchamianie" lub powodując niepowodzenie aktualizacji z powodu przekroczenia limitu czasu. Nieaktualny plik blokady bazy danych jest teraz usuwany przed uruchomieniem PostgreSQL. @@ -97,7 +97,7 @@ Pełny dziennik zmian: https://github.com/nextcloud-releases/server/releases/tag **Correctifs** - Correction d'un bogue où des changements réseau en arrière-plan sur le serveur pouvaient placer Nextcloud dans une boucle de redémarrages. -- Correction d'un bogue dans la migration depuis StartOS 0.3.5.x qui pouvait ignorer le déplacement de la base de données PostgreSQL tout en signalant une réussite — laissant Nextcloud incapable de démarrer et la migration incapable de s'exécuter à nouveau. Elle vérifie désormais la base de données avant toute modification et s'arrête avec une explication claire si elle n'en trouve pas. +- Correction d'un bogue dans la migration depuis StartOS 0.3.5.x qui pouvait ignorer le déplacement de la base de données PostgreSQL tout en signalant une réussite — laissant Nextcloud incapable de démarrer et la migration incapable de s'exécuter à nouveau. Elle vérifie désormais la base de données avant toute modification et s'arrête avec une explication claire si elle ne peut pas continuer en toute sécurité. - La mise à jour indique désormais la progression lors de la migration d'une instance depuis StartOS 0.3.5.x. Sur une grande instance, cette étape parcourt chaque fichier pour corriger ses permissions et peut durer des heures ; auparavant elle n'affichait aucune progression, ce qui ressemblait à une mise à jour bloquée. - Correction d'un bogue où PostgreSQL pouvait refuser de démarrer après un arrêt brutal — une coupure de courant, un arrêt forcé ou une mise à jour échouée —, laissant Nextcloud bloqué sur « démarrage » ou faisant échouer une mise à jour par dépassement de délai. Un fichier de verrou de base de données obsolète est désormais supprimé avant le démarrage de PostgreSQL. diff --git a/startos/versions/from035x.ts b/startos/versions/from035x.ts index e8b11c9..c26af5d 100644 --- a/startos/versions/from035x.ts +++ b/startos/versions/from035x.ts @@ -3,9 +3,10 @@ * * This is the **StartOS layout** migration: it converts what 0.3.5.1 left on * disk into the layout the 0.4.0 package expects — Postgres cluster location, - * `config.yaml` → `config.php`, and file permissions. It is driven by the - * package version graph and runs at most once, on the first update away from - * `32.0.11:0`. + * `config.yaml` → `config.php`, and file permissions. The version graph invokes + * `migrations.up` on every update into the current version, from any older one; + * the `config.yaml` marker is what makes this a no-op on an instance that never + * ran on 0.3.5x. * * Do not confuse it with the **upstream application** upgrade in * [`../init/bootstrapNextcloud.ts`](../init/bootstrapNextcloud.ts), which runs @@ -16,13 +17,14 @@ * before the upstream upgrade starts. */ -import { T, YAML } from '@start9labs/start-sdk' +import { SubContainer, T, YAML } from '@start9labs/start-sdk' import { readFile, rm, stat } from 'fs/promises' import { cp } from 'node:fs/promises' import { resetAdmin } from '../actions/maintenance/resetAdmin' import { configPhp } from '../fileModels/config.php' import { storeJson } from '../fileModels/store.json' import { i18n } from '../i18n' +import { manifest } from '../manifest' import { sdk } from '../sdk' import { NEXTCLOUD_PATH, @@ -61,6 +63,21 @@ const exists = (p: string) => () => false, ) +const PGDATA_NOT_EMPTY = + 'Nextcloud cannot move its PostgreSQL database into place because the destination directory already holds files. Nothing has been changed and your data is still on disk. Please contact Start9 support. Do NOT uninstall the package — that would delete your files as well.' + +/** + * Remove the empty `PGDATA` shell the Postgres entrypoint leaves behind, so the + * `mv` that follows moves the cluster into place rather than inside it. Absent + * is the normal case; holding anything is not. `exec` does not throw and the + * two outcomes are indistinguishable from its exit code, so check directly. + */ +const clearPgdataShell = async (sub: SubContainer) => { + await sub.exec(['rmdir', PGDATA], { user: 'root' }) + if (await exists(`${POSTGRES_VOLUME_HOST}/data`)) + throw new Error(PGDATA_NOT_EMPTY) +} + /** * Where a Postgres cluster may be found, relative to the `db` volume root: * `data` if a previous run already relocated it, `17/main` or `15/main` if it @@ -68,21 +85,36 @@ const exists = (p: string) => */ const PG_LOCATIONS = ['data', '17/main', '15/main'] as const +/** Major version of the postgres image; see `PG_MAJOR` in UPDATING.md. */ +const PG_MAJOR = '17' as const + +/** 0.3.5x's own `pg_upgrade` 15 → 17 touched this only once it succeeded. */ +const PG_UPGRADE_MARKER = `${POSTGRES_VOLUME_HOST}/.pg17_upgrade_complete` + +type Cluster = { at: (typeof PG_LOCATIONS)[number]; major: string } + +/** Major version that wrote the cluster at `dir`, or '' if there isn't one. */ +const clusterVersion = (dir: string) => + readFile(`${dir}/PG_VERSION`, 'utf-8').then( + (v) => v.trim(), + () => '', + ) + /** - * The location of the real cluster, or `null` if there isn't one. + * The real cluster and the major version that wrote it, or `null` if there + * isn't one. * * Identified by `PG_VERSION`, never by the directory existing. The Postgres * entrypoint runs `mkdir -p "$PGDATA"` in `docker_create_db_directories` * *before* it checks whether the database is initialized and bails, so every - * start against an unmigrated volume leaves an empty `data/` behind. Both - * guards here used to test for the directory, so that empty directory read as - * "already relocated": the move was skipped, the migration reported success, - * and it deleted the 0.3.5x marker on its way out — disarming itself while the - * cluster was never moved. + * start against an unmigrated volume leaves an empty `data/` behind, which used + * to read as "already relocated" — the move was skipped, the migration reported + * success, and it deleted the 0.3.5x marker on its way out. */ -const findCluster = async (): Promise<(typeof PG_LOCATIONS)[number] | null> => { - for (const loc of PG_LOCATIONS) { - if (await exists(`${POSTGRES_VOLUME_HOST}/${loc}/PG_VERSION`)) return loc +const findCluster = async (): Promise => { + for (const at of PG_LOCATIONS) { + const major = await clusterVersion(`${POSTGRES_VOLUME_HOST}/${at}`) + if (major) return { at, major } } return null } @@ -113,12 +145,7 @@ const relocatePostgresFrom035x = async ( // Move the cluster from the 0.3.5x Debian layout to the canonical Docker // path. `from` is known to hold a real cluster, and PGDATA is known not // to — the caller established both via PG_VERSION. - // - // rmdir, not rm -rf: PGDATA here is the empty shell the Postgres - // entrypoint leaves behind, and rmdir refuses to remove a directory with - // anything in it. If some future state puts real content there, this - // fails loudly rather than deleting a database. - await sub.exec(['rmdir', PGDATA], { user: 'root' }) + await clearPgdataShell(sub) await sub.execFail(['mv', `${POSTGRES_PATH}/${from}`, PGDATA], { user: 'root', }) @@ -293,8 +320,8 @@ const relocatePostgresFromBeta = async (effects: T.Effects) => { // the directory would both miss the real cluster and let `mv` run against an // existing PGDATA, which moves the source *inside* it (data/docker) rather // than into place. - if (!(await exists(`${POSTGRES_VOLUME_HOST}/17/docker/PG_VERSION`))) return - if (await exists(`${POSTGRES_VOLUME_HOST}/data/PG_VERSION`)) return + if (!(await clusterVersion(`${POSTGRES_VOLUME_HOST}/17/docker`))) return + if (await clusterVersion(`${POSTGRES_VOLUME_HOST}/data`)) return const pgMounts = sdk.Mounts.of().mountVolume({ volumeId: 'db', @@ -308,7 +335,7 @@ const relocatePostgresFromBeta = async (effects: T.Effects) => { pgMounts, 'pg-relocate', async (sub) => { - await sub.exec(['rmdir', PGDATA], { user: 'root' }) + await clearPgdataShell(sub) await sub.execFail(['mv', `${POSTGRES_PATH}/17/docker`, PGDATA], { user: 'root', }) @@ -348,7 +375,24 @@ export const migrateFrom035x = async ( : 'This Nextcloud package was configured on StartOS 0.3.5x but never started, so there is no data to migrate to 0.4.0. Please uninstall the Nextcloud package and reinstall it to set up a fresh 0.4.0 install.', ) } - await relocatePostgresFrom035x(effects, cluster) + // 0.3.5x upgraded its own cluster 15 → 17 in pg_upgrade's copy mode and + // reaped 15/main on the *next* launch, gated on the marker. So 15/main + // without the marker means that upgrade never finished, and any 17/main + // beside it is the empty initdb stub it left — which findCluster prefers. + if ( + (await clusterVersion(`${POSTGRES_VOLUME_HOST}/15/main`)) && + !(await exists(PG_UPGRADE_MARKER)) + ) { + throw new Error( + `Nextcloud's PostgreSQL 15 to ${PG_MAJOR} upgrade never finished on StartOS 0.3.5.x, so its database cannot be migrated to 0.4.0. Nothing has been changed and your files and database are still on disk. Please contact Start9 support. Do NOT uninstall the package — that would delete your files as well.`, + ) + } + if (cluster.major !== PG_MAJOR) { + throw new Error( + `Nextcloud found a PostgreSQL ${cluster.major} database, but this version of Nextcloud runs PostgreSQL ${PG_MAJOR}, so it cannot be migrated. Nothing has been changed and your files and database are still on disk. Please contact Start9 support. Do NOT uninstall the package — that would delete your files as well.`, + ) + } + await relocatePostgresFrom035x(effects, cluster.at) await importConfigFrom035x(effects, configYaml) // Weighted far above the steps around it: on a large instance this walk // runs for hours while everything else here takes seconds. Without a phase From 70f647ceb4b63f2a3393050f00d57ea1bae28b51 Mon Sep 17 00:00:00 2001 From: Helix <267227783+helix-nine@users.noreply.github.com> Date: Wed, 29 Jul 2026 19:37:15 +0000 Subject: [PATCH 4/4] chore: bump Nextcloud to 33.0.7; 33.0.6:3 -> 33.0.7:0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Latest release on the 33 line, cut 2026-07-23. Maintenance only: no shipped-app changes (`core/shipped.json` is identical between v33.0.6 and v33.0.7), no PHP floor change (`lib/versioncheck.php` is byte-identical), so the preserved-defaults list in `disableUnstableApps` is unaffected. Carries security hardening (sabre/xml callable deserialization disabled, federated-share rate limiting, lost-password form throttling, user-search disclosure tightening, code-signing revocation list and CA bundle refresh), the usual sharing/DAV/external-storage fixes, and perf work on PROPFIND, trashbin deletes and the photocache. It also carries one user-visible regression, so the release notes call it out: upstream disabled the ImageMagick preview providers as a security stopgap (nextcloud/server#62148 on stable33), and `PreviewManager` gates the whole imagick block on that, so PDF, SVG, TIFF, HEIC, PSD, EPS, TTF, TGA and SGI previews are gone until 33.0.8. The re-enable merged to stable33 on 2026-07-29 as #62619 but is not tagged yet. The revision resets to :0 because the upstream version changed. This is now a real application upgrade rather than a StartOS-only revision bump, so `runUpstreamUpgrade` no longer early-returns and `occ upgrade` runs during init — on a 0.3.5x instance that happens after the layout migration in the same update. 32 -> 33 is a single major step, so the one-major-at-a-time guard in `bootstrapNextcloud` still permits it. --- nextcloud.Dockerfile | 2 +- startos/versions/current.ts | 42 +++++++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/nextcloud.Dockerfile b/nextcloud.Dockerfile index d5a43ae..93ca54e 100644 --- a/nextcloud.Dockerfile +++ b/nextcloud.Dockerfile @@ -1,4 +1,4 @@ -ARG NEXTCLOUD_VERSION=33.0.6 +ARG NEXTCLOUD_VERSION=33.0.7 FROM nextcloud:${NEXTCLOUD_VERSION}-apache RUN apt-get update \ diff --git a/startos/versions/current.ts b/startos/versions/current.ts index e9ca020..a425c3b 100644 --- a/startos/versions/current.ts +++ b/startos/versions/current.ts @@ -2,9 +2,9 @@ import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' import { migrateFrom035x } from './from035x' export const current = VersionInfo.of({ - version: '33.0.6:3', + version: '33.0.7:0', releaseNotes: { - en_US: `Adds File Browser External Storage integration and repackages Nextcloud on start-sdk 2.0 (bundled image updated to Nextcloud 33.0.6 — upstream security and bug fixes). + en_US: `Adds File Browser External Storage integration and repackages Nextcloud on start-sdk 2.0 (bundled image updated to Nextcloud 33.0.7 — upstream security and bug fixes). **External Storage** @@ -21,10 +21,14 @@ export const current = VersionInfo.of({ - The update now reports progress while migrating an instance from StartOS 0.3.5.x. On a large instance that step walks every file to correct its permissions and can run for hours; it previously showed no movement at all, which looked like a hung update. - Fixed a bug where PostgreSQL could refuse to start after an unclean shutdown — a power loss, a forced stop, or a failed update — leaving Nextcloud stuck on "starting" or an update failing with a timeout. A stale database lock file is now cleared before PostgreSQL starts. +**Known issue** + +- This Nextcloud release temporarily disables ImageMagick-based previews upstream, so thumbnails are unavailable for PDF, SVG, TIFF, HEIC, PSD and a few other formats. Photos and videos in common formats are unaffected. Upstream has already fixed this for its next release. + Internal updates (start-sdk 2.0). -Full changelog: https://github.com/nextcloud-releases/server/releases/tag/v33.0.6`, - es_ES: `Añade la integración de Almacenamiento externo de File Browser y reempaqueta Nextcloud sobre start-sdk 2.0 (imagen incluida actualizada a Nextcloud 33.0.6 — correcciones de seguridad y de errores upstream). +Full changelog: https://github.com/nextcloud-releases/server/releases/tag/v33.0.7`, + es_ES: `Añade la integración de Almacenamiento externo de File Browser y reempaqueta Nextcloud sobre start-sdk 2.0 (imagen incluida actualizada a Nextcloud 33.0.7 — correcciones de seguridad y de errores upstream). **Almacenamiento externo** @@ -41,10 +45,14 @@ Full changelog: https://github.com/nextcloud-releases/server/releases/tag/v33.0. - La actualización ahora informa del progreso al migrar una instancia desde StartOS 0.3.5.x. En una instancia grande, ese paso recorre todos los archivos para corregir sus permisos y puede tardar horas; antes no mostraba ningún avance, lo que parecía una actualización bloqueada. - Corregido un error por el que PostgreSQL podía negarse a arrancar tras un apagado no limpio —un corte de energía, una parada forzada o una actualización fallida—, dejando Nextcloud atascado en «iniciando» o provocando que una actualización fallara por tiempo de espera agotado. Ahora se elimina el archivo de bloqueo obsoleto de la base de datos antes de iniciar PostgreSQL. +**Problema conocido** + +- Esta versión de Nextcloud desactiva temporalmente las vistas previas basadas en ImageMagick, por lo que no hay miniaturas para PDF, SVG, TIFF, HEIC, PSD y algunos otros formatos. Las fotos y los vídeos en formatos habituales no se ven afectados. Upstream ya lo ha corregido para su próxima versión. + Actualizaciones internas (start-sdk 2.0). -Registro de cambios completo: https://github.com/nextcloud-releases/server/releases/tag/v33.0.6`, - de_DE: `Fügt die File-Browser-Integration „Externer Speicher" hinzu und stellt Nextcloud auf start-sdk 2.0 um (mitgeliefertes Image auf Nextcloud 33.0.6 aktualisiert — Sicherheits- und Fehlerkorrekturen im Upstream). +Registro de cambios completo: https://github.com/nextcloud-releases/server/releases/tag/v33.0.7`, + de_DE: `Fügt die File-Browser-Integration „Externer Speicher" hinzu und stellt Nextcloud auf start-sdk 2.0 um (mitgeliefertes Image auf Nextcloud 33.0.7 aktualisiert — Sicherheits- und Fehlerkorrekturen im Upstream). **Externer Speicher** @@ -61,10 +69,14 @@ Registro de cambios completo: https://github.com/nextcloud-releases/server/relea - Die Aktualisierung meldet jetzt den Fortschritt, während eine Instanz von StartOS 0.3.5.x migriert wird. Bei einer großen Instanz durchläuft dieser Schritt jede Datei, um ihre Berechtigungen zu korrigieren, und kann Stunden dauern; zuvor war überhaupt kein Fortschritt sichtbar, was wie eine hängende Aktualisierung wirkte. - Ein Fehler wurde behoben, durch den PostgreSQL nach einem unsauberen Herunterfahren — einem Stromausfall, einem erzwungenen Stopp oder einer fehlgeschlagenen Aktualisierung — den Start verweigern konnte, sodass Nextcloud im Zustand „wird gestartet" hängen blieb oder eine Aktualisierung mit einer Zeitüberschreitung fehlschlug. Eine veraltete Sperrdatei der Datenbank wird jetzt vor dem Start von PostgreSQL entfernt. +**Bekanntes Problem** + +- Diese Nextcloud-Version deaktiviert vorübergehend die auf ImageMagick basierenden Vorschauen, sodass für PDF, SVG, TIFF, HEIC, PSD und einige weitere Formate keine Miniaturansichten verfügbar sind. Fotos und Videos in gängigen Formaten sind nicht betroffen. Upstream hat dies für die nächste Version bereits behoben. + Interne Aktualisierungen (start-sdk 2.0). -Vollständige Änderungsliste: https://github.com/nextcloud-releases/server/releases/tag/v33.0.6`, - pl_PL: `Dodaje integrację Magazynu zewnętrznego z File Browser i przenosi Nextcloud na start-sdk 2.0 (dołączony obraz zaktualizowany do Nextcloud 33.0.6 — poprawki bezpieczeństwa i błędów w upstreamie). +Vollständige Änderungsliste: https://github.com/nextcloud-releases/server/releases/tag/v33.0.7`, + pl_PL: `Dodaje integrację Magazynu zewnętrznego z File Browser i przenosi Nextcloud na start-sdk 2.0 (dołączony obraz zaktualizowany do Nextcloud 33.0.7 — poprawki bezpieczeństwa i błędów w upstreamie). **Magazyn zewnętrzny** @@ -81,10 +93,14 @@ Vollständige Änderungsliste: https://github.com/nextcloud-releases/server/rele - Aktualizacja pokazuje teraz postęp podczas migracji instancji ze StartOS 0.3.5.x. W dużej instancji ten krok przechodzi przez każdy plik, aby poprawić jego uprawnienia, i może trwać godzinami; wcześniej nie pokazywał żadnego postępu, co wyglądało jak zawieszona aktualizacja. - Naprawiono błąd, przez który PostgreSQL mógł odmówić uruchomienia po nieczystym zamknięciu — awarii zasilania, wymuszonym zatrzymaniu lub nieudanej aktualizacji — pozostawiając Nextcloud w stanie „uruchamianie" lub powodując niepowodzenie aktualizacji z powodu przekroczenia limitu czasu. Nieaktualny plik blokady bazy danych jest teraz usuwany przed uruchomieniem PostgreSQL. +**Znany problem** + +- Ta wersja Nextcloud tymczasowo wyłącza podglądy oparte na ImageMagick, więc miniatury nie są dostępne dla plików PDF, SVG, TIFF, HEIC, PSD i kilku innych formatów. Zdjęcia i filmy w popularnych formatach nie są objęte tym problemem. Upstream naprawił to już w kolejnym wydaniu. + Aktualizacje wewnętrzne (start-sdk 2.0). -Pełny dziennik zmian: https://github.com/nextcloud-releases/server/releases/tag/v33.0.6`, - fr_FR: `Ajoute l'intégration Stockage externe de File Browser et repackage Nextcloud sur start-sdk 2.0 (image fournie mise à jour vers Nextcloud 33.0.6 — correctifs de sécurité et de bogues en amont). +Pełny dziennik zmian: https://github.com/nextcloud-releases/server/releases/tag/v33.0.7`, + fr_FR: `Ajoute l'intégration Stockage externe de File Browser et repackage Nextcloud sur start-sdk 2.0 (image fournie mise à jour vers Nextcloud 33.0.7 — correctifs de sécurité et de bogues en amont). **Stockage externe** @@ -101,9 +117,13 @@ Pełny dziennik zmian: https://github.com/nextcloud-releases/server/releases/tag - La mise à jour indique désormais la progression lors de la migration d'une instance depuis StartOS 0.3.5.x. Sur une grande instance, cette étape parcourt chaque fichier pour corriger ses permissions et peut durer des heures ; auparavant elle n'affichait aucune progression, ce qui ressemblait à une mise à jour bloquée. - Correction d'un bogue où PostgreSQL pouvait refuser de démarrer après un arrêt brutal — une coupure de courant, un arrêt forcé ou une mise à jour échouée —, laissant Nextcloud bloqué sur « démarrage » ou faisant échouer une mise à jour par dépassement de délai. Un fichier de verrou de base de données obsolète est désormais supprimé avant le démarrage de PostgreSQL. +**Problème connu** + +- Cette version de Nextcloud désactive temporairement les aperçus basés sur ImageMagick en amont, de sorte que les miniatures ne sont pas disponibles pour les PDF, SVG, TIFF, HEIC, PSD et quelques autres formats. Les photos et vidéos aux formats courants ne sont pas concernées. Le correctif est déjà intégré en amont pour la prochaine version. + Mises à jour internes (start-sdk 2.0). -Journal des modifications complet : https://github.com/nextcloud-releases/server/releases/tag/v33.0.6`, +Journal des modifications complet : https://github.com/nextcloud-releases/server/releases/tag/v33.0.7`, }, migrations: { up: ({ effects, progress }) => migrateFrom035x(effects, progress),