dotenvcrypt 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +56 -14
- data/bin/dotenvcrypt +6 -4
- data/lib/dotenvcrypt/cli/commands.rb +87 -0
- data/lib/dotenvcrypt/cli/key_manager.rb +31 -0
- data/lib/dotenvcrypt/version.rb +13 -1
- data/lib/dotenvcrypt.rb +25 -3
- metadata +18 -4
- data/lib/dotenvcrypt/encryptor.rb +0 -65
- data/lib/dotenvcrypt/manager.rb +0 -90
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 111cda13c2044b722ef62e9fb14425ad5c02476934d81a65708e1c72e031a11c
|
|
4
|
+
data.tar.gz: '0084c2763785031c2bf4806d03dcad28c31a9480953c66438dfcf1e18729f5db'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 04c1dbfae62eebd97256f895692ba7cac8f55214cdebea571a2157bf9108848c49c5359c1e46ce73fcc950840f6bb30e3306c9f00e4b187b3c6338d0c7f81a5b
|
|
7
|
+
data.tar.gz: 43a7f64f3487e7787cfab7bb13937937fb8fee32552c4b1898233c3d5b6f4f21e9c7ba254f8742b8420e05097eac591d87ce2039ea572a285f0c412f8ed817ae
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dotenvcrypt
|
|
1
|
+
# dotenvcrypt
|
|
2
2
|
|
|
3
3
|
**Securely encrypt, manage, and load your `.env` files in public repositories.**
|
|
4
4
|
|
|
@@ -6,15 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Features
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
11
|
+
- Encrypt `.env` files into `.env.enc` for safe storage in Git
|
|
12
|
+
- Decrypt and load environment variables securely into the shell
|
|
13
|
+
- Edit encrypted `.env.enc`, then re-encrypt after saving
|
|
14
|
+
- Use as a CLI tool or as a Ruby library
|
|
14
15
|
|
|
15
16
|
---
|
|
16
17
|
|
|
17
|
-
##
|
|
18
|
+
## Installation
|
|
18
19
|
|
|
19
20
|
### Using Homebrew (recommended)
|
|
20
21
|
|
|
@@ -22,7 +23,15 @@
|
|
|
22
23
|
brew install namolnad/formulae/dotenvcrypt
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
### Using RubyGems
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
gem install dotenvcrypt
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## CLI Usage
|
|
26
35
|
|
|
27
36
|
### Basic Commands
|
|
28
37
|
|
|
@@ -45,13 +54,13 @@ eval "$(dotenvcrypt decrypt .env.enc)"
|
|
|
45
54
|
dotenvcrypt looks for your encryption key in these locations (in order):
|
|
46
55
|
|
|
47
56
|
1. Command line argument: `--key YOUR_SECRET_KEY`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
2. Environment variable: `DOTENVCRYPT_KEY`
|
|
58
|
+
3. File: `./.dotenvcrypt.key` (in the current directory)
|
|
59
|
+
4. File: `$XDG_CONFIG_HOME/dotenvcrypt/secret.key` (or `$HOME/.config/dotenvcrypt/secret.key`)
|
|
60
|
+
5. File: `$HOME/.dotenvcrypt.key`
|
|
61
|
+
6. Interactive prompt (if no key is found)
|
|
53
62
|
|
|
54
|
-
###
|
|
63
|
+
### Shell Integration Example
|
|
55
64
|
|
|
56
65
|
Add this to your shell profile (`.zshrc`, `.bashrc`, etc.) to automatically load variables:
|
|
57
66
|
|
|
@@ -65,10 +74,43 @@ if [[ ! -f $dotenvcrypt_key_path || ! -s $dotenvcrypt_key_path ]]; then
|
|
|
65
74
|
chmod 600 $dotenvcrypt_key_path
|
|
66
75
|
fi
|
|
67
76
|
|
|
68
|
-
# Load encrypted environment variables if
|
|
77
|
+
# Load encrypted environment variables if dotenvcrypt is installed
|
|
69
78
|
if command -v dotenvcrypt &> /dev/null; then
|
|
70
79
|
set -a # automatically export all variables
|
|
71
80
|
eval "$(dotenvcrypt decrypt $HOME/.env.enc)"
|
|
72
81
|
set +a # stop automatically exporting
|
|
73
82
|
fi
|
|
74
83
|
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Library Usage
|
|
88
|
+
|
|
89
|
+
You can also use dotenvcrypt as a Ruby library in your applications:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
require 'dotenvcrypt'
|
|
93
|
+
|
|
94
|
+
# Load encrypted .env file directly into ENV
|
|
95
|
+
Dotenvcrypt.load_env('.env.enc', key: 'your-secret-key')
|
|
96
|
+
|
|
97
|
+
# Now your environment variables are available
|
|
98
|
+
puts ENV['DATABASE_URL']
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For more advanced library usage, see the [dotenvcrypt-core](https://github.com/namolnad/dotenvcrypt/tree/main/core) documentation.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Security
|
|
106
|
+
|
|
107
|
+
- Uses AES-256 in GCM (Galois/Counter Mode) for authenticated encryption
|
|
108
|
+
- Generates secure random initialization vectors (IV) for each encryption
|
|
109
|
+
- Provides tamper detection via authentication tags
|
|
110
|
+
- Keys are hashed with SHA256 before use
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
data/bin/dotenvcrypt
CHANGED
|
@@ -49,17 +49,19 @@ if command.nil?
|
|
|
49
49
|
exit 1
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
# Fetch encryption key
|
|
53
|
+
key = Dotenvcrypt::CLI::KeyManager.fetch_key(options[:key])
|
|
54
|
+
commands = Dotenvcrypt::CLI::Commands.new(key: key)
|
|
53
55
|
|
|
54
56
|
case command
|
|
55
57
|
when 'encrypt'
|
|
56
58
|
input_file = ARGV[0] || UNENCRYPTED_FILE
|
|
57
59
|
output_file = ARGV[1] || ENCRYPTED_FILE
|
|
58
|
-
|
|
60
|
+
commands.encrypt_file(input_file, output_file)
|
|
59
61
|
when 'decrypt'
|
|
60
|
-
|
|
62
|
+
commands.decrypt_file(ARGV[0] || ENCRYPTED_FILE)
|
|
61
63
|
when 'edit'
|
|
62
|
-
|
|
64
|
+
commands.edit_file(ARGV[0] || ENCRYPTED_FILE)
|
|
63
65
|
else
|
|
64
66
|
puts "Unknown command: #{command}"
|
|
65
67
|
puts opt_parser
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tempfile'
|
|
4
|
+
|
|
5
|
+
module Dotenvcrypt
|
|
6
|
+
module CLI
|
|
7
|
+
class Commands
|
|
8
|
+
def initialize(key:)
|
|
9
|
+
@key = key
|
|
10
|
+
@encryptor = Dotenvcrypt::Core::Encryptor.new(key)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Encrypt an existing `.env` file
|
|
14
|
+
def encrypt_file(unencrypted_file, encrypted_file)
|
|
15
|
+
unless File.exist?(unencrypted_file)
|
|
16
|
+
puts "❌ File not found: #{unencrypted_file}"
|
|
17
|
+
exit(1)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
encrypted_data = @encryptor.encrypt(File.read(unencrypted_file))
|
|
21
|
+
File.write(encrypted_file, encrypted_data)
|
|
22
|
+
|
|
23
|
+
puts "🔒 Encrypted #{unencrypted_file} → #{encrypted_file}"
|
|
24
|
+
rescue Dotenvcrypt::Core::EncryptionError => e
|
|
25
|
+
puts "❌ Encryption failed: #{e.message}"
|
|
26
|
+
exit(1)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Decrypt an encrypted `.env` file and print to stdout
|
|
30
|
+
def decrypt_file(encrypted_file)
|
|
31
|
+
unless File.exist?(encrypted_file)
|
|
32
|
+
puts "❌ File not found: #{encrypted_file}"
|
|
33
|
+
exit(1)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
decrypted_content = @encryptor.decrypt(File.read(encrypted_file))
|
|
37
|
+
puts decrypted_content
|
|
38
|
+
rescue Dotenvcrypt::Core::DecryptionError => e
|
|
39
|
+
puts "❌ Decryption failed: #{e.message}"
|
|
40
|
+
exit(1)
|
|
41
|
+
rescue Dotenvcrypt::Core::InvalidFormatError => e
|
|
42
|
+
puts "❌ #{e.message}"
|
|
43
|
+
exit(1)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Edit decrypted env, then re-encrypt
|
|
47
|
+
def edit_file(encrypted_file)
|
|
48
|
+
unless File.exist?(encrypted_file)
|
|
49
|
+
puts "❌ File not found: #{encrypted_file}"
|
|
50
|
+
exit(1)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
temp_file = Tempfile.new('dotenvcrypt')
|
|
54
|
+
|
|
55
|
+
begin
|
|
56
|
+
original_content = @encryptor.decrypt(File.read(encrypted_file))
|
|
57
|
+
|
|
58
|
+
File.write(temp_file.path, original_content)
|
|
59
|
+
|
|
60
|
+
puts "Waiting for file to be saved. Abort with Ctrl-C."
|
|
61
|
+
|
|
62
|
+
system("#{ENV['EDITOR'] || 'vim'} #{temp_file.path}")
|
|
63
|
+
|
|
64
|
+
updated_content = File.read(temp_file.path)
|
|
65
|
+
|
|
66
|
+
if updated_content != original_content
|
|
67
|
+
encrypted_data = @encryptor.encrypt(updated_content)
|
|
68
|
+
File.write(encrypted_file, encrypted_data)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
puts "🔒 Encrypted and saved #{encrypted_file}"
|
|
72
|
+
rescue Dotenvcrypt::Core::DecryptionError => e
|
|
73
|
+
puts "❌ Decryption failed: #{e.message}"
|
|
74
|
+
exit(1)
|
|
75
|
+
rescue Dotenvcrypt::Core::EncryptionError => e
|
|
76
|
+
puts "❌ Encryption failed: #{e.message}"
|
|
77
|
+
exit(1)
|
|
78
|
+
rescue Dotenvcrypt::Core::InvalidFormatError => e
|
|
79
|
+
puts "❌ #{e.message}"
|
|
80
|
+
exit(1)
|
|
81
|
+
ensure
|
|
82
|
+
temp_file.unlink if temp_file
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tty-prompt'
|
|
4
|
+
|
|
5
|
+
module Dotenvcrypt
|
|
6
|
+
module CLI
|
|
7
|
+
class KeyManager
|
|
8
|
+
# Determine config directory following XDG Base Directory Specification
|
|
9
|
+
CONFIG_HOME = ENV['XDG_CONFIG_HOME'] || "#{ENV['HOME']}/.config"
|
|
10
|
+
CONFIG_DIR = "#{CONFIG_HOME}/dotenvcrypt".freeze
|
|
11
|
+
|
|
12
|
+
# Check multiple locations in order of preference
|
|
13
|
+
ENCRYPTION_KEY_LOCATIONS = [
|
|
14
|
+
"#{Dir.pwd}/.dotenvcrypt.key", # Project-specific key (current directory)
|
|
15
|
+
"#{CONFIG_DIR}/secret.key", # XDG standard location
|
|
16
|
+
"#{ENV['HOME']}/.dotenvcrypt.key" # Legacy location (for backward compatibility)
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
def self.fetch_key(cli_key = nil)
|
|
20
|
+
return cli_key if cli_key && !cli_key.empty?
|
|
21
|
+
return ENV['DOTENVCRYPT_KEY'] if ENV['DOTENVCRYPT_KEY'] && !ENV['DOTENVCRYPT_KEY'].empty?
|
|
22
|
+
|
|
23
|
+
ENCRYPTION_KEY_LOCATIONS.each do |key_file|
|
|
24
|
+
return File.read(key_file).strip if File.exist?(key_file)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
TTY::Prompt.new.ask("Enter encryption key:", echo: false)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/dotenvcrypt/version.rb
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# During gem build or in monorepo: read version from core directly
|
|
4
|
+
core_version_file = File.expand_path('../../core/lib/dotenvcrypt/core/version.rb', __dir__)
|
|
5
|
+
if File.exist?(core_version_file)
|
|
6
|
+
# Load from monorepo structure
|
|
7
|
+
require_relative '../../core/lib/dotenvcrypt/core/version'
|
|
8
|
+
else
|
|
9
|
+
# For gem installation: load from installed dotenvcrypt-core gem
|
|
10
|
+
require 'dotenvcrypt/core/version'
|
|
11
|
+
end
|
|
12
|
+
|
|
1
13
|
module Dotenvcrypt
|
|
2
|
-
VERSION =
|
|
14
|
+
VERSION = Core::VERSION
|
|
3
15
|
end
|
data/lib/dotenvcrypt.rb
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# For development/monorepo: try to load from relative path first
|
|
4
|
+
begin
|
|
5
|
+
require_relative '../../core/lib/dotenvcrypt-core'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
# For gem installation: load from installed dotenvcrypt-core gem
|
|
8
|
+
require 'dotenvcrypt-core'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require 'tty-prompt'
|
|
12
|
+
require 'tty-command'
|
|
13
|
+
require_relative 'dotenvcrypt/version'
|
|
14
|
+
require_relative 'dotenvcrypt/cli/key_manager'
|
|
15
|
+
require_relative 'dotenvcrypt/cli/commands'
|
|
16
|
+
|
|
17
|
+
module Dotenvcrypt
|
|
18
|
+
# Re-export core functionality (if not already defined)
|
|
19
|
+
Core = Dotenvcrypt::Core unless defined?(Core)
|
|
20
|
+
|
|
21
|
+
# Convenience method for library usage
|
|
22
|
+
def self.load_env(encrypted_file_path, key:)
|
|
23
|
+
Core.load_env(encrypted_file_path, key: key)
|
|
24
|
+
end
|
|
25
|
+
end
|
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dotenvcrypt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dan Loman
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-12-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: dotenvcrypt-core
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.7.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.7.0
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: tty-prompt
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -51,8 +65,8 @@ files:
|
|
|
51
65
|
- README.md
|
|
52
66
|
- bin/dotenvcrypt
|
|
53
67
|
- lib/dotenvcrypt.rb
|
|
54
|
-
- lib/dotenvcrypt/
|
|
55
|
-
- lib/dotenvcrypt/
|
|
68
|
+
- lib/dotenvcrypt/cli/commands.rb
|
|
69
|
+
- lib/dotenvcrypt/cli/key_manager.rb
|
|
56
70
|
- lib/dotenvcrypt/version.rb
|
|
57
71
|
homepage: https://github.com/namolnad/dotenvcrypt
|
|
58
72
|
licenses:
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
module Dotenvcrypt
|
|
2
|
-
class Encryptor
|
|
3
|
-
def initialize(encryption_key)
|
|
4
|
-
@encryption_key = encryption_key
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
# Encrypt file
|
|
8
|
-
def encrypt(data)
|
|
9
|
-
cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
|
10
|
-
cipher.encrypt
|
|
11
|
-
key = OpenSSL::Digest::SHA256.digest(encryption_key)
|
|
12
|
-
cipher.key = key
|
|
13
|
-
|
|
14
|
-
# Generate a secure random IV (12 bytes is recommended for GCM)
|
|
15
|
-
iv = cipher.random_iv
|
|
16
|
-
|
|
17
|
-
encrypted = cipher.update(data) + cipher.final
|
|
18
|
-
|
|
19
|
-
# Get the authentication tag (16 bytes)
|
|
20
|
-
auth_tag = cipher.auth_tag
|
|
21
|
-
|
|
22
|
-
# Combine IV, encrypted data, and auth tag
|
|
23
|
-
combined = iv + auth_tag + encrypted
|
|
24
|
-
|
|
25
|
-
# Convert to base64 for readability
|
|
26
|
-
Base64.strict_encode64(combined)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Decrypt file
|
|
30
|
-
def decrypt(encoded_data)
|
|
31
|
-
begin
|
|
32
|
-
data = Base64.strict_decode64(encoded_data)
|
|
33
|
-
rescue ArgumentError
|
|
34
|
-
puts "❌ Decryption failed. File is not in valid base64 format."
|
|
35
|
-
exit(1)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# For GCM mode:
|
|
39
|
-
# - IV is 12 bytes
|
|
40
|
-
# - Auth tag is 16 bytes
|
|
41
|
-
iv_size = 12
|
|
42
|
-
auth_tag_size = 16
|
|
43
|
-
|
|
44
|
-
# Extract the components
|
|
45
|
-
iv = data[0...iv_size]
|
|
46
|
-
auth_tag = data[iv_size...(iv_size + auth_tag_size)]
|
|
47
|
-
encrypted_data = data[(iv_size + auth_tag_size)..-1]
|
|
48
|
-
|
|
49
|
-
cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
|
50
|
-
cipher.decrypt
|
|
51
|
-
cipher.key = OpenSSL::Digest::SHA256.digest(encryption_key)
|
|
52
|
-
cipher.iv = iv
|
|
53
|
-
cipher.auth_tag = auth_tag
|
|
54
|
-
|
|
55
|
-
cipher.update(encrypted_data) + cipher.final
|
|
56
|
-
rescue OpenSSL::Cipher::CipherError
|
|
57
|
-
puts "❌ Decryption failed. Invalid key, corrupted file, or tampering detected."
|
|
58
|
-
exit(1)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
private
|
|
62
|
-
|
|
63
|
-
attr_reader :encryption_key
|
|
64
|
-
end
|
|
65
|
-
end
|
data/lib/dotenvcrypt/manager.rb
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'base64'
|
|
4
|
-
require 'openssl'
|
|
5
|
-
require 'tempfile'
|
|
6
|
-
require 'tty-prompt'
|
|
7
|
-
require 'tty-command'
|
|
8
|
-
require_relative 'encryptor'
|
|
9
|
-
|
|
10
|
-
module Dotenvcrypt
|
|
11
|
-
class Manager
|
|
12
|
-
# Determine config directory following XDG Base Directory Specification
|
|
13
|
-
CONFIG_HOME = ENV['XDG_CONFIG_HOME'] || "#{ENV['HOME']}/.config"
|
|
14
|
-
CONFIG_DIR = "#{CONFIG_HOME}/dotenvcrypt".freeze
|
|
15
|
-
|
|
16
|
-
# Check multiple locations in order of preference
|
|
17
|
-
ENCRYPTION_KEY_LOCATIONS = [
|
|
18
|
-
"#{Dir.pwd}/.dotenvcrypt.key", # Project-specific key (current directory)
|
|
19
|
-
"#{CONFIG_DIR}/secret.key", # XDG standard location
|
|
20
|
-
"#{ENV['HOME']}/.dotenvcrypt.key" # Legacy location (for backward compatibility)
|
|
21
|
-
].freeze
|
|
22
|
-
|
|
23
|
-
def initialize(encryption_key = nil)
|
|
24
|
-
@encryption_key = encryption_key
|
|
25
|
-
@encryptor = Encryptor.new(fetch_key)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Encrypt an existing `.env` file
|
|
29
|
-
def encrypt_file(unencrypted_file, encrypted_file)
|
|
30
|
-
unless File.exist?(unencrypted_file)
|
|
31
|
-
puts "❌ File not found: #{unencrypted_file}"
|
|
32
|
-
exit(1)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
File.open(encrypted_file, 'w') do |f|
|
|
36
|
-
f.write encryptor.encrypt(File.read(unencrypted_file))
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
puts "🔒 Encrypted #{unencrypted_file} → #{encrypted_file}"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Decrypt an encrypted `.env` file and print to stdout
|
|
43
|
-
def decrypt_file(encrypted_file)
|
|
44
|
-
puts encryptor.decrypt(File.read(encrypted_file))
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Edit decrypted env, then re-encrypt
|
|
48
|
-
def edit_file(encrypted_file)
|
|
49
|
-
temp_file = Tempfile.new('dotenvcrypt')
|
|
50
|
-
|
|
51
|
-
original_content = encryptor.decrypt(File.read(encrypted_file))
|
|
52
|
-
|
|
53
|
-
File.open(temp_file.path, 'w') do |f|
|
|
54
|
-
f.write original_content
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
puts "Waiting for file to be saved. Abort with Ctrl-C."
|
|
58
|
-
|
|
59
|
-
system("#{ENV['EDITOR'] || 'vim'} #{temp_file.path}")
|
|
60
|
-
|
|
61
|
-
updated_content = File.read(temp_file.path)
|
|
62
|
-
|
|
63
|
-
if updated_content != original_content
|
|
64
|
-
File.open(encrypted_file, 'w') do |f|
|
|
65
|
-
f.write encryptor.encrypt(updated_content)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
puts "🔒 Encrypted and saved #{encrypted_file}"
|
|
70
|
-
ensure
|
|
71
|
-
temp_file.unlink if temp_file
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
attr_reader :encryption_key, :encryptor
|
|
77
|
-
|
|
78
|
-
# Get encryption key: From CLI arg OR encryption-key file OR securely prompt from stdin
|
|
79
|
-
def fetch_key
|
|
80
|
-
return encryption_key if encryption_key && !encryption_key.empty?
|
|
81
|
-
return ENV['DOTENVCRYPT_KEY'] if ENV['DOTENVCRYPT_KEY'] && !ENV['DOTENVCRYPT_KEY'].empty?
|
|
82
|
-
ENCRYPTION_KEY_LOCATIONS.each do |key_file|
|
|
83
|
-
return File.read(key_file).strip if File.exist?(key_file)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
TTY::Prompt.new.ask("Enter encryption key:", echo: false)
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|