bard-api 0.6.0 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee48d71ea32a1a05c4a540cdab7b8127f94e9366cfcc259a829f2843d0ce8fe2
4
- data.tar.gz: f3baa8c04d7acf644692c9cc5dbd6dc143504c22d1e4b8380134d4cb29217728
3
+ metadata.gz: 4d4da564c789355e60c447afcd4509dab027ae176561da244b95b31787df786b
4
+ data.tar.gz: b6307c6ee310ce199ee18b8760e846272cf7c66276cb3d5d5a64ecf60d580f5e
5
5
  SHA512:
6
- metadata.gz: 0ca7e1d49a6dfac34bf2c55e01f6e2dbb4d287ca003301a371a092ae57e3ab4e62110d694ee8d9863053fe32fbff97bd2834338128c8b8afd47fafe88d4e36c7
7
- data.tar.gz: 5b1167e219a79526de91876267bf769ad895c6aaf8cea1df2e5c6fe316307a05e33b29da3f5c39748037e5027b978d82b4da7e2647f10a6c301b535414fbae55
6
+ metadata.gz: d0c3651eaa2dd8ad9ba27693f942d7b9f29acbc72d9104d012d24bfa3b8179ea13531c000bd7c7d9b883a9b70a9d99fcda5235ddeb03773c7b3bd6c57ba5a496
7
+ data.tar.gz: c8e8658f60a5327f7e6770c457501c2b73769b17842ad73830c7ce81159736be48542fda169bf6bd500db6698e5088af772d52cf0c4f6c15c88b722d7ce7170b
data/lib/bard/api/app.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "rack"
4
4
  require "json"
5
+ require "bundler"
5
6
  require_relative "auth"
6
7
  require "bard/backup"
7
8
 
@@ -25,17 +26,38 @@ module Bard
25
26
  @deploy_runner ||= method(:spawn_detached_deploy)
26
27
  end
27
28
 
29
+ # with_unbundled_env is essential: this worker runs under the app's bundle
30
+ # (RUBYOPT=-rbundler/setup, BUNDLE_GEMFILE). Inherited into the deploy, that makes every
31
+ # ruby command — bundle install included — crash at startup the moment git pull lands a
32
+ # lockfile with not-yet-installed gems. The deploy must start from a clean env.
28
33
  def spawn_detached_deploy(command)
29
- pid = Process.spawn(
30
- "systemd-run", "--user", "--scope", "--collect", "--quiet", "bash", "-lc", command,
31
- :in => "/dev/null", %i[out err] => ["log/bard-deploy.log", "a"],
32
- )
33
- Process.detach(pid)
34
+ Bundler.with_unbundled_env do
35
+ pid = Process.spawn(
36
+ "systemd-run", "--user", "--scope", "--collect", "--quiet", "bash", "-lc", command,
37
+ :in => "/dev/null", %i[out err] => ["log/bard-deploy.log", "a"],
38
+ )
39
+ Process.detach(pid)
40
+ end
34
41
  end
35
42
  end
36
43
 
37
44
  DEPLOY_LOCK = "tmp/bard-deploy.lock"
38
- DEPLOY_COMMAND = "flock -n #{DEPLOY_LOCK} -c 'git pull --ff-only origin master && bin/setup'"
45
+ DEPLOYED_SHA = "tmp/bard-deployed.sha"
46
+ FAILED_SHA = "tmp/bard-deploy-failed.sha"
47
+ DEPLOY_LOG = "log/bard-deploy.log"
48
+ # How long a failed sha short-circuits re-deploy: long enough to end the caller's poll loop
49
+ # without a re-spawn, short enough that a fresh `bard deploy` run can retry the same sha.
50
+ FAILED_COOLDOWN = 60
51
+
52
+ # Record the deployed sha only after bin/setup fully succeeds — a bare `git pull` advances
53
+ # HEAD before the bundle/migrate/restart run, so HEAD alone would read as "done" too early.
54
+ # On any failure, stamp the attempted sha so we don't re-spawn (and restart Puma) on every
55
+ # poll; the marker is written inside the flock, so it's on disk before the lock releases.
56
+ DEPLOY_COMMAND =
57
+ "flock -n #{DEPLOY_LOCK} -c '" \
58
+ "rm -f #{FAILED_SHA}; " \
59
+ "git pull --ff-only origin master && bin/setup && git rev-parse HEAD > #{DEPLOYED_SHA} " \
60
+ "|| git rev-parse origin/master > #{FAILED_SHA}'"
39
61
 
40
62
  def call(env)
41
63
  request = Rack::Request.new(env)
@@ -126,12 +148,42 @@ module Bard
126
148
  return json_response(200, { status: "noop", sha: sha }) if target == sha
127
149
  return json_response(409, { status: "deploying", sha: sha }) if deploy_in_progress?
128
150
 
151
+ # A prior attempt at this exact sha just failed. Report it instead of re-spawning the
152
+ # deploy on every poll (which would restart Puma every few seconds and never converge).
153
+ if target == failed_sha && recently_failed?
154
+ return json_response(500, {
155
+ status: "failed",
156
+ sha: sha,
157
+ error: "deploy failed on the server; see #{DEPLOY_LOG}",
158
+ log: deploy_log_tail,
159
+ })
160
+ end
161
+
129
162
  self.class.deploy_runner.call(DEPLOY_COMMAND)
130
163
  json_response(202, { status: "deploying", sha: sha })
131
164
  end
132
165
 
166
+ # The last FULLY deployed sha (bin/setup succeeded), not merely where git HEAD points.
167
+ # Absent marker => nothing has completed yet, so it can never match the requested sha and
168
+ # the caller keeps polling until bin/setup writes it — or times out on a real failure.
133
169
  def current_sha
134
- `git rev-parse HEAD`.chomp
170
+ return "" unless File.exist?(DEPLOYED_SHA)
171
+ File.read(DEPLOYED_SHA).chomp
172
+ end
173
+
174
+ # The sha whose deploy most recently failed (bin/setup or the git pull errored out).
175
+ def failed_sha
176
+ return "" unless File.exist?(FAILED_SHA)
177
+ File.read(FAILED_SHA).chomp
178
+ end
179
+
180
+ def recently_failed?
181
+ File.exist?(FAILED_SHA) && (Time.now - File.mtime(FAILED_SHA)) < FAILED_COOLDOWN
182
+ end
183
+
184
+ def deploy_log_tail(lines = 30)
185
+ return "" unless File.exist?(DEPLOY_LOG)
186
+ File.readlines(DEPLOY_LOG).last(lines).join
135
187
  end
136
188
 
137
189
  def deploy_in_progress?
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Bard
4
4
  module Api
5
- VERSION = "0.6.0"
5
+ VERSION = "0.6.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bard-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-07-09 00:00:00.000000000 Z
10
+ date: 2026-07-10 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: jwt