CluPilotCloud/app/Support/NextcloudOcc.php

64 lines
2.6 KiB
PHP

<?php
namespace App\Support;
/**
* The one place a guest command that runs Nextcloud's `occ` is built.
*
* `docker compose exec` runs as **root** unless it is told otherwise, and the
* official Nextcloud image will not have it: `console.php` compares the calling
* uid against the owner of `config/config.php` — www-data — and exits 1 with
* "Console has to be executed with the user that owns the file config/config.php"
* before a single command is dispatched. Every occ call in this product was
* therefore failing on every instance, and the ones that go through
* CustomerStep::guest() turned that exit code into a retry and then into a failed
* run for a machine that was otherwise finished.
*
* The same default already cost this project days on its OWN container, where
* `artisan optimize` ran as root and left storage/logs/laravel.log unwritable —
* see deploy/update.sh's in_app() and tests/Feature/DeploymentRunsAsTheAppUserTest.
* That lesson was written down for the deployment and not for the guests.
*
* It lives here, in one place, because the prefix was pasted into five files and
* a sixth call site would have got it wrong again. Nothing else in app/ may spell
* `docker compose exec` out by hand; the test above refuses it.
*/
final class NextcloudOcc
{
/** Where the compose project sits inside every guest this product builds. */
public const DIRECTORY = '/opt/nextcloud';
/**
* The account that owns the Nextcloud installation inside the image.
*
* Named rather than defaulted: the rule is that somebody chose the user, so
* a reader can tell a deliberate root from a forgotten one.
*/
public const USER = 'www-data';
/**
* A guest shell command that runs `occ <arguments>` as that account.
*
* Environment values are passed on the docker invocation itself and handed
* through with `-e`, because an assignment placed before the `cd` would not
* survive the `&&` — this is how OC_PASS reaches `user:add
* --password-from-env` without the password ever appearing in an occ
* argument (and so in the guest's process list).
*
* @param array<string, string> $env
*/
public static function command(string $arguments, array $env = []): string
{
$assignments = '';
$forwards = '';
foreach ($env as $name => $value) {
$assignments .= $name.'='.escapeshellarg($value).' ';
$forwards .= '-e '.$name.' ';
}
return 'cd '.self::DIRECTORY.' && '.$assignments
.'docker compose exec -T -u '.self::USER.' '.$forwards.'app php occ '.$arguments;
}
}