` mechanism. Every setting reachable from * outside is a setting whose value depends on where you look, and the * blast radius of getting that wrong is the whole settings table. One * named key per setting that needs it, added when it needs it. */ class SeedSettingsCommand extends Command { protected $signature = 'projectsend:seed-settings'; protected $description = 'Apply provisioning defaults from the environment to settings that have never been set'; public function handle(Settings $settings): int { $enforcement = config('projectsend.platform.two_factor_enforcement'); if (is_string($enforcement) && $enforcement !== '') { $this->seedTwoFactorEnforcement($settings, $enforcement); } $scanner = config('projectsend.scanning.default_address'); if (is_string($scanner) && trim($scanner) !== '') { $this->seedScanner($settings, trim($scanner)); } return self::SUCCESS; } /** * Point a fresh installation at its scanner, and switch scanning on. * * For the operator who brings up the optional scanner container beside * the application: without this they would have to find the settings * screen and type an address the compose file already knows. Unlike * PROJECTSEND_SCANNER_ADDRESS this leaves both the address and the * switch editable afterwards — it is a starting value, not a policy. * * Both are seeded together or neither: an address with scanning off * would look configured and check nothing, and scanning on with no * address would hold every upload. */ private function seedScanner(Settings $settings, string $address): void { // The address, asked of the table for the reason given below: its // default is the empty string, so get() cannot tell "never set" // from "deliberately cleared". if (StoredSetting::query()->where('key', Setting::VirusScannerAddress->value)->exists()) { return; } $settings->set(Setting::VirusScannerAddress, $address); $settings->set(Setting::VirusScanningEnabled, true); $this->info("Virus scanning seeded to '{$address}' and switched on (first boot)."); } private function seedTwoFactorEnforcement(Settings $settings, string $value): void { if (TwoFactorEnforcement::tryFrom($value) === null) { // Named rather than ignored. A typo here means a tenant // provisioned without the policy it was meant to have, and // silence would make that indistinguishable from success. $this->warn("PROJECTSEND_TWO_FACTOR_ENFORCEMENT='{$value}' is not one of none, staff, clients, all — leaving the setting alone."); return; } // Asked of the table rather than of Settings::get(), which cannot // tell a stored value apart from the enum's own default — and // 'none' is that default, so get() would report the thing we are // trying to detect the absence of. if (StoredSetting::query()->where('key', Setting::TwoFactorEnforcement->value)->exists()) { return; } $settings->set(Setting::TwoFactorEnforcement, $value); $this->info("Two-factor enforcement seeded to '{$value}' (first boot)."); } }