fix(versions): show build commit + branch in production

The Version page read the deployed commit/branch from .git at runtime, which
the prod Docker image does not contain — so it showed 'Build —' and the channel
as the branch. install.sh now bakes the host's git short-SHA and branch into
.env (CLUSEV_BUILD_SHA / CLUSEV_BUILD_BRANCH); the page prefers those and keeps
the live .git read as the dev fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/v1-foundation v0.9.10
boban 2026-06-19 18:35:35 +02:00
parent 997b624b0f
commit 81a9e4d641
6 changed files with 53 additions and 4 deletions

View File

@ -81,6 +81,10 @@ IMAGE_TAG=latest
APP_DOMAIN= APP_DOMAIN=
APP_SCHEME=http APP_SCHEME=http
ACME_EMAIL= ACME_EMAIL=
# Deployed commit + branch, shown on the Version page. install.sh fills these from the host's
# .git (the prod image ships none) — leave empty; in dev the page reads .git directly.
CLUSEV_BUILD_SHA=
CLUSEV_BUILD_BRANCH=
# External reverse-proxy TLS: set to the upstream proxy's address/CIDR so Caddy trusts its # External reverse-proxy TLS: set to the upstream proxy's address/CIDR so Caddy trusts its
# X-Forwarded-Proto. Leave as 127.0.0.1/32 (trust nothing) when Caddy terminates TLS itself. # X-Forwarded-Proto. Leave as 127.0.0.1/32 (trust nothing) when Caddy terminates TLS itself.
TRUSTED_PROXY_CIDR=127.0.0.1/32 TRUSTED_PROXY_CIDR=127.0.0.1/32

View File

@ -13,6 +13,16 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui
_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._ _Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._
## [0.9.10] - 2026-06-19
### Behoben
- **Build-Commit & Branch werden jetzt auch in Produktion angezeigt** (System → Version & Releases,
Karte „Installiert"). Sie wurden zur Laufzeit aus `.git` gelesen — das im Docker-Prod-Image
fehlt — also stand dort „Build —" und als Branch der Kanal. `install.sh` bäckt den deployten
Commit (`git rev-parse --short HEAD`) und Branch jetzt aus dem `.git` des Hosts in die `.env`
(`CLUSEV_BUILD_SHA`/`CLUSEV_BUILD_BRANCH`); die Versions-Seite bevorzugt diese und liest im Dev
weiterhin direkt aus `.git`.
## [0.9.9] - 2026-06-19 ## [0.9.9] - 2026-06-19
### Hinzugefügt ### Hinzugefügt

View File

@ -245,9 +245,19 @@ class Index extends Component
return version_compare(ltrim($tag, 'vV'), ltrim($installed, 'vV'), '>'); return version_compare(ltrim($tag, 'vV'), ltrim($installed, 'vV'), '>');
} }
/** Resolve the current commit from .git without needing the git binary. */ /**
* Resolve the deployed commit + branch. In production this comes from config (install.sh
* baked it into .env from the host's .git the prod image ships none); in dev it falls back
* to a live .git read. Branch defaults to the channel only when nothing is known.
*/
private function build(): array private function build(): array
{ {
$sha = config('clusev.build.sha');
$branch = config('clusev.build.branch');
if ($sha || $branch) {
return ['sha' => $sha, 'branch' => $branch ?: $this->channel()];
}
$root = base_path('.git'); $root = base_path('.git');
$head = @file_get_contents($root.'/HEAD'); $head = @file_get_contents($root.'/HEAD');

View File

@ -1,9 +1,16 @@
<?php <?php
return [ return [
// First tagged release is v0.1.0 (semantic, not -dev). The live build hash // First tagged release is v0.1.0 (semantic, not -dev).
// is resolved from .git at runtime (see App\Livewire\Versions\Index). 'version' => '0.9.10',
'version' => '0.9.9',
// Deployed commit + branch. install.sh bakes these into .env from the host's .git (the prod
// image ships no .git); the Versions page prefers them and falls back to a live .git read in
// dev. Empty in dev (no install.sh run) → the .git fallback fills them in.
'build' => [
'sha' => env('CLUSEV_BUILD_SHA') ?: null,
'branch' => env('CLUSEV_BUILD_BRANCH') ?: null,
],
// Default user channel. Only 'stable' and 'beta' are ever offered to users. // Default user channel. Only 'stable' and 'beta' are ever offered to users.
'channel' => 'stable', 'channel' => 'stable',

View File

@ -208,6 +208,12 @@ if [ -n "$DOMAIN" ]; then force_kv SESSION_SECURE_COOKIE true; else force_kv SES
# Containers run as the dedicated clusev user (it owns the install dir + ./run). # Containers run as the dedicated clusev user (it owns the install dir + ./run).
force_kv HOST_UID "$(id -u clusev)" force_kv HOST_UID "$(id -u clusev)"
force_kv HOST_GID "$(id -g clusev)" force_kv HOST_GID "$(id -g clusev)"
# Bake the deployed commit + branch into .env so the dashboard's Version page can show them in
# production. The prod image ships NO .git (dockerignored), so a runtime .git read is impossible
# there; we capture it here on the HOST (where .git exists) and config:cache freezes it later.
git config --global --add safe.directory "$(pwd)" 2>/dev/null || true
force_kv CLUSEV_BUILD_SHA "$(git rev-parse --short=7 HEAD 2>/dev/null || true)"
force_kv CLUSEV_BUILD_BRANCH "$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
info "Secrets ok; Proxy/URL aus APP_DOMAIN abgeleitet" info "Secrets ok; Proxy/URL aus APP_DOMAIN abgeleitet"
# ── [4/9] image ────────────────────────────────────────────────────── # ── [4/9] image ──────────────────────────────────────────────────────

View File

@ -127,6 +127,18 @@ class VersionUpdateCheckTest extends TestCase
); );
} }
public function test_build_info_prefers_the_baked_config_over_a_git_read(): void
{
// Production bakes sha/branch into config via .env (no .git in the image).
config()->set('clusev.build.sha', 'abc1234');
config()->set('clusev.build.branch', 'feat/v1-foundation');
Livewire::test(Index::class)
->assertViewHas('build', ['sha' => 'abc1234', 'branch' => 'feat/v1-foundation'])
->assertSee('abc1234')
->assertSee('feat/v1-foundation');
}
public function test_request_update_is_throttled_per_user(): void public function test_request_update_is_throttled_per_user(): void
{ {
config()->set('clusev.version', '0.9.4'); config()->set('clusev.version', '0.9.4');