Class: Bard::Api::App

Inherits:
Object
  • Object
show all
Defined in:
lib/bard/api/app.rb

Constant Summary collapse

DEPLOY_LOCK =
"tmp/bard-deploy.lock"
DEPLOYED_SHA =
"tmp/bard-deployed.sha"
DEPLOY_COMMAND =

Record the deployed sha only after bin/setup fully succeeds — a bare git pull advances HEAD before the bundle/migrate/restart run, so HEAD alone would read as "done" too early.

"flock -n #{DEPLOY_LOCK} -c 'git pull --ff-only origin master && bin/setup && git rev-parse HEAD > #{DEPLOYED_SHA}'"

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.backup_runnerObject

Runs the backup task out-of-band so the request returns immediately. Override to wire backups into a project's own job queue.



17
18
19
# File 'lib/bard/api/app.rb', line 17

def backup_runner
  @backup_runner ||= ->(task) { Thread.new { task.call } }
end

.deploy_runnerObject

bin/setup restarts Puma via procsd restartsystemctl --user restart, which SIGKILLs the app unit's whole cgroup. So the deploy can't run in this worker (or any plain child of it) — it'd be killed mid-deploy. We run it in its own systemd --user scope: a separate cgroup that survives the restart.



25
26
27
# File 'lib/bard/api/app.rb', line 25

def deploy_runner
  @deploy_runner ||= method(:spawn_detached_deploy)
end

Class Method Details

.spawn_detached_deploy(command) ⇒ Object

with_unbundled_env is essential: this worker runs under the app's bundle (RUBYOPT=-rbundler/setup, BUNDLE_GEMFILE). Inherited into the deploy, that makes every ruby command — bundle install included — crash at startup the moment git pull lands a lockfile with not-yet-installed gems. The deploy must start from a clean env.



33
34
35
36
37
38
39
40
41
# File 'lib/bard/api/app.rb', line 33

def spawn_detached_deploy(command)
  Bundler.with_unbundled_env do
    pid = Process.spawn(
      "systemd-run", "--user", "--scope", "--collect", "--quiet", "bash", "-lc", command,
      :in => "/dev/null", %i[out err] => ["log/bard-deploy.log", "a"],
    )
    Process.detach(pid)
  end
end

Instance Method Details

#call(env) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/bard/api/app.rb', line 50

def call(env)
  request = Rack::Request.new(env)
  method = request.request_method
  path = request.path_info

  case [method, path]
  when ["GET", "/health"]
    health(request)
  when ["POST", "/backups"]
    create_backup(request)
  when ["GET", "/backups/latest"]
    latest_backup(request)
  when ["GET", "/config"]
    config(request)
  when ["POST", "/deploy"]
    create_deploy(request)
  else
    not_found
  end
rescue => e
  json_response(500, { error: e.message })
end