Skip to content

Commit 515e23f

Browse files
grafstclaude
andcommitted
Auto-decrypt and re-encrypt encrypted env files
When the target file doesn't exist but a .encrypted version does, offer to decrypt it before syncing and re-encrypt it afterwards. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f8ba8fc commit 515e23f

1 file changed

Lines changed: 62 additions & 2 deletions

File tree

src/Commands/EnvsyncCommand.php

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public function handle(): int
1515
{
1616
$sourceFile = '.env';
1717
$targetFile = $this->option('path');
18+
$wasDecrypted = false;
1819

1920
// Check if source file exists
2021
if (!File::exists($sourceFile)) {
@@ -24,8 +25,33 @@ public function handle(): int
2425

2526
// Check if target file exists
2627
if (!File::exists($targetFile)) {
27-
$this->error("Target file '{$targetFile}' does not exist.");
28-
return self::FAILURE;
28+
$encryptedFile = $targetFile . '.encrypted';
29+
30+
if (File::exists($encryptedFile)) {
31+
if ($this->option('no-interaction')) {
32+
$this->error("Target file '{$targetFile}' does not exist, but '{$encryptedFile}' was found. Run 'php artisan env:decrypt --env=" . $this->getEnvFromPath($targetFile) . "' to decrypt it first.");
33+
return self::FAILURE;
34+
}
35+
36+
if ($this->confirm("Target file '{$targetFile}' does not exist, but '{$encryptedFile}' was found. Would you like to decrypt it?")) {
37+
$exitCode = $this->call('env:decrypt', [
38+
'--env' => $this->getEnvFromPath($targetFile),
39+
]);
40+
41+
if ($exitCode !== 0 || !File::exists($targetFile)) {
42+
$this->error("Failed to decrypt '{$encryptedFile}'.");
43+
return self::FAILURE;
44+
}
45+
46+
$this->info("Successfully decrypted '{$encryptedFile}' to '{$targetFile}'.");
47+
$wasDecrypted = true;
48+
} else {
49+
return self::FAILURE;
50+
}
51+
} else {
52+
$this->error("Target file '{$targetFile}' does not exist.");
53+
return self::FAILURE;
54+
}
2955
}
3056

3157
$this->info("Syncing '{$sourceFile}' with '{$targetFile}'");
@@ -108,6 +134,26 @@ public function handle(): int
108134
}
109135
}
110136

137+
// Offer to re-encrypt if we decrypted at the start
138+
if ($wasDecrypted && !$this->option('no-interaction')) {
139+
if ($this->confirm("Would you like to re-encrypt '{$targetFile}'?", true)) {
140+
$exitCode = $this->call('env:encrypt', [
141+
'--env' => $this->getEnvFromPath($targetFile),
142+
'--force' => true,
143+
]);
144+
145+
if ($exitCode === 0) {
146+
$this->info("Successfully re-encrypted '{$targetFile}'.");
147+
148+
// Clean up the decrypted file
149+
File::delete($targetFile);
150+
$this->info("Removed decrypted '{$targetFile}'.");
151+
} else {
152+
$this->warn("Failed to re-encrypt '{$targetFile}'. The decrypted file has been left in place.");
153+
}
154+
}
155+
}
156+
111157
return self::SUCCESS;
112158
}
113159

@@ -685,6 +731,20 @@ private function handleRemoveFromSource(array $sourceEntries, array $targetEntri
685731
return true;
686732
}
687733

734+
/**
735+
* Extract the environment name from an env file path (e.g., '.env.staging' -> 'staging')
736+
*/
737+
private function getEnvFromPath(string $filePath): string
738+
{
739+
$filename = basename($filePath);
740+
741+
if (preg_match('/^\.env\.(.+)$/', $filename, $matches)) {
742+
return $matches[1];
743+
}
744+
745+
return '';
746+
}
747+
688748
/**
689749
* Check if a file is version controlled (in git)
690750
*/

0 commit comments

Comments
 (0)