diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index eb0f79e..2067f9b 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -104,18 +104,21 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Generate image tags + - name: Generate image tags and version id: tags run: | TAGS="${{ env.IMAGE }}:${{ github.sha }}" + BUILD_VERSION="dev" if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then TAGS="$TAGS,${{ env.IMAGE }}:latest" elif [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then TAGS="$TAGS,${{ env.IMAGE }}:dev" elif [[ "${{ github.ref }}" == refs/tags/* ]]; then TAGS="$TAGS,${{ env.IMAGE }}:latest,${{ env.IMAGE }}:${{ github.ref_name }}" + BUILD_VERSION="${{ github.ref_name }}" fi echo "value=$TAGS" >> $GITHUB_OUTPUT + echo "build_version=$BUILD_VERSION" >> $GITHUB_OUTPUT - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -133,5 +136,6 @@ jobs: context: . push: true tags: ${{ steps.tags.outputs.value }} + build-args: BUILD_VERSION=${{ steps.tags.outputs.build_version }} cache-from: type=registry,ref=${{ env.IMAGE }}:cache cache-to: type=registry,ref=${{ env.IMAGE }}:cache,mode=max diff --git a/Dockerfile b/Dockerfile index 8a8275d..699e156 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,5 +21,10 @@ COPY alembic/ alembic/ # Ensure Python finds the source tree (where static files live) before site-packages ENV PYTHONPATH=/app/src +# Version is injected at build time via --build-arg BUILD_VERSION=YY.MM.DD.N +# Falls back to "dev" for local / untagged builds +ARG BUILD_VERSION=dev +ENV APP_VERSION=$BUILD_VERSION + EXPOSE 5000 CMD ["sh", "-c", "alembic upgrade head && hypercorn 'fabledassistant.app:create_app()' --bind 0.0.0.0:5000 --keep-alive 600"] diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 2cb95e6..89d67d6 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -28,6 +28,7 @@ const saving = ref(false); const saved = ref(false); const exporting = ref(false); const restoring = ref(false); +const appVersion = ref('dev'); const restoreFileInput = ref(null); // Migrate stored "admin" → "config"; unknown tabs fall back to "general" @@ -176,6 +177,10 @@ const searchLoading = ref(false); const searchError = ref(""); onMounted(async () => { + try { + const v = await apiGet<{ version: string }>('/api/version') + appVersion.value = v.version + } catch { /* non-critical */ } await store.fetchSettings(); pushStore.checkSubscription(); assistantName.value = store.assistantName; @@ -1031,6 +1036,11 @@ function formatUserDate(iso: string): string { +
+

About

+

Fabled Assistant {{ appVersion }}

+
+ @@ -2206,6 +2216,25 @@ function formatUserDate(iso: string): string { margin-right: 0.25rem; } +/* ── About / version ─────────────────────────────────────────── */ +.version-line { + margin: 0; + font-size: 0.9rem; + color: var(--color-text-secondary); + display: flex; + align-items: center; + gap: 0.6rem; +} +.version-badge { + font-family: ui-monospace, monospace; + font-size: 0.8rem; + padding: 0.15rem 0.5rem; + background: var(--color-bg-secondary); + border: 1px solid var(--color-border); + border-radius: 4px; + color: var(--color-text-muted); +} + /* ── Groups tab ──────────────────────────────────────────────── */ .btn-primary { padding: 0.4rem 0.9rem; diff --git a/src/fabledassistant/routes/api.py b/src/fabledassistant/routes/api.py index 9f8af18..a15fd84 100644 --- a/src/fabledassistant/routes/api.py +++ b/src/fabledassistant/routes/api.py @@ -1,3 +1,5 @@ +import os + from quart import Blueprint, jsonify api = Blueprint("api", __name__, url_prefix="/api") @@ -6,3 +8,8 @@ api = Blueprint("api", __name__, url_prefix="/api") @api.route("/health") async def health(): return jsonify({"status": "ok"}) + + +@api.route("/version") +async def version(): + return jsonify({"version": os.environ.get("APP_VERSION", "dev")})