Class: Everywhere::Commands::UpdatesKeygen

Inherits:
Dry::CLI::Command
  • Object
show all
Defined in:
lib/everywhere/commands/updates_keygen.rb

Overview

Mint the minisign-compatible keypair that signs this app's updates. Secret key lands in ~/.rubyeverywhere/keys/<bundle_id>.key (never in the app repo); the public key goes into everywhere.yml's updates.public_key, where the build bakes it into the shell for verification.

Instance Method Summary collapse

Instance Method Details

#call(root: ".", force: false, passphrase: false) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/everywhere/commands/updates_keygen.rb', line 21

def call(root: ".", force: false, passphrase: false, **)
  config = Everywhere::Config.load(File.expand_path(root))
  bundle_id = config.bundle_id
  keys_dir = File.join(Paths.cache_dir, "keys")
  sk_path = File.join(keys_dir, "#{bundle_id}.key")
  pub_path = File.join(keys_dir, "#{bundle_id}.pub")

  if File.exist?(sk_path) && !force
    UI.die!("a signing key for #{bundle_id} already exists at #{UI.short_path(sk_path)}" \
            "pass --force to overwrite it (shipped apps signed with the old key will REFUSE updates signed with a new one)")
  end

  UI.phase("Generating update signing key for #{bundle_id}")
  secret = resolve_passphrase(passphrase)
  pair = Minisign.keygen(passphrase: secret)

  require "fileutils"
  FileUtils.mkdir_p(keys_dir, mode: 0o700)
  File.write(sk_path, pair.secret_key, perm: 0o600)
  File.write(pub_path, pair.public_key, perm: 0o644)
  # perm: only applies when the file is created — an overwrite (--force,
  # or a key from an older version) keeps whatever mode it had.
  File.chmod(0o600, sk_path)

  UI.ok("secret key #{UI.short_path(sk_path)} #{UI.dim("(key id #{pair.key_id_hex})")}")
  if secret.empty?
    UI.warn("this secret key is stored UNENCRYPTED — anyone who can read " \
            "#{UI.short_path(sk_path)} can sign updates your users will install")
  end
  UI.ok("public key #{UI.short_path(pub_path)}")

  UI.phase("Add the public key to config/everywhere.yml")
  puts <<~YAML

    updates:
      url: https://your-bucket-or-cdn.example.com/#{config.name.downcase.gsub(/[^a-z0-9]+/, "-")}
      public_key: "#{Minisign.public_key_base64(pair.public_key)}"

  YAML
  UI.substep("back up the secret key somewhere safe — losing it strands every shipped app on its current version")
end