instance($run); $node = (string) $run->context('node'); $vmid = (int) $run->context('vmid'); $pve = $this->pve->forHost($instance->host); // Deploy once (guarded). The DB password is persisted ENCRYPTED before the // stack starts and reused on retry, so a crash before `stack_deployed` // can't regenerate a password that mismatches the initialized database. if (! $run->context('stack_deployed')) { $encrypted = $run->context('db_password'); $dbPassword = $encrypted !== null ? Crypt::decryptString($encrypted) : Str::random(28); if ($encrypted === null) { $run->mergeContext(['db_password' => Crypt::encryptString($dbPassword)]); } $this->guest($pve, $run, 'install -d /opt/nextcloud && printf %s '.escapeshellarg('MYSQL_PASSWORD='.$dbPassword."\n"). ' > /opt/nextcloud/.env'); $this->guest($pve, $run, 'cd /opt/nextcloud && docker compose up -d'); $run->mergeContext(['stack_deployed' => true]); } // Poll until Nextcloud is INSTALLED, not merely answering (poll doesn't // consume the retry budget). // // `curl -sf` alone was the whole readiness test, and status.php answers // 200 on an instance that has never been installed — the body then reads // {"installed":false,…}. So the run marched on into `occ config:system:set` // and `occ user:add`, neither of which is even REGISTERED as a command on // an uninstalled Nextcloud, and the failure surfaced three steps later as // an unexplained non-zero exit inside the guest. The flag is the one thing // status.php says that distinguishes a web server from a Nextcloud. $health = $pve->guestExec($node, $vmid, 'curl -sf http://localhost/status.php 2>/dev/null' .' | grep -qE \'"installed"[[:space:]]*:[[:space:]]*true\' && echo ok || echo wait'); if (trim($health['out-data'] ?? '') !== 'ok') { // Its own deadline, below maxDuration, so what an operator reads is // what happened rather than 'step timed out'. Long: the first boot // pulls the images, and a slow mirror is not a fault. if ($run->started_at !== null && $run->started_at->copy()->addSeconds(self::READY_DEADLINE)->isPast()) { return StepResult::fail('nextcloud_not_installed'); } return StepResult::poll(15, 'waiting for Nextcloud to report itself installed'); } $run->forgetContext('db_password'); // scrub once the stack is up return StepResult::advance(); } }