Class: LocalVault::Store
- Inherits:
-
Object
- Object
- LocalVault::Store
- Defined in:
- lib/localvault/store.rb
Overview
File-system storage for a single vault’s encrypted data and metadata.
Each vault lives at ~/.localvault/vaults/<name>/ with two files:
-
meta.yml— salt, creation date, version, secret count -
secrets.enc— encrypted JSON blob (XSalsa20-Poly1305)
Uses atomic writes (tempfile + rename) to prevent corruption. All directories are created with mode 0700, all files with mode 0600.
Defined Under Namespace
Classes: InvalidVaultName
Constant Summary collapse
- VAULT_NAME_PATTERN =
Letters, digits, underscore, dash. Must start with alphanumeric.
/\A[a-zA-Z0-9][a-zA-Z0-9_\-]*\z/
Instance Attribute Summary collapse
-
#vault_name ⇒ Object
readonly
Returns the value of attribute vault_name.
Class Method Summary collapse
-
.list_vaults ⇒ Array<String>
List all vault names found on disk.
Instance Method Summary collapse
-
#count ⇒ Integer
Number of secrets stored in this vault.
-
#create!(salt:) ⇒ void
Create a new vault on disk with initial metadata.
-
#create_meta!(salt:) ⇒ void
Create or overwrite metadata with a new salt, preserving created_at if present.
-
#destroy! ⇒ void
Permanently delete this vault’s directory and all its contents.
-
#exists? ⇒ Boolean
Check whether this vault exists on disk.
-
#initialize(vault_name) ⇒ Store
constructor
Initialize a store for the given vault name.
-
#meta ⇒ Hash?
Read and parse the vault’s metadata.
-
#meta_path ⇒ String
Absolute path to the metadata file.
-
#read_encrypted ⇒ String?
Read the encrypted secrets blob from disk.
-
#salt ⇒ String?
Read the raw salt bytes from metadata.
-
#secrets_path ⇒ String
Absolute path to the encrypted secrets file.
-
#update_count!(n) ⇒ void
Update the secret count in metadata.
-
#vault_path ⇒ String
Absolute path to this vault’s directory.
-
#write_encrypted(bytes) ⇒ void
Atomically write encrypted bytes to disk using tempfile + rename.
Constructor Details
#initialize(vault_name) ⇒ Store
Initialize a store for the given vault name.
35 36 37 38 |
# File 'lib/localvault/store.rb', line 35 def initialize(vault_name) validate_vault_name!(vault_name) @vault_name = vault_name end |
Instance Attribute Details
#vault_name ⇒ Object (readonly)
Returns the value of attribute vault_name.
29 30 31 |
# File 'lib/localvault/store.rb', line 29 def vault_name @vault_name end |
Class Method Details
.list_vaults ⇒ Array<String>
List all vault names found on disk.
191 192 193 194 195 196 197 198 |
# File 'lib/localvault/store.rb', line 191 def self.list_vaults vaults_dir = Config.vaults_path return [] unless File.directory?(vaults_dir) Dir.children(vaults_dir) .select { |name| File.directory?(File.join(vaults_dir, name)) } .sort end |
Instance Method Details
#count ⇒ Integer
Number of secrets stored in this vault.
107 108 109 |
# File 'lib/localvault/store.rb', line 107 def count &.dig("count") || 0 end |
#create!(salt:) ⇒ void
This method returns an undefined value.
Create a new vault on disk with initial metadata.
73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/localvault/store.rb', line 73 def create!(salt:) raise "Vault '#{vault_name}' already exists" if exists? FileUtils.mkdir_p(vault_path, mode: 0o700) = { "name" => vault_name, "created_at" => Time.now.utc.iso8601, "version" => 1, "salt" => Base64.strict_encode64(salt), "count" => 0 } () end |
#create_meta!(salt:) ⇒ void
This method returns an undefined value.
Create or overwrite metadata with a new salt, preserving created_at if present.
154 155 156 157 158 159 160 161 162 163 |
# File 'lib/localvault/store.rb', line 154 def (salt:) existing = = { "name" => vault_name, "created_at" => existing&.dig("created_at") || Time.now.utc.iso8601, "version" => 1, "salt" => Base64.strict_encode64(salt) } () end |
#destroy! ⇒ void
This method returns an undefined value.
Permanently delete this vault’s directory and all its contents.
168 169 170 |
# File 'lib/localvault/store.rb', line 168 def destroy! FileUtils.rm_rf(vault_path) end |
#exists? ⇒ Boolean
Check whether this vault exists on disk.
64 65 66 |
# File 'lib/localvault/store.rb', line 64 def exists? File.directory?(vault_path) && File.exist?() end |
#meta ⇒ Hash?
Read and parse the vault’s metadata.
90 91 92 93 |
# File 'lib/localvault/store.rb', line 90 def return nil unless File.exist?() YAML.safe_load_file() end |
#meta_path ⇒ String
Absolute path to the metadata file.
57 58 59 |
# File 'lib/localvault/store.rb', line 57 def File.join(vault_path, "meta.yml") end |
#read_encrypted ⇒ String?
Read the encrypted secrets blob from disk.
125 126 127 128 |
# File 'lib/localvault/store.rb', line 125 def read_encrypted return nil unless File.exist?(secrets_path) File.binread(secrets_path) end |
#salt ⇒ String?
Read the raw salt bytes from metadata.
98 99 100 101 102 |
# File 'lib/localvault/store.rb', line 98 def salt m = return nil unless m && m["salt"] Base64.strict_decode64(m["salt"]) end |
#secrets_path ⇒ String
Absolute path to the encrypted secrets file.
50 51 52 |
# File 'lib/localvault/store.rb', line 50 def secrets_path File.join(vault_path, "secrets.enc") end |
#update_count!(n) ⇒ void
This method returns an undefined value.
Update the secret count in metadata.
115 116 117 118 119 120 |
# File 'lib/localvault/store.rb', line 115 def update_count!(n) m = return unless m m["count"] = n (m) end |
#vault_path ⇒ String
Absolute path to this vault’s directory.
43 44 45 |
# File 'lib/localvault/store.rb', line 43 def vault_path File.join(Config.vaults_path, vault_name) end |
#write_encrypted(bytes) ⇒ void
This method returns an undefined value.
Atomically write encrypted bytes to disk using tempfile + rename.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/localvault/store.rb', line 134 def write_encrypted(bytes) FileUtils.mkdir_p(vault_path, mode: 0o700) # Atomic write: write to temp file, then rename tmp = Tempfile.new("localvault", vault_path) tmp.binmode tmp.write(bytes) tmp.close File.rename(tmp.path, secrets_path) File.chmod(0o600, secrets_path) rescue StandardError tmp&.close tmp&.unlink raise end |