Merge pull request 'fix(web): download button + server version display' (#37) from dev into main
This commit was merged in pull request #37.
This commit is contained in:
@@ -50,9 +50,11 @@ jobs:
|
|||||||
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
||||||
VERSION="${GITHUB_REF#refs/tags/}"
|
VERSION="${GITHUB_REF#refs/tags/}"
|
||||||
echo "args=-t ${IMAGE}:${VERSION} -t ${IMAGE}:latest" >> "$GITHUB_OUTPUT"
|
echo "args=-t ${IMAGE}:${VERSION} -t ${IMAGE}:latest" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "::notice::Release build: ${VERSION} + latest"
|
echo "::notice::Release build: ${VERSION} + latest"
|
||||||
else
|
else
|
||||||
echo "args=-t ${IMAGE}:main" >> "$GITHUB_OUTPUT"
|
echo "args=-t ${IMAGE}:main" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "version=main" >> "$GITHUB_OUTPUT"
|
||||||
echo "::notice::Main-branch build: :main"
|
echo "::notice::Main-branch build: :main"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -93,4 +95,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
if: steps.guard.outputs.ready == 'true'
|
if: steps.guard.outputs.ready == 'true'
|
||||||
run: docker buildx build --push ${{ steps.tags.outputs.args }} .
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--build-arg MINSTREL_VERSION="${{ steps.tags.outputs.version }}" \
|
||||||
|
--push ${{ steps.tags.outputs.args }} .
|
||||||
|
|||||||
+7
-1
@@ -15,7 +15,13 @@ COPY . .
|
|||||||
# Overwrite the committed placeholder with the freshly-built SPA assets.
|
# Overwrite the committed placeholder with the freshly-built SPA assets.
|
||||||
COPY --from=web /web/build ./web/build
|
COPY --from=web /web/build ./web/build
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
RUN go build -trimpath -ldflags="-s -w" -o /out/minstrel ./cmd/minstrel
|
# Version stamping: release.yml passes the git tag via MINSTREL_VERSION
|
||||||
|
# build-arg; local `docker build` falls back to "dev". Surfaced at
|
||||||
|
# /healthz for operator-side image-version verification.
|
||||||
|
ARG MINSTREL_VERSION=dev
|
||||||
|
RUN go build -trimpath \
|
||||||
|
-ldflags="-s -w -X 'git.fabledsword.com/bvandeusen/minstrel/internal/server.ServerVersion=${MINSTREL_VERSION}'" \
|
||||||
|
-o /out/minstrel ./cmd/minstrel
|
||||||
|
|
||||||
FROM debian:bookworm-slim
|
FROM debian:bookworm-slim
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ func (s *Server) handleHealthz(w http.ResponseWriter, _ *http.Request) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_ = json.NewEncoder(w).Encode(map[string]string{
|
_ = json.NewEncoder(w).Encode(map[string]string{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
|
"version": ServerVersion,
|
||||||
"min_client_version": MinClientVersion,
|
"min_client_version": MinClientVersion,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,13 @@ package server
|
|||||||
// accepts. Bump it when a server change requires a paired client update;
|
// accepts. Bump it when a server change requires a paired client update;
|
||||||
// older clients see version_too_old at /healthz and refuse to operate.
|
// older clients see version_too_old at /healthz and refuse to operate.
|
||||||
const MinClientVersion = "0.1.0"
|
const MinClientVersion = "0.1.0"
|
||||||
|
|
||||||
|
// ServerVersion is the deployed server image's version tag. Defaults to
|
||||||
|
// "dev" for local builds; overridden at link time via:
|
||||||
|
//
|
||||||
|
// -ldflags="-X 'git.fabledsword.com/bvandeusen/minstrel/internal/server.ServerVersion=v2026.05.10.2'"
|
||||||
|
//
|
||||||
|
// release.yml passes the git tag through MINSTREL_VERSION build-arg →
|
||||||
|
// Dockerfile ldflag. Surfaced at /healthz so operators can verify which
|
||||||
|
// image their container is running without exec'ing into it.
|
||||||
|
var ServerVersion = "dev"
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
// bandwidth abuse on the APK stream, so this component only shows
|
// bandwidth abuse on the APK stream, so this component only shows
|
||||||
// for logged-in users. Mount in Settings, never in pre-auth surfaces.
|
// for logged-in users. Mount in Settings, never in pre-auth surfaces.
|
||||||
|
|
||||||
|
// Server returns snake_case (clientVersionResponse in
|
||||||
|
// internal/api/client_assets.go). Map to a camelCase struct for
|
||||||
|
// the rest of the component.
|
||||||
|
type WireInfo = { version: string; apk_url: string; size_bytes: number };
|
||||||
type Info = { version: string; apkUrl: string; sizeBytes: number };
|
type Info = { version: string; apkUrl: string; sizeBytes: number };
|
||||||
|
|
||||||
let info = $state<Info | null>(null);
|
let info = $state<Info | null>(null);
|
||||||
@@ -22,12 +26,12 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
const body = await api.get<Partial<Info>>('/api/client/version');
|
const body = await api.get<Partial<WireInfo>>('/api/client/version');
|
||||||
if (!body.version || !body.apkUrl) return;
|
if (!body.version || !body.apk_url) return;
|
||||||
info = {
|
info = {
|
||||||
version: body.version,
|
version: body.version,
|
||||||
apkUrl: body.apkUrl,
|
apkUrl: body.apk_url,
|
||||||
sizeBytes: body.sizeBytes ?? 0
|
sizeBytes: body.size_bytes ?? 0
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
// 404 (no APK), 401 (somehow unauthed), network error — silent.
|
// 404 (no APK), 401 (somehow unauthed), network error — silent.
|
||||||
@@ -36,17 +40,23 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if info}
|
{#if info}
|
||||||
<a
|
<section class="space-y-3 rounded border border-border bg-surface p-4">
|
||||||
href={info.apkUrl}
|
<h2 class="text-lg font-semibold">Mobile app</h2>
|
||||||
download="minstrel.apk"
|
<p class="text-sm text-text-secondary">
|
||||||
class="mobile-app-download flex items-center gap-3 rounded-md border border-border bg-surface px-3 py-2 text-sm text-text-primary no-underline transition-colors hover:bg-surface-hover hover:text-accent focus-visible:ring-2 focus-visible:ring-accent"
|
Install the Android app paired to this server. Sign in with the same account.
|
||||||
>
|
</p>
|
||||||
<Smartphone size={18} strokeWidth={1.5} class="shrink-0 text-accent" />
|
<a
|
||||||
<span class="flex flex-col leading-tight">
|
href={info.apkUrl}
|
||||||
<span class="font-medium">Get the Android app</span>
|
download="minstrel.apk"
|
||||||
<span class="text-xs text-text-secondary">
|
class="mobile-app-download flex items-center gap-3 rounded-md border border-border bg-surface px-3 py-2 text-sm text-text-primary no-underline transition-colors hover:bg-surface-hover hover:text-accent focus-visible:ring-2 focus-visible:ring-accent"
|
||||||
{info.version}{info.sizeBytes > 0 ? ` · ${formatSize(info.sizeBytes)}` : ''}
|
>
|
||||||
|
<Smartphone size={18} strokeWidth={1.5} class="shrink-0 text-accent" />
|
||||||
|
<span class="flex flex-col leading-tight">
|
||||||
|
<span class="font-medium">Get the Android app</span>
|
||||||
|
<span class="text-xs text-text-secondary">
|
||||||
|
{info.version}{info.sizeBytes > 0 ? ` · ${formatSize(info.sizeBytes)}` : ''}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</a>
|
||||||
</a>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
// Reads /healthz on mount and surfaces the deployed server version
|
||||||
|
// as understated text. Useful when verifying which image a container
|
||||||
|
// is actually running (came up debugging the in-app update flow when
|
||||||
|
// it wasn't obvious whether v2026.05.10.0 or .1 was deployed).
|
||||||
|
//
|
||||||
|
// /healthz is unauthenticated, so the bare fetch works without
|
||||||
|
// credentials. Renders nothing on parse failure or pre-version
|
||||||
|
// images that don't include the field — graceful degradation.
|
||||||
|
|
||||||
|
type Health = { status: string; version?: string };
|
||||||
|
|
||||||
|
let version = $state<string | null>(null);
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/healthz');
|
||||||
|
if (!res.ok) return;
|
||||||
|
const body = (await res.json()) as Partial<Health>;
|
||||||
|
if (body.version && body.version !== 'dev') {
|
||||||
|
version = body.version;
|
||||||
|
} else if (body.version === 'dev') {
|
||||||
|
// Local dev images report "dev" — show it so the operator
|
||||||
|
// can tell they're not on a release tag.
|
||||||
|
version = 'dev';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// network / parse error — silent.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if version}
|
||||||
|
<p class="text-xs text-text-secondary">Server {version}</p>
|
||||||
|
{/if}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
import { errCode } from '$lib/api/errors';
|
import { errCode } from '$lib/api/errors';
|
||||||
import { pushToast } from '$lib/stores/toast.svelte';
|
import { pushToast } from '$lib/stores/toast.svelte';
|
||||||
import MobileAppDownload from '$lib/components/MobileAppDownload.svelte';
|
import MobileAppDownload from '$lib/components/MobileAppDownload.svelte';
|
||||||
|
import ServerVersion from '$lib/components/ServerVersion.svelte';
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
@@ -325,11 +326,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3 rounded border border-border bg-surface p-4">
|
<MobileAppDownload />
|
||||||
<h2 class="text-lg font-semibold">Mobile app</h2>
|
|
||||||
<p class="text-sm text-text-secondary">
|
<div class="pt-2 text-center">
|
||||||
Install the Android app paired to this server. Sign in with the same account.
|
<ServerVersion />
|
||||||
</p>
|
</div>
|
||||||
<MobileAppDownload />
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user