CluPilotCloud/app/Support/NextcloudOcc.php

111 lines
4.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;
}
/**
* A guest shell command inside the SAME container, for anything that is not
* `occ` — FailedLoginReader's `stat`/`tail` on Nextcloud's own log file, for
* instance. Same account, same "one place" rule as command() above: nothing
* else in app/ may spell `docker compose exec` out by hand, and the test
* next to that rule only exempts this file.
*/
public static function exec(string $arguments): string
{
return 'cd '.self::DIRECTORY.' && docker compose exec -T -u '.self::USER.' app '.$arguments;
}
/**
* Wie command(), aber der Wert wird erst von der Shell IM Container
* eingesetzt.
*
* command() taugt fuer `--password-from-env`, wo occ selbst die
* Umgebungsvariable liest. `config:system:set` kann das nicht: es will den
* Wert als Argument. Setzte die aeussere Shell ihn ein, stuende das
* Passwort in der Prozessliste der KUNDEN-VM — dort, wo jeder mit einer
* Shell auf der Maschine `ps` ausfuehren kann. So steht es nur in der des
* Containers.
*
* EHRLICHERWEISE ist das Hygiene, kein Schutz: Nextcloud legt
* `mail_smtppassword` anschliessend im Klartext in config/config.php ab.
* Wer auf der Maschine eine Shell hat, liest es dort. Die eigentliche
* Eingrenzung liegt woanders — das Versandkonto kann nur senden, und der
* Versandport nimmt nur die eigenen Hostadressen an. Diese Methode senkt
* die Gelegenheit, sie beseitigt sie nicht.
*
* @param array<string, string> $env
*/
public static function commandExpandingEnv(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 sh -c '.escapeshellarg('php occ '.$arguments);
}
}