diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 25ae03d2..25b17016 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -117,6 +117,7 @@ jobs: build-args: | BETTERDESK_COMMIT_SHA=${{ github.sha }} BETTERDESK_IMAGE_VERSION=${{ needs.read-version.outputs.version }} + BETTERDESK_PRODUCT_VERSION=${{ needs.read-version.outputs.version }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 @@ -222,6 +223,7 @@ jobs: build-args: | BETTERDESK_COMMIT_SHA=${{ github.sha }} BETTERDESK_IMAGE_VERSION=${{ needs.read-version.outputs.version }} + BETTERDESK_PRODUCT_VERSION=${{ needs.read-version.outputs.version }} cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/version-bump-dev.yml b/.github/workflows/version-bump-dev.yml index c0b5a285..f4ee6fd2 100644 --- a/.github/workflows/version-bump-dev.yml +++ b/.github/workflows/version-bump-dev.yml @@ -41,9 +41,7 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add VERSION web-nodejs/package.json betterdesk.sh betterdesk.ps1 betterdesk-docker.sh \ - CHANGELOG.md README.md Dockerfile Dockerfile.server Dockerfile.console \ - docker-compose.quick.yml docker/entrypoint.sh docker-entrypoint.sh + git add $(node scripts/bump-version.js --list-paths) if git diff --cached --quiet; then echo "No version files changed — skipping commit" exit 0 diff --git a/.github/workflows/version-bump-main.yml b/.github/workflows/version-bump-main.yml index 050f560a..2b02a795 100644 --- a/.github/workflows/version-bump-main.yml +++ b/.github/workflows/version-bump-main.yml @@ -46,9 +46,7 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add VERSION web-nodejs/package.json betterdesk.sh betterdesk.ps1 betterdesk-docker.sh \ - CHANGELOG.md README.md Dockerfile Dockerfile.server Dockerfile.console \ - docker-compose.quick.yml docker/entrypoint.sh docker-entrypoint.sh + git add $(node scripts/bump-version.js --list-paths) BRANCH="release/v${NEW_VERSION}" git checkout -b "$BRANCH" git commit -m "chore: release version ${NEW_VERSION} [version-bump]" diff --git a/.github/workflows/version-verify.yml b/.github/workflows/version-verify.yml index c99835c0..71f95e4e 100644 --- a/.github/workflows/version-verify.yml +++ b/.github/workflows/version-verify.yml @@ -5,6 +5,8 @@ on: branches: [main, dev] paths: - 'VERSION' + - 'betterdesk-server/VERSION' + - 'betterdesk-server/internal/productversion/VERSION' - 'web-nodejs/package.json' - 'betterdesk.sh' - 'betterdesk.ps1' @@ -21,6 +23,8 @@ on: branches: [main, dev] paths: - 'VERSION' + - 'betterdesk-server/VERSION' + - 'betterdesk-server/internal/productversion/VERSION' - 'web-nodejs/package.json' - 'betterdesk.sh' - 'betterdesk.ps1' diff --git a/Dockerfile b/Dockerfile index 461831e4..f60ff0c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,11 @@ COPY betterdesk-server/go.mod betterdesk-server/go.sum ./ RUN go mod download COPY betterdesk-server/ . + +ARG BETTERDESK_PRODUCT_VERSION=dev + RUN CGO_ENABLED=0 GOOS=linux go build \ - -ldflags="-s -w" \ + -ldflags="-s -w -X main.Version=${BETTERDESK_PRODUCT_VERSION}" \ -tags "netgo osusergo" \ -o /betterdesk-server . diff --git a/Dockerfile.server b/Dockerfile.server index 9d548d03..d7552ccd 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -21,9 +21,11 @@ RUN go mod download # Copy full source COPY betterdesk-server/ . +ARG BETTERDESK_PRODUCT_VERSION=dev + # Build static binary (modernc.org/sqlite is pure Go — no CGO needed) RUN CGO_ENABLED=0 GOOS=linux go build \ - -ldflags="-s -w" \ + -ldflags="-s -w -X main.Version=${BETTERDESK_PRODUCT_VERSION}" \ -tags "netgo osusergo" \ -o /betterdesk-server . diff --git a/betterdesk-server/VERSION b/betterdesk-server/VERSION new file mode 100644 index 00000000..5365c9e8 --- /dev/null +++ b/betterdesk-server/VERSION @@ -0,0 +1 @@ +3.2.15 diff --git a/betterdesk-server/internal/productversion/VERSION b/betterdesk-server/internal/productversion/VERSION new file mode 100644 index 00000000..5365c9e8 --- /dev/null +++ b/betterdesk-server/internal/productversion/VERSION @@ -0,0 +1 @@ +3.2.15 diff --git a/betterdesk-server/internal/productversion/version.go b/betterdesk-server/internal/productversion/version.go new file mode 100644 index 00000000..3043c637 --- /dev/null +++ b/betterdesk-server/internal/productversion/version.go @@ -0,0 +1,19 @@ +// Package productversion exposes the embedded BetterDesk product semver. +package productversion + +import ( + _ "embed" + "strings" +) + +//go:embed VERSION +var embedded []byte + +// Product returns the embedded product version (X.Y.Z) or "dev" when unset. +func Product() string { + v := strings.TrimSpace(string(embedded)) + if v == "" { + return "dev" + } + return v +} diff --git a/betterdesk-server/internal/productversion/version_test.go b/betterdesk-server/internal/productversion/version_test.go new file mode 100644 index 00000000..c43e9e2e --- /dev/null +++ b/betterdesk-server/internal/productversion/version_test.go @@ -0,0 +1,10 @@ +package productversion + +import "testing" + +func TestProduct_notEmpty(t *testing.T) { + v := Product() + if v == "" { + t.Fatal("Product() returned empty string") + } +} diff --git a/betterdesk-server/main.go b/betterdesk-server/main.go index f830384a..9878cee1 100644 --- a/betterdesk-server/main.go +++ b/betterdesk-server/main.go @@ -26,6 +26,7 @@ import ( "github.com/unitronix/betterdesk-server/config" "github.com/unitronix/betterdesk-server/crypto" "github.com/unitronix/betterdesk-server/db" + "github.com/unitronix/betterdesk-server/internal/productversion" "github.com/unitronix/betterdesk-server/logging" "github.com/unitronix/betterdesk-server/metrics" "github.com/unitronix/betterdesk-server/ratelimit" @@ -41,6 +42,14 @@ var ( BuildDate = "unknown" ) +func init() { + if Version == "dev" { + if v := productversion.Product(); v != "" && v != "dev" { + Version = v + } + } +} + func main() { cfg := parseFlags() diff --git a/betterdesk.sh b/betterdesk.sh index 74d0753f..5e6a8173 100755 --- a/betterdesk.sh +++ b/betterdesk.sh @@ -1419,7 +1419,7 @@ compile_go_server() { go mod download # Build with optimizations - CGO_ENABLED=0 go build -ldflags="-s -w" -o "$output_name" . + CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=${VERSION}" -o "$output_name" . if [ -f "$output_name" ]; then chmod +x "$output_name" diff --git a/docker-compose.quick.macvlan.yml b/docker-compose.quick.macvlan.yml index 675ac800..d796b448 100644 --- a/docker-compose.quick.macvlan.yml +++ b/docker-compose.quick.macvlan.yml @@ -28,13 +28,13 @@ # (issue #186): use service_started (not service_healthy), pin image tags, and # set DB_PATH=/app/data/db_v2.sqlite3 plus AUTH_DB_PATH on the server. # -# Image tag (aligned with CHANGELOG / git tag v3.2.14): -# Default: 3.2.14 | Rolling: BETTERDESK_IMAGE_TAG=latest +# Image tag (aligned with CHANGELOG / git tag v3.2.15): +# Default: 3.2.15 | Rolling: BETTERDESK_IMAGE_TAG=latest # ============================================================================= services: server: - image: ghcr.io/unitronix/betterdesk-server:${BETTERDESK_IMAGE_TAG:-3.2.14} + image: ghcr.io/unitronix/betterdesk-server:${BETTERDESK_IMAGE_TAG:-3.2.15} container_name: betterdesk-server hostname: betterdesk-server command: ["/usr/local/bin/betterdesk-server", "-mode", "all", "-api-port", "21114", "-key-file", "/opt/rustdesk/id_ed25519"] @@ -70,7 +70,7 @@ services: start_period: 60s console: - image: ghcr.io/unitronix/betterdesk-console:${BETTERDESK_IMAGE_TAG:-3.2.14} + image: ghcr.io/unitronix/betterdesk-console:${BETTERDESK_IMAGE_TAG:-3.2.15} container_name: betterdesk-console # Shares server network stack — panel and RustDesk ports use MACVLAN_IPV4. network_mode: service:server @@ -97,7 +97,7 @@ services: - WS_HBBR_PORT=21117 - DOCKER=true - BETTERDESK_UPDATE_MODE=image - - BETTERDESK_IMAGE_TAG=${BETTERDESK_IMAGE_TAG:-3.2.14} + - BETTERDESK_IMAGE_TAG=${BETTERDESK_IMAGE_TAG:-3.2.15} - TZ=${TZ:-UTC} depends_on: # service_started (not healthy): avoids deadlock with auth.db on first boot (#138, #186). diff --git a/docs/important/branching-and-versioning.md b/docs/important/branching-and-versioning.md index ca563841..8366e91e 100644 --- a/docs/important/branching-and-versioning.md +++ b/docs/important/branching-and-versioning.md @@ -39,7 +39,8 @@ Every automated bump updates these together: **Tier 1** - `VERSION` -- `web-nodejs/package.json` +- `web-nodejs/package.json` (npm mirror; panel reads root `VERSION` via `web-nodejs/lib/productVersion.js`) +- `betterdesk-server/VERSION` and `betterdesk-server/internal/productversion/VERSION` (Go `go:embed` + builds) - `betterdesk.sh`, `betterdesk.ps1`, `betterdesk-docker.sh` - `CHANGELOG.md` (moves `[Unreleased]` → new section) - `README.md` (version badge) @@ -47,9 +48,11 @@ Every automated bump updates these together: **Tier 2 (Docker)** - `Dockerfile`, `Dockerfile.server`, `Dockerfile.console` -- `docker-compose.quick.yml` +- `docker-compose.quick.yml`, `docker-compose.quick.macvlan.yml` - `docker/entrypoint.sh`, `docker-entrypoint.sh` +The web console and Go server should report the **same product semver** after a normal install, panel update, or release build. Settings → Server Information shows both the product version and the live Go server version from `/api/health`. + Sub-project versions (`betterdesk-mgmt`, `betterdesk-agent-client`, SDKs) are **not** bumped with the main product unless explicitly requested. ### Maintainer commands @@ -58,6 +61,9 @@ Sub-project versions (`betterdesk-mgmt`, `betterdesk-agent-client`, SDKs) are ** # Verify all Tier 1/2 files match VERSION (CI runs this too) node scripts/bump-version.js --verify +# List every path touched by automated bumps (used by CI git add) +node scripts/bump-version.js --list-paths + # Manual bump (normally CI handles this) node scripts/bump-version.js --patch # +0.0.1 node scripts/bump-version.js --minor # +0.1.0 diff --git a/scripts/bump-version.js b/scripts/bump-version.js index fedfb207..1c9d63d9 100644 --- a/scripts/bump-version.js +++ b/scripts/bump-version.js @@ -152,8 +152,30 @@ const FILE_RULES = [ `\${BETTERDESK_IMAGE_VERSION:-${version}}` ), }, + { + id: 'betterdesk-server-version', + path: 'betterdesk-server/VERSION', + extract: (content) => content.trim(), + apply: (_content, version) => `${version}\n`, + }, + { + id: 'betterdesk-server-productversion-embed', + path: 'betterdesk-server/internal/productversion/VERSION', + extract: (content) => content.trim(), + apply: (_content, version) => `${version}\n`, + }, ]; +const CHANGELOG_PATH = 'CHANGELOG.md'; + +function getManagedPaths() { + const paths = FILE_RULES.map((rule) => rule.path); + if (!paths.includes(CHANGELOG_PATH)) { + paths.push(CHANGELOG_PATH); + } + return paths; +} + function escapeRegExp(value) { return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } @@ -308,12 +330,13 @@ function verifyVersions() { } function printUsage() { - console.log(`Usage: node scripts/bump-version.js [--patch | --minor | --set X.Y.Z] [--dry-run] [--verify] + console.log(`Usage: node scripts/bump-version.js [--patch | --minor | --set X.Y.Z] [--dry-run] [--verify] [--list-paths] --patch Bump patch (+0.0.1) --minor Bump minor (+0.1.0, reset patch) --set X.Y.Z Set explicit version --verify Exit 1 if Tier 1/2 files disagree with VERSION + --list-paths Print managed file paths (one per line) for CI git add --dry-run Print planned version without writing files`); } @@ -321,6 +344,14 @@ function main() { const args = process.argv.slice(2); const dryRun = args.includes('--dry-run'); const verify = args.includes('--verify'); + const listPaths = args.includes('--list-paths'); + + if (listPaths) { + for (const filePath of getManagedPaths()) { + console.log(filePath); + } + return; + } if (verify) { verifyVersions(); @@ -365,6 +396,7 @@ if (require.main === module) { module.exports = { FILE_RULES, + getManagedPaths, readCanonicalVersion, bumpPatch, bumpMinor, diff --git a/web-nodejs/config/config.js b/web-nodejs/config/config.js index 3c4a1ae3..9aab633f 100644 --- a/web-nodejs/config/config.js +++ b/web-nodejs/config/config.js @@ -30,12 +30,8 @@ if (fs.existsSync(_envFile)) { } catch (_e) { /* .env read failed — continue with existing env vars */ } } -// Read version from package.json -let pkgVersion = '2.0.0'; -try { - const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); - pkgVersion = pkg.version || pkgVersion; -} catch (e) { /* use default */ } +const { readProductVersion } = require('../lib/productVersion'); +const pkgVersion = readProductVersion({ consoleDir: path.join(__dirname, '..') }); // Environment / platform detection const NODE_ENV = process.env.NODE_ENV || 'development'; diff --git a/web-nodejs/lang/ar.json b/web-nodejs/lang/ar.json index 3aea8fb6..6490ec48 100644 --- a/web-nodejs/lang/ar.json +++ b/web-nodejs/lang/ar.json @@ -582,7 +582,9 @@ "enrollment_tag_picker": "اختر علامات من علامات الأجهزة الموجودة", "enrollment_tag_picker_hint": "يتيح للمشغلين اختيار علامات من القائمة المستخدمة في صفحة الأجهزة بدلًا من الكتابة فقط.", "enrollment_open_requests": "فتح طلبات التسجيل", - "enrollment_pending_count": "{count} طلب تسجيل معلق" + "enrollment_pending_count": "{count} طلب تسجيل معلق", + "go_server_version": "خادم Go", + "go_server_version_dev_hint": "الثنائي Go يبلغ عن بناء تطويري — أعد البناء أو حدّث الخادم ليطابق إصدار المنتج" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/cs.json b/web-nodejs/lang/cs.json index fbf97e7f..f742b389 100644 --- a/web-nodejs/lang/cs.json +++ b/web-nodejs/lang/cs.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "Wybor tagow z listy istniejacych", "enrollment_tag_picker_hint": "Umozliwia wybor tagow z listy uzywanej na stronie Urzadzenia zamiast samego wpisywania.", "enrollment_open_requests": "Otworz zadania rejestracji", - "enrollment_pending_count": "{count} oczekujacych zadan rejestracji" + "enrollment_pending_count": "{count} oczekujacych zadan rejestracji", + "go_server_version": "Go server", + "go_server_version_dev_hint": "Binární Go server hlásí vývojovou sestavu — přestavte nebo aktualizujte server, aby odpovídal verzi produktu" }, "audit": { "time": "Čas", diff --git a/web-nodejs/lang/da.json b/web-nodejs/lang/da.json index 90d0f21e..7f96484d 100644 --- a/web-nodejs/lang/da.json +++ b/web-nodejs/lang/da.json @@ -581,7 +581,9 @@ "advanced_file_build-env": "Agent-Build-Umgebung (/etc/betterdesk/build.env)", "advanced_file_docker-supervisord": "Docker: supervisord.conf (supervisor-konfiguration)", "advanced_file_docker-compose": "Docker: docker-compose.yml (orkestrering)", - "advanced_restart_none": "Für diese Fil ist kein Tjenesteneustart erforderlich." + "advanced_restart_none": "Für diese Fil ist kein Tjenesteneustart erforderlich.", + "go_server_version": "Go-server", + "go_server_version_dev_hint": "Go-binær rapporterer udviklingsbuild — genopbyg eller opdater serveren for at matche produktversionen" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/de.json b/web-nodejs/lang/de.json index 9443295f..7218cc93 100644 --- a/web-nodejs/lang/de.json +++ b/web-nodejs/lang/de.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "Tags aus vorhandenen Geräte-Tags auswählen", "enrollment_tag_picker_hint": "Ermöglicht Operatoren, Tags aus der auf der Geräteseite verwendeten Liste auszuwählen, anstatt nur zu tippen.", "enrollment_open_requests": "Offene Registrierungsanfragen", - "enrollment_pending_count": "{count} ausstehende Registrierungsanfrage(n)" + "enrollment_pending_count": "{count} ausstehende Registrierungsanfrage(n)", + "go_server_version": "Go-Server", + "go_server_version_dev_hint": "Go-Binary meldet Entwicklungsbuild — Server neu bauen oder aktualisieren, um die Produktversion anzugleichen" }, "audit": { "time": "Zeit", diff --git a/web-nodejs/lang/en.json b/web-nodejs/lang/en.json index edb2228c..702946c1 100644 --- a/web-nodejs/lang/en.json +++ b/web-nodejs/lang/en.json @@ -575,7 +575,9 @@ "totp_recovery_warning": "Save these recovery codes in a safe place. Each code can only be used once. If you lose access to your authenticator app, you can use these codes to log in.", "totp_done": "Done", "totp_disable_warning": "Disabling two-factor authentication will make your account less secure. Enter your password to confirm.", - "totp_disabled_success": "Two-factor authentication has been disabled." + "totp_disabled_success": "Two-factor authentication has been disabled.", + "go_server_version": "Go server", + "go_server_version_dev_hint": "Go binary reports development build — rebuild or update the server to match the product version" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/es.json b/web-nodejs/lang/es.json index e61674e1..1d5c70ff 100644 --- a/web-nodejs/lang/es.json +++ b/web-nodejs/lang/es.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "Elegir etiquetas de las etiquetas de dispositivos existentes", "enrollment_tag_picker_hint": "Permite a los operadores seleccionar etiquetas de la lista usada en la página de Dispositivos en lugar de solo escribirlas.", "enrollment_open_requests": "Solicitudes de inscripción abiertas", - "enrollment_pending_count": "{count} solicitud(es) de inscripción pendiente(s)" + "enrollment_pending_count": "{count} solicitud(es) de inscripción pendiente(s)", + "go_server_version": "Servidor Go", + "go_server_version_dev_hint": "El binario Go informa compilación de desarrollo — recompile o actualice el servidor para igualar la versión del producto" }, "audit": { "time": "Hora", diff --git a/web-nodejs/lang/fi.json b/web-nodejs/lang/fi.json index d3ae7ac0..cf7722a4 100644 --- a/web-nodejs/lang/fi.json +++ b/web-nodejs/lang/fi.json @@ -581,7 +581,9 @@ "advanced_file_build-env": "Agent-Build-Umgebung (/etc/betterdesk/build.env)", "advanced_file_docker-supervisord": "Docker: supervisord.conf (supervisor-asetukset)", "advanced_file_docker-compose": "Docker: docker-compose.yml (orkestrointi)", - "advanced_restart_none": "Für diese Tiedosto ist kein Palveluneustart erforderlich." + "advanced_restart_none": "Für diese Tiedosto ist kein Palveluneustart erforderlich.", + "go_server_version": "Go-palvelin", + "go_server_version_dev_hint": "Go-binääri raportoi kehitysversion — rakenna palvelin uudelleen tai päivitä se vastaamaan tuoteversiota" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/fr.json b/web-nodejs/lang/fr.json index 55753b11..7412a4f4 100644 --- a/web-nodejs/lang/fr.json +++ b/web-nodejs/lang/fr.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "Sélectionner des tags parmi les tags d'appareils existants", "enrollment_tag_picker_hint": "Permet aux opérateurs de sélectionner des tags dans la liste utilisée sur la page Appareils plutôt que de les saisir uniquement.", "enrollment_open_requests": "Demandes d'inscription ouvertes", - "enrollment_pending_count": "{count} demande(s) d'inscription en attente" + "enrollment_pending_count": "{count} demande(s) d'inscription en attente", + "go_server_version": "Serveur Go", + "go_server_version_dev_hint": "Le binaire Go signale une build de développement — reconstruisez ou mettez à jour le serveur pour correspondre à la version produit" }, "audit": { "time": "Heure", diff --git a/web-nodejs/lang/hi.json b/web-nodejs/lang/hi.json index 484db49c..59d252cb 100644 --- a/web-nodejs/lang/hi.json +++ b/web-nodejs/lang/hi.json @@ -581,7 +581,9 @@ "enrollment_tag_picker": "मौजूदा डिवाइस टैग से टैग चुनें", "enrollment_tag_picker_hint": "ऑपरेटरों को केवल टाइप करने की बजाय डिवाइस पेज पर उपयोग की गई टैग सूची से चुनने देता है।", "enrollment_open_requests": "नामांकन अनुरोध खोलें", - "enrollment_pending_count": "{count} लंबित नामांकन अनुरोध" + "enrollment_pending_count": "{count} लंबित नामांकन अनुरोध", + "go_server_version": "Go सर्वर", + "go_server_version_dev_hint": "Go बाइनरी विकास बिल्ड की सूचना देता है — उत्पाद संस्करण से मेल खाने के लिए सर्वर को पुनर्निर्माण या अपडेट करें" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/hu.json b/web-nodejs/lang/hu.json index b88deab8..abdce3e8 100644 --- a/web-nodejs/lang/hu.json +++ b/web-nodejs/lang/hu.json @@ -581,7 +581,9 @@ "advanced_file_build-env": "Agent-Build-Umgebung (/etc/betterdesk/build.env)", "advanced_file_docker-supervisord": "Docker: supervisord.conf (supervisor konfiguráció)", "advanced_file_docker-compose": "Docker: docker-compose.yml (orchestráció)", - "advanced_restart_none": "Für diese Fájl ist kein Szolgáltatásneustart erforderlich." + "advanced_restart_none": "Für diese Fájl ist kein Szolgáltatásneustart erforderlich.", + "go_server_version": "Go szerver", + "go_server_version_dev_hint": "A Go bináris fejlesztői buildet jelez — építse újra vagy frissítse a szervert a termékverzióhoz" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/id.json b/web-nodejs/lang/id.json index 9ce0a39f..d9113c8e 100644 --- a/web-nodejs/lang/id.json +++ b/web-nodejs/lang/id.json @@ -581,7 +581,9 @@ "enrollment_tag_picker": "Pilih tag dari tag perangkat yang ada", "enrollment_tag_picker_hint": "Memungkinkan operator memilih tag dari daftar di halaman Perangkat alih-alih hanya mengetik.", "enrollment_open_requests": "Buka permintaan pendaftaran", - "enrollment_pending_count": "{count} permintaan pendaftaran tertunda" + "enrollment_pending_count": "{count} permintaan pendaftaran tertunda", + "go_server_version": "Server Go", + "go_server_version_dev_hint": "Biner Go melaporkan build pengembangan — bangun ulang atau perbarui server agar sesuai versi produk" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/it.json b/web-nodejs/lang/it.json index a6c90972..bc41f08b 100644 --- a/web-nodejs/lang/it.json +++ b/web-nodejs/lang/it.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "Sèlectionner des tags parmi les tags d'dispositivi existants", "enrollment_tag_picker_hint": "Permet aux opèrateurs de sèlectionner des tags dall'elenco utilisèe sur la page Dispositivi plutôt que de les saisir uniquement.", "enrollment_open_requests": "Demandes d'inscription ouvertes", - "enrollment_pending_count": "{count} demande(s) d'inscription en attesa" + "enrollment_pending_count": "{count} demande(s) d'inscription en attesa", + "go_server_version": "Server Go", + "go_server_version_dev_hint": "Il binario Go segnala build di sviluppo — ricompilare o aggiornare il server per allineare la versione prodotto" }, "audit": { "time": "Ora", diff --git a/web-nodejs/lang/ja.json b/web-nodejs/lang/ja.json index 8dcc915f..54e96a49 100644 --- a/web-nodejs/lang/ja.json +++ b/web-nodejs/lang/ja.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "既存のデバイスタグからタグを選択", "enrollment_tag_picker_hint": "オペレーターがデバイスページで使用されるタグリストから選択できます(手動入力のみに限定されません)。", "enrollment_open_requests": "登録リクエストを開く", - "enrollment_pending_count": "{count} 件の保留中の登録リクエスト" + "enrollment_pending_count": "{count} 件の保留中の登録リクエスト", + "go_server_version": "Goサーバー", + "go_server_version_dev_hint": "Goバイナリが開発ビルドを報告しています — 製品バージョンに合わせてサーバーを再ビルドまたは更新してください" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/ko.json b/web-nodejs/lang/ko.json index d1f5475e..74d7833d 100644 --- a/web-nodejs/lang/ko.json +++ b/web-nodejs/lang/ko.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "기존 기기 태그에서 태그 선택", "enrollment_tag_picker_hint": "운영자가 기기 페이지에서 사용되는 태그 목록에서 선택할 수 있습니다(입력만으로 제한되지 않음).", "enrollment_open_requests": "등록 요청 열기", - "enrollment_pending_count": "대기 중인 등록 요청 {count}건" + "enrollment_pending_count": "대기 중인 등록 요청 {count}건", + "go_server_version": "Go 서버", + "go_server_version_dev_hint": "Go 바이너리가 개발 빌드를 보고합니다 — 제품 버전과 일치하도록 서버를 다시 빌드하거나 업데이트하세요" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/nb.json b/web-nodejs/lang/nb.json index 6f019082..36d6ee25 100644 --- a/web-nodejs/lang/nb.json +++ b/web-nodejs/lang/nb.json @@ -581,7 +581,9 @@ "advanced_file_build-env": "Agent-Build-Umgebung (/etc/betterdesk/build.env)", "advanced_file_docker-supervisord": "Docker: supervisord.conf (supervisor-konfigurasjon)", "advanced_file_docker-compose": "Docker: docker-compose.yml (orkestrering)", - "advanced_restart_none": "Für diese Fil ist kein Tjenesteneustart erforderlich." + "advanced_restart_none": "Für diese Fil ist kein Tjenesteneustart erforderlich.", + "go_server_version": "Go-server", + "go_server_version_dev_hint": "Go-binæren rapporterer utviklingsbuild — bygg på nytt eller oppdater serveren for å matche produktversjonen" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/nl.json b/web-nodejs/lang/nl.json index e45916cf..2e9470ad 100644 --- a/web-nodejs/lang/nl.json +++ b/web-nodejs/lang/nl.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "Tags aus vorhandenen Gerate-Tags auswahlen", "enrollment_tag_picker_hint": "Ermoglicht Operatoren, Tags aus der auf der Gerateseite verwendeten Liste auszuwahlen, anstatt nur zu tippen.", "enrollment_open_requests": "Offene Registrierungsanfragen", - "enrollment_pending_count": "{count} ausstehende Registrierungsanfrage(n)" + "enrollment_pending_count": "{count} ausstehende Registrierungsanfrage(n)", + "go_server_version": "Go-server", + "go_server_version_dev_hint": "Go-binary meldt ontwikkelbuild — herbouw of werk de server bij om de productversie te matchen" }, "audit": { "time": "Tijd", diff --git a/web-nodejs/lang/pl.json b/web-nodejs/lang/pl.json index 794f5e97..5fe6e425 100644 --- a/web-nodejs/lang/pl.json +++ b/web-nodejs/lang/pl.json @@ -575,7 +575,9 @@ "totp_recovery_warning": "Zapisz te kody odzyskiwania w bezpiecznym miejscu. Każdy kod może być użyty tylko raz. Jeśli stracisz dostęp do aplikacji uwierzytelniającej, możesz użyć tych kodów do zalogowania.", "totp_done": "Gotowe", "totp_disable_warning": "Wyłączenie uwierzytelniania dwuskładnikowego zmniejszy bezpieczeństwo konta. Wprowadź hasło, aby potwierdzić.", - "totp_disabled_success": "Uwierzytelnianie dwuskładnikowe zostało wyłączone." + "totp_disabled_success": "Uwierzytelnianie dwuskładnikowe zostało wyłączone.", + "go_server_version": "Serwer Go", + "go_server_version_dev_hint": "Binarny serwer Go zgłasza build deweloperski — przebuduj lub zaktualizuj serwer, aby dopasować wersję produktu" }, "audit": { "time": "Czas", diff --git a/web-nodejs/lang/pt.json b/web-nodejs/lang/pt.json index 917527a9..f90460ab 100644 --- a/web-nodejs/lang/pt.json +++ b/web-nodejs/lang/pt.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "Escolher etiquetas de las etiquetas de dispositivos existentes", "enrollment_tag_picker_hint": "Permite aos operadores selecionar etiquetas de la lista usada en la página de Dispositivos em vez de solo escribirlas.", "enrollment_open_requests": "Pedidos de registo abiertas", - "enrollment_pending_count": "{count} solicitud(es) de registo pendente(s)" + "enrollment_pending_count": "{count} solicitud(es) de registo pendente(s)", + "go_server_version": "Servidor Go", + "go_server_version_dev_hint": "O binário Go reporta build de desenvolvimento — recompile ou atualize o servidor para corresponder à versão do produto" }, "audit": { "time": "Hora", diff --git a/web-nodejs/lang/ro.json b/web-nodejs/lang/ro.json index c0b87a70..d2830f51 100644 --- a/web-nodejs/lang/ro.json +++ b/web-nodejs/lang/ro.json @@ -581,7 +581,9 @@ "advanced_file_build-env": "Agent-Build-Umgebung (/etc/betterdesk/build.env)", "advanced_file_docker-supervisord": "Docker: supervisord.conf (configurare supervisord)", "advanced_file_docker-compose": "Docker: docker-compose.yml (orchestrare)", - "advanced_restart_none": "Für diese Fișier ist kein Serviciuneustart erforderlich." + "advanced_restart_none": "Für diese Fișier ist kein Serviciuneustart erforderlich.", + "go_server_version": "Server Go", + "go_server_version_dev_hint": "Binarul Go raportează build de dezvoltare — reconstruiți sau actualizați serverul pentru a corespunde versiunii produsului" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/sv.json b/web-nodejs/lang/sv.json index cdef6e2f..225d93ec 100644 --- a/web-nodejs/lang/sv.json +++ b/web-nodejs/lang/sv.json @@ -581,7 +581,9 @@ "advanced_file_build-env": "Agent-Build-Umgebung (/etc/betterdesk/build.env)", "advanced_file_docker-supervisord": "Docker: supervisord.conf (supervisor-konfiguration)", "advanced_file_docker-compose": "Docker: docker-compose.yml (orkestrering)", - "advanced_restart_none": "Für diese Fil ist kein Tjänstneustart erforderlich." + "advanced_restart_none": "Für diese Fil ist kein Tjänstneustart erforderlich.", + "go_server_version": "Go-server", + "go_server_version_dev_hint": "Go-binären rapporterar utvecklingsbuild — bygg om eller uppdatera servern för att matcha produktversionen" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/th.json b/web-nodejs/lang/th.json index 1e3045e6..9eb61ce6 100644 --- a/web-nodejs/lang/th.json +++ b/web-nodejs/lang/th.json @@ -581,7 +581,9 @@ "enrollment_tag_picker": "เลือกแท็กจากแท็กอุปกรณ์ที่มีอยู่", "enrollment_tag_picker_hint": "ให้ผู้ดำเนินการเลือกแท็กจากรายการในหน้าอุปกรณ์แทนการพิมพ์เท่านั้น", "enrollment_open_requests": "เปิดคำขอลงทะเบียน", - "enrollment_pending_count": "คำขอลงทะเบียนที่รอดำเนินการ {count} รายการ" + "enrollment_pending_count": "คำขอลงทะเบียนที่รอดำเนินการ {count} รายการ", + "go_server_version": "เซิร์ฟเวอร์ Go", + "go_server_version_dev_hint": "ไบนารี Go รายงานว่าเป็น build พัฒนา — สร้างใหม่หรืออัปเดตเซิร์ฟเวอร์ให้ตรงกับเวอร์ชันผลิตภัณฑ์" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/tr.json b/web-nodejs/lang/tr.json index 590dc9c8..8685d855 100644 --- a/web-nodejs/lang/tr.json +++ b/web-nodejs/lang/tr.json @@ -581,7 +581,9 @@ "enrollment_tag_picker": "Mevcut cihaz etiketlerinden etiket seç", "enrollment_tag_picker_hint": "Operatörlerin yalnızca yazmak yerine Cihazlar sayfasındaki etiket listesinden seçmesine olanak tanır.", "enrollment_open_requests": "Kayıt isteklerini aç", - "enrollment_pending_count": "{count} bekleyen kayıt isteği" + "enrollment_pending_count": "{count} bekleyen kayıt isteği", + "go_server_version": "Go sunucusu", + "go_server_version_dev_hint": "Go ikilisi geliştirme derlemesi bildiriyor — ürün sürümüyle eşleşmesi için sunucuyu yeniden derleyin veya güncelleyin" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/uk.json b/web-nodejs/lang/uk.json index 8d5d1e56..a0c92b67 100644 --- a/web-nodejs/lang/uk.json +++ b/web-nodejs/lang/uk.json @@ -581,7 +581,9 @@ "advanced_file_build-env": "Agent-Build-Umgebung (/etc/betterdesk/build.env)", "advanced_file_docker-supervisord": "Docker: supervisord.conf (конфігурація supervisord)", "advanced_file_docker-compose": "Docker: docker-compose.yml (оркестрація)", - "advanced_restart_none": "Für diese Файл ist kein Службаneustart erforderlich." + "advanced_restart_none": "Für diese Файл ist kein Службаneustart erforderlich.", + "go_server_version": "Go-сервер", + "go_server_version_dev_hint": "Go-бінарник повідомляє про dev-збірку — перезберіть або оновіть сервер, щоб відповідати версії продукту" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/vi.json b/web-nodejs/lang/vi.json index a904c75d..1a69974d 100644 --- a/web-nodejs/lang/vi.json +++ b/web-nodejs/lang/vi.json @@ -581,7 +581,9 @@ "enrollment_tag_picker": "Chọn thẻ từ thẻ thiết bị hiện có", "enrollment_tag_picker_hint": "Cho phép người vận hành chọn thẻ từ danh sách trên trang Thiết bị thay vì chỉ gõ tay.", "enrollment_open_requests": "Mở yêu cầu đăng ký", - "enrollment_pending_count": "{count} yêu cầu đăng ký đang chờ" + "enrollment_pending_count": "{count} yêu cầu đăng ký đang chờ", + "go_server_version": "Máy chủ Go", + "go_server_version_dev_hint": "Nhị phân Go báo bản dựng phát triển — build lại hoặc cập nhật máy chủ để khớp phiên bản sản phẩm" }, "audit": { "time": "Time", diff --git a/web-nodejs/lang/zh-TW.json b/web-nodejs/lang/zh-TW.json index a8402eea..ebbca8f5 100644 --- a/web-nodejs/lang/zh-TW.json +++ b/web-nodejs/lang/zh-TW.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "從現有裝置標籤中選擇標籤", "enrollment_tag_picker_hint": "允許操作員從裝置頁面的標籤清單中選擇,而不僅限於手動輸入。", "enrollment_open_requests": "開啟註冊請求", - "enrollment_pending_count": "{count} 個待處理的註冊請求" + "enrollment_pending_count": "{count} 個待處理的註冊請求", + "go_server_version": "Go 伺服器", + "go_server_version_dev_hint": "Go 二進位報告為開發建置 — 請重新建置或更新伺服器以符合產品版本" }, "audit": { "time": "時間", diff --git a/web-nodejs/lang/zh.json b/web-nodejs/lang/zh.json index b712d0bd..09167d58 100644 --- a/web-nodejs/lang/zh.json +++ b/web-nodejs/lang/zh.json @@ -575,7 +575,9 @@ "enrollment_tag_picker": "从现有设备标签中选择标签", "enrollment_tag_picker_hint": "允许操作员从设备页面标签列表中选择,而不仅限于手动输入。", "enrollment_open_requests": "打开注册请求", - "enrollment_pending_count": "{count} 个待处理注册请求" + "enrollment_pending_count": "{count} 个待处理注册请求", + "go_server_version": "Go 服务器", + "go_server_version_dev_hint": "Go 二进制报告为开发构建 — 请重新构建或更新服务器以匹配产品版本" }, "audit": { "time": "时间", diff --git a/web-nodejs/lib/productVersion.js b/web-nodejs/lib/productVersion.js new file mode 100644 index 00000000..a41a8cd6 --- /dev/null +++ b/web-nodejs/lib/productVersion.js @@ -0,0 +1,49 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/; + +function getProjectRoot() { + return path.join(__dirname, '..', '..'); +} + +function readPackageJsonVersion(consoleDir) { + try { + const pkgPath = path.join(consoleDir || path.join(__dirname, '..'), 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + const v = (pkg.version || '').trim(); + return SEMVER_RE.test(v) ? v : null; + } catch (_e) { + return null; + } +} + +/** + * Read canonical BetterDesk product version. + * Prefers repo-root VERSION, then web-nodejs/package.json, then fallback. + */ +function readProductVersion({ rootDir, consoleDir, fallback = '0.0.0' } = {}) { + const projectRoot = rootDir || getProjectRoot(); + const versionFile = path.join(projectRoot, 'VERSION'); + + try { + if (fs.existsSync(versionFile)) { + const v = fs.readFileSync(versionFile, 'utf8').trim(); + if (SEMVER_RE.test(v)) return v; + } + } catch (_e) { /* fall through */ } + + const fromPkg = readPackageJsonVersion(consoleDir || path.join(projectRoot, 'web-nodejs')); + if (fromPkg) return fromPkg; + + return fallback; +} + +module.exports = { + SEMVER_RE, + getProjectRoot, + readProductVersion, + readPackageJsonVersion, +}; diff --git a/web-nodejs/public/js/settings.js b/web-nodejs/public/js/settings.js index 83eaaca2..c25d9baa 100644 --- a/web-nodejs/public/js/settings.js +++ b/web-nodejs/public/js/settings.js @@ -193,6 +193,17 @@ document.getElementById('db-path').textContent = data.paths?.database || '-'; document.getElementById('uptime').textContent = formatUptime(data.server?.uptime); + + const goVerEl = document.getElementById('go-server-version'); + if (goVerEl) { + const goVer = data.goServer?.version; + goVerEl.textContent = goVer ? `v${goVer}` : '-'; + if (goVer && goVer === 'dev' && data.app?.version && data.app.version !== 'dev') { + goVerEl.title = typeof _ === 'function' ? _('settings.go_server_version_dev_hint') : ''; + } else { + goVerEl.title = ''; + } + } } catch (error) { console.error('Failed to load server info:', error); diff --git a/web-nodejs/services/updateService.js b/web-nodejs/services/updateService.js index 68023be0..de72ca5a 100644 --- a/web-nodejs/services/updateService.js +++ b/web-nodejs/services/updateService.js @@ -26,6 +26,7 @@ const path = require('path'); const https = require('https'); const { execSync, execFileSync } = require('child_process'); const config = require('../config/config'); +const { readProductVersion } = require('../lib/productVersion'); const { createConsoleDeployGraph } = require('../lib/consoleDeployGraph'); const { resolveChildPath, resolvePathUnderRoot, existsConfinedChild, removeConfinedChild } = require('../lib/safePath'); const { runConsoleNpmInstall } = require('../lib/consoleNpmInstall'); @@ -483,12 +484,7 @@ async function getRemoteHeadSHA() { } function getLocalVersion() { - const versionFile = path.join(PROJECT_ROOT, 'VERSION'); - if (fs.existsSync(versionFile)) { - const v = fs.readFileSync(versionFile, 'utf8').trim(); - if (v) return v; - } - return config.appVersion; + return readProductVersion({ rootDir: PROJECT_ROOT }) || config.appVersion; } // ======================== Classify ====================================== @@ -1155,9 +1151,11 @@ async function buildGoServer(preferredGoBinPath = null) { env: buildEnv }); + const productVersion = readProductVersion({ rootDir: PROJECT_ROOT }); + const ldflags = `-s -w -X main.Version=${productVersion}`; await spawnPromise( goBin, - ['build', '-trimpath', '-ldflags=-s -w', '-o', binaryName, '.'], + ['build', '-trimpath', `-ldflags=${ldflags}`, '-o', binaryName, '.'], { cwd: serverDir, timeout: 600000, env: buildEnv } ); diff --git a/web-nodejs/tests/productVersion.test.js b/web-nodejs/tests/productVersion.test.js new file mode 100644 index 00000000..a58b7aec --- /dev/null +++ b/web-nodejs/tests/productVersion.test.js @@ -0,0 +1,48 @@ +'use strict'; + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { readProductVersion } = require('../lib/productVersion'); + +describe('productVersion', () => { + let tmpRoot; + + beforeEach(() => { + tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'bd-version-')); + }); + + afterEach(() => { + fs.rmSync(tmpRoot, { recursive: true, force: true }); + }); + + test('reads semver from repo-root VERSION', () => { + fs.writeFileSync(path.join(tmpRoot, 'VERSION'), '4.1.2\n'); + expect(readProductVersion({ rootDir: tmpRoot })).toBe('4.1.2'); + }); + + test('falls back to package.json when VERSION is missing', () => { + const consoleDir = path.join(tmpRoot, 'web-nodejs'); + fs.mkdirSync(consoleDir, { recursive: true }); + fs.writeFileSync( + path.join(consoleDir, 'package.json'), + JSON.stringify({ version: '3.9.8' }) + ); + expect(readProductVersion({ rootDir: tmpRoot, consoleDir })).toBe('3.9.8'); + }); + + test('prefers VERSION over package.json', () => { + const consoleDir = path.join(tmpRoot, 'web-nodejs'); + fs.mkdirSync(consoleDir, { recursive: true }); + fs.writeFileSync(path.join(tmpRoot, 'VERSION'), '2.0.1\n'); + fs.writeFileSync( + path.join(consoleDir, 'package.json'), + JSON.stringify({ version: '9.9.9' }) + ); + expect(readProductVersion({ rootDir: tmpRoot, consoleDir })).toBe('2.0.1'); + }); + + test('returns fallback when no sources available', () => { + expect(readProductVersion({ rootDir: tmpRoot, fallback: '0.0.0' })).toBe('0.0.0'); + }); +}); diff --git a/web-nodejs/views/settings.ejs b/web-nodejs/views/settings.ejs index f6f6d1e7..c5dc6e70 100644 --- a/web-nodejs/views/settings.ejs +++ b/web-nodejs/views/settings.ejs @@ -100,6 +100,13 @@ v${appVersion} +
+ + dns + ${_('settings.go_server_version')} + + - +
storage