121 lines
5.2 KiB
PHP
121 lines
5.2 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.
|
|
*
|
|
* Ohne diese Methode staende der Wert an drei Stellen: im Argv der
|
|
* VM-Shell (wo HttpProxmoxClient::guestExec() die ganze Zeichenkette als
|
|
* ['/bin/sh', '-c', $command] uebergibt), im Argv des docker-Aufrufs, und
|
|
* im Argv von `php occ` im Container. Mit dieser Methode reist der Wert
|
|
* als Umgebungsvariable in den Container und wird erst dort von der `sh`
|
|
* eingesetzt — der Gewinn ist, dass er nicht im Prozessabbild DES
|
|
* CONTAINERS steht, wo jeder Nextcloud-Prozess und jeder, der sich
|
|
* hineinhängt, ihn aus der Umgebung auslesen kann.
|
|
*
|
|
* Der Wert bleibt im Argv der VM-Shell sichtbar, fuer die Dauer des
|
|
* Aufrufs: Debians `dash` (die Standard-Shell in den Kunden-VMs)
|
|
* optimiert den Elternprozess nicht weg, wie `bash` es täte.
|
|
*
|
|
* 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);
|
|
}
|
|
}
|