diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
index ab9283fa02..1802e8f336 100644
--- a/.github/workflows/docker.yaml
+++ b/.github/workflows/docker.yaml
@@ -136,6 +136,7 @@ jobs:
sbom: true
build-args: |
BUILD_HASH=${{ github.sha }}
+ BUILD_CHANNEL=${{ github.ref == 'refs/heads/dev' && 'dev' || (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) && 'main' || 'unknown' }}
${{ matrix.variant.build_args }}
- name: Export digest
diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml
index 9995ccedae..40cebc5028 100644
--- a/.github/workflows/release-pypi.yml
+++ b/.github/workflows/release-pypi.yml
@@ -28,6 +28,9 @@ jobs:
with:
python-version: 3.11
- name: Build
+ env:
+ APP_BUILD_CHANNEL: main
+ APP_BUILD_HASH: ${{ github.sha }}
run: |
python -m pip install --upgrade pip
pip install build
diff --git a/Dockerfile b/Dockerfile
index 08d7a71eec..4bf9cfc57c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -19,6 +19,7 @@ ARG USE_AUXILIARY_EMBEDDING_MODEL=TaylorAI/bge-micro-v2
ARG USE_TIKTOKEN_ENCODING_NAME="cl100k_base"
ARG BUILD_HASH=dev-build
+ARG BUILD_CHANNEL=unknown
# Override at your own risk - non-root configurations are untested
ARG UID=0
ARG GID=0
@@ -26,6 +27,7 @@ ARG GID=0
######## WebUI frontend ########
FROM --platform=$BUILDPLATFORM node:22-alpine3.20 AS build
ARG BUILD_HASH
+ARG BUILD_CHANNEL
ARG USE_SLIM
ARG UID
ARG GID
@@ -43,6 +45,7 @@ RUN npm ci --force
COPY . .
ENV APP_BUILD_HASH=${BUILD_HASH}
+ENV APP_BUILD_CHANNEL=${BUILD_CHANNEL}
RUN npm run build && \
if [ "$USE_SLIM" = "true" ]; then find build -type f -name '*.map' -delete; fi
@@ -225,7 +228,7 @@ RUN if [ "$USE_PERMISSION_HARDENING" = "true" ]; then \
USER $UID:$GID
ARG BUILD_HASH
-ENV WEBUI_BUILD_VERSION=${BUILD_HASH}
+ENV WEBUI_BUILD_HASH=${BUILD_HASH}
ENV DOCKER=true
CMD [ "bash", "start.sh"]
diff --git a/hatch_build.py b/hatch_build.py
index bf47545680..623b368b78 100644
--- a/hatch_build.py
+++ b/hatch_build.py
@@ -17,7 +17,6 @@ class CustomBuildHook(BuildHookInterface):
stderr.write('### npm install\n')
subprocess.run([npm, 'install', '--force'], check=True) # noqa: S603
stderr.write('\n### npm run build\n')
- os.environ['APP_BUILD_HASH'] = version
node_options = os.environ.get('NODE_OPTIONS', '')
if '--max-old-space-size' not in node_options:
os.environ['NODE_OPTIONS'] = f'{node_options} --max-old-space-size=8192'.strip()
diff --git a/scripts/build-info.js b/scripts/build-info.js
new file mode 100644
index 0000000000..c8b8816d9e
--- /dev/null
+++ b/scripts/build-info.js
@@ -0,0 +1,43 @@
+import { execFileSync } from 'node:child_process';
+import { readFileSync } from 'node:fs';
+import { join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+export function resolveBuildInfo({
+ env = process.env,
+ cwd = fileURLToPath(new URL('../', import.meta.url))
+} = {}) {
+ const git = (...args) => {
+ try {
+ return execFileSync('git', args, {
+ cwd,
+ encoding: 'utf8',
+ stdio: ['ignore', 'pipe', 'ignore']
+ }).trim();
+ } catch {
+ return '';
+ }
+ };
+
+ const suppliedHash = env.APP_BUILD_HASH ?? '';
+ const hash = /^(?:[a-f\d]{7,40}|[a-f\d]{64})$/i.test(suppliedHash)
+ ? suppliedHash
+ : git('rev-parse', 'HEAD');
+ let channel = env.APP_BUILD_CHANNEL;
+ if (!['main', 'dev', 'unknown'].includes(channel)) {
+ const branch = git('branch', '--show-current');
+ channel = branch === 'main' || branch === 'dev' ? branch : 'unknown';
+ if (!branch && hash) {
+ try {
+ const { version } = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
+ if (git('tag', '--points-at', 'HEAD').split('\n').includes(`v${version}`)) {
+ channel = 'main';
+ }
+ } catch {
+ // Source archives may have neither Git metadata nor a package version.
+ }
+ }
+ }
+
+ return { channel, hash };
+}
diff --git a/src/app.d.ts b/src/app.d.ts
index f59b884c51..e3b97f2232 100644
--- a/src/app.d.ts
+++ b/src/app.d.ts
@@ -1,6 +1,10 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
+ const APP_VERSION: string;
+ const APP_BUILD_HASH: string;
+ const APP_BUILD_CHANNEL: 'main' | 'dev' | 'unknown';
+
namespace App {
// interface Error {}
// interface Locals {}
diff --git a/src/lib/components/admin/Settings/General.svelte b/src/lib/components/admin/Settings/General.svelte
index 88b4412db7..65f91be375 100644
--- a/src/lib/components/admin/Settings/General.svelte
+++ b/src/lib/components/admin/Settings/General.svelte
@@ -9,8 +9,8 @@
import SettingsSelect from '$lib/components/common/SettingsSelect.svelte';
import LanguageModeSelect from '$lib/components/common/LanguageModeSelect.svelte';
import Switch from '$lib/components/common/Switch.svelte';
- import Tooltip from '$lib/components/common/Tooltip.svelte';
- import { WEBUI_BUILD_HASH, WEBUI_VERSION } from '$lib/constants';
+ import BuildVersion from '$lib/components/common/BuildVersion.svelte';
+ import { WEBUI_BUILD_CHANNEL, WEBUI_VERSION } from '$lib/constants';
import { banners as _banners, config, showChangelog } from '$lib/stores';
import type { Banner } from '$lib/types';
import { compareVersion } from '$lib/utils';
@@ -112,7 +112,7 @@
banners = [...$_banners];
- if ($config?.features?.enable_version_update_check) {
+ if (WEBUI_BUILD_CHANNEL === 'main' && $config?.features?.enable_version_update_check) {
checkForVersionUpdates();
}
});
@@ -137,9 +137,9 @@
{$i18n.t('settings.admin.general.version.label')}
- v{WEBUI_VERSION}
+
- {#if $config?.features?.enable_version_update_check}
+ {#if WEBUI_BUILD_CHANNEL === 'main' && $config?.features?.enable_version_update_check}
{#if version.latest === null}
{$i18n.t('Could not check for updates')}
- {#if $config?.features?.enable_version_update_check}
+ {#if WEBUI_BUILD_CHANNEL === 'main' && $config?.features?.enable_version_update_check}