#!/usr/bin/env php [DB_PATH] * * Steps performed: * 1. Read old APP_KEY from the .env file adjacent to this script's app root. * 2. Decrypt the current password from smtp_settings using the old key. * 3. Encrypt it with the new key. * 4. Write the new ciphertext back to the DB. * 5. Update app/.env with the new APP_KEY. * * After running this script, redeploy or manually update app/.env on every * environment that shares the same database. */ $newKeyB64 = $argv[1] ?? ''; $dbPath = $argv[2] ?? null; if ($newKeyB64 === '') { fwrite(STDERR, "Usage: php reencrypt-smtp-password.php [DB_PATH]\n"); fwrite(STDERR, "Generate a key: php -r \"echo base64_encode(random_bytes(32));\"\n"); exit(1); } $newKeyRaw = base64_decode($newKeyB64, strict: true); if ($newKeyRaw === false || strlen($newKeyRaw) !== 32) { fwrite(STDERR, "ERROR: new key must be a base64-encoded 32-byte value.\n"); exit(1); } // Locate app root (script lives in app/scripts/ or scripts/ next to app/) $candidates = [ __DIR__ . '/../app', // repo root / scripts/ __DIR__ . '/..', // app / scripts/ ]; $appRoot = null; foreach ($candidates as $c) { if (file_exists(realpath($c) . '/src/Crypto.php')) { $appRoot = realpath($c); break; } } if ($appRoot === null) { fwrite(STDERR, "ERROR: could not locate app root (looking for src/Crypto.php).\n"); exit(1); } define('APP_ROOT', $appRoot); require_once $appRoot . '/src/Crypto.php'; $dbPath = $dbPath ?? $appRoot . '/storage/xamxam.db'; if (!file_exists($dbPath)) { fwrite(STDERR, "ERROR: database not found: $dbPath\n"); exit(1); } $pdo = new PDO('sqlite:' . $dbPath); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $row = $pdo->query("SELECT password FROM smtp_settings WHERE id = 1")->fetch(PDO::FETCH_ASSOC); if (!$row) { fwrite(STDERR, "ERROR: no smtp_settings row found.\n"); exit(1); } // Decrypt with the old key (read from existing .env via Crypto::decrypt) $plaintext = Crypto::decrypt($row['password']); if ($plaintext === '') { echo "Password is empty — nothing to re-encrypt.\n"; exit(0); } // Encrypt with the new key directly (bypass the singleton so we can supply a different key) $iv = random_bytes(12); $tag = ''; $cipher = openssl_encrypt($plaintext, 'aes-256-gcm', $newKeyRaw, OPENSSL_RAW_DATA, $iv, $tag, '', 16); if ($cipher === false) { fwrite(STDERR, "ERROR: encryption failed: " . openssl_error_string() . "\n"); exit(1); } $newBlob = base64_encode($iv . $tag . $cipher); // Write new ciphertext to DB $pdo->prepare("UPDATE smtp_settings SET password = ? WHERE id = 1")->execute([$newBlob]); echo "DB updated with new ciphertext.\n"; // Update .env $envFile = $appRoot . '/.env'; $envContent = file_exists($envFile) ? file_get_contents($envFile) : ''; if (preg_match('/^APP_KEY=.*/m', $envContent)) { $envContent = preg_replace('/^APP_KEY=.*/m', 'APP_KEY=' . $newKeyB64, $envContent); } else { $envContent .= "APP_KEY={$newKeyB64}\n"; } file_put_contents($envFile, $envContent); echo ".env updated with new APP_KEY.\n"; echo "Done. Redeploy app/.env to all environments: just deploy-env\n";