Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/Commands/ConfigSave.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
use Gitcd\Helpers\Str;
use Gitcd\Helpers\Dir;
use Gitcd\Helpers\Git;
use Gitcd\Helpers\FileEncryption;
use Gitcd\Helpers\Secrets;
use Gitcd\Utils\Json;

Class ConfigSave extends Command {
Expand Down Expand Up @@ -96,6 +98,25 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return Command::SUCCESS;
}

// Re-encrypt .env files if encryption is configured
$secretsMode = Json::read('deployment.secrets', 'file', $repo_dir);
if ($secretsMode === 'encrypted' && Secrets::hasKey()) {
$unencrypted = FileEncryption::findUnencryptedEnvFiles($configrepo);
if (!empty($unencrypted)) {
$output->writeln("<info>Encrypting .env files...</info>");
$dir = rtrim($configrepo, '/') . '/';
foreach ($unencrypted as $envName) {
$envPath = $dir . $envName;
$encPath = $envPath . '.enc';
if (Secrets::encryptFile($envPath, $encPath)) {
$output->writeln(" <fg=green>✓</> Encrypted: {$envName} → {$envName}.enc");
} else {
$output->writeln(" <fg=red>✗</> Failed to encrypt: {$envName}");
}
}
}
}

Git::commit( 'Saving untracked changes', $configrepo );
Git::push( $configrepo );
$output->writeln("<info>Your config repo was saved</info>");
Expand Down
31 changes: 14 additions & 17 deletions src/Commands/ProtocolStart.php
Original file line number Diff line number Diff line change
Expand Up @@ -565,25 +565,26 @@ private function startContainers(string $dir, array $ctx): void
}

if ($tmpEnv) {
$secretsFile = rtrim($dir, '/') . '/.env.protocol-secrets';
copy($tmpEnv, $secretsFile);
chmod($secretsFile, 0600);
unlink($tmpEnv);

$overrideFile = SecretsProvider::generateComposeOverride($composePath, $secretsFile);
// Keep secrets in RAM — use the tmpEnv path directly (already on /dev/shm)
$overrideFile = SecretsProvider::generateComposeOverride($composePath, $tmpEnv);

$runner->log("{$dockerCommand} up --build -d (with secrets)");
Shell::run("cd " . escapeshellarg($dir)
$runner->log("tmpEnv={$tmpEnv} overrideFile={$overrideFile}");
$runner->log("override contents: " . file_get_contents($overrideFile));

$cmd = "cd " . escapeshellarg($dir)
. " && {$dockerCommand} -f " . escapeshellarg($composePath)
. " -f " . escapeshellarg($overrideFile)
. $portOverrideFlag
. " up --build -d 2>&1");
. " up --build -d 2>&1";
$runner->log("docker cmd: {$cmd}");
Shell::run($cmd);

unlink($secretsFile);
unlink($tmpEnv);
unlink($overrideFile);
$runner->log("Secrets temp files cleaned up");
} else {
$runner->log("{$dockerCommand} up --build -d");
$runner->log("{$dockerCommand} up --build -d (NO secrets - resolveToTempFile returned null)");
Shell::run("cd " . escapeshellarg($dir)
. " && {$dockerCommand}"
. " -f " . escapeshellarg($composePath)
Expand All @@ -602,13 +603,9 @@ private function dockerUpWithSecrets(string $dir, StageRunner $runner): bool
{
$tmpEnv = SecretsProvider::resolveToTempFile($dir);
if ($tmpEnv) {
$secretsFile = rtrim($dir, '/') . '/.env.protocol-secrets';
copy($tmpEnv, $secretsFile);
chmod($secretsFile, 0600);
unlink($tmpEnv);

// Keep secrets in RAM — use the tmpEnv path directly (already on /dev/shm)
$composePath = rtrim($dir, '/') . '/docker-compose.yml';
$overrideFile = SecretsProvider::generateComposeOverride($composePath, $secretsFile);
$overrideFile = SecretsProvider::generateComposeOverride($composePath, $tmpEnv);
$envFile = rtrim($dir, '/') . '/.env.deployment';
$dockerCommand = Docker::getDockerCommand();

Expand All @@ -621,7 +618,7 @@ private function dockerUpWithSecrets(string $dir, StageRunner $runner): bool
. " up -d 2>&1", $returnVar);
$started = $returnVar === 0;

unlink($secretsFile);
unlink($tmpEnv);
unlink($overrideFile);
$runner->log("Secrets temp files cleaned up");
return $started;
Expand Down
27 changes: 25 additions & 2 deletions src/Helpers/SecretsProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,17 @@ class SecretsProvider
public static function resolveToTempFile(string $repoDir): ?string
{
$mode = Json::read('deployment.secrets', 'file', $repoDir);
self::log("resolveToTempFile: mode={$mode} repoDir={$repoDir}");

if ($mode === 'encrypted') {
$configRepo = Config::repo($repoDir);
if (!$configRepo) {
self::log("resolveToTempFile: no config repo found");
return null;
}
$encFile = $configRepo . '.env.enc';
if (!is_file($encFile) || !Secrets::hasKey()) {
self::log("resolveToTempFile: enc file missing or no key");
return null;
}
return Secrets::decryptToTempFile($encFile);
Expand All @@ -70,15 +73,19 @@ public static function resolveToTempFile(string $repoDir): ?string
if ($mode === 'aws') {
// Dynamic load to avoid hard dependency on the plugin
if (!class_exists('\\Gitcd\\Plugins\\awssecrets\\AwsSecretsHelper')) {
self::log("resolveToTempFile: AwsSecretsHelper class not found");
return null;
}

// Pull secrets from AWS
$awsEnv = \Gitcd\Plugins\awssecrets\AwsSecretsHelper::pullSecret($repoDir);
if ($awsEnv === null) {
self::log("resolveToTempFile: AWS pullSecret returned null");
return null;
}

self::log("resolveToTempFile: AWS pull OK, got " . strlen($awsEnv) . " bytes");

// Read the existing .env (non-secret config like LOG_LEVEL, APP_ENV)
$baseEnv = self::readBaseEnv($repoDir);

Expand All @@ -91,12 +98,28 @@ public static function resolveToTempFile(string $repoDir): ?string
file_put_contents($tmpFile, $merged);
chmod($tmpFile, 0600);

self::log("resolveToTempFile: wrote {$tmpFile}");
return $tmpFile;
}

self::log("resolveToTempFile: mode '{$mode}' not handled, returning null");
return null;
}

/**
* Write a line to the secrets debug log.
*/
private static function log(string $message): void
{
$logDir = '/var/log/protocol';
if (!is_dir($logDir)) {
@mkdir($logDir, 0755, true);
}
$logFile = $logDir . '/secrets-provider.log';
$line = '[' . date('Y-m-d H:i:s') . '] ' . $message . "\n";
file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
}

/**
* Generate a docker-compose override file that adds env_file to every service.
*
Expand All @@ -113,14 +136,14 @@ public static function generateComposeOverride(string $composePath, string $envF
// Parse the original compose file to get service names
$parsed = Yaml::parseFile($composePath);
$services = $parsed['services'] ?? [];
$envFileName = basename($envFilePath);
$envFileRef = realpath($envFilePath) ?: $envFilePath;

// Build override YAML
$override = "services:\n";
foreach (array_keys($services) as $serviceName) {
$override .= " {$serviceName}:\n";
$override .= " env_file:\n";
$override .= " - {$envFileName}\n";
$override .= " - {$envFileRef}\n";
}

$overridePath = dirname($composePath) . '/.docker-compose.secrets-override.yml';
Expand Down
Loading