diff --git a/.env.example b/.env.example index e6f2403..9aedbf9 100644 --- a/.env.example +++ b/.env.example @@ -22,8 +22,8 @@ # Delay before checking bandwidth downgrade (seconds) #DOWNGRADE_START_CHECK=1800 -# Relay server port -#HBBS_PORT=21117 +# Relay server port (hbbr) +#HBBR_PORT=21117 # ============================ # HBBS (Rendezvous / Signal Server) @@ -85,7 +85,7 @@ # Gin mode (release/debug/test) #RUSTDESK_API_GIN_MODE=release -# Language for API responses (en, ru, fr, es, ko) +# Language for API responses (en, ru, zh-CN) #RUSTDESK_API_LANG=en # Enable web client (1=on, 0=off) @@ -120,16 +120,16 @@ #RUSTDESK_API_POSTGRESQL_DBNAME=postgres #RUSTDESK_API_POSTGRESQL_SSLMODE=disable -# RustDesk server addresses (announced to clients) +# RustDesk server endpoints (announced to clients; keep explicit ports where required) #RUSTDESK_API_RUSTDESK_ID_SERVER= #RUSTDESK_API_RUSTDESK_RELAY_SERVER= #RUSTDESK_API_RUSTDESK_API_SERVER= #RUSTDESK_API_RUSTDESK_WS_HOST= -# Path to the public key file (generated by hbbs on first run) +# Current public-key file contract: use the file generated by hbbs on first run. #RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub -# Encryption key (same as HBBS KEY) +# Legacy raw key compatibility only; new deployments must use RUSTDESK_API_KEY_FILE. #RUSTDESK_API_RUSTDESK_KEY= # Enable personal API (1=on, 0=off) @@ -152,7 +152,7 @@ #RUSTDESK_API_REDIS_ADDR= #RUSTDESK_API_REDIS_PASSWORD= -# Cache type (memory/redis) +# Cache type (memory/file/redis) #RUSTDESK_API_CACHE_TYPE=memory # Proxy for outgoing connections (OAuth etc.) @@ -163,15 +163,20 @@ # Custom Client Builder # ============================ -# Build agents use shared Docker volumes for job tickets. -# Build output is written to rdgen-data:/output/{build_id}/ -# No additional env vars are required for basic operation. +# Manual/historical file-queue material only (not the current provider-dispatch +# build path): old agents used the shared Docker volume for job tickets and wrote +# output below rdgen-data:/output/{build_id}/. No additional env vars are required +# for the current provider-backed operation. # Secret encryption key for data at rest (BUGS.md B-008). # Encrypts the GitHub PAT, the workflow PayloadKey and permanent_password # (stored inside custom_json) in the database with AES-256-GCM. # Read directly from the environment (NOT via the RUSTDESK_API_ prefix). -# Use a long random value; keep it stable (rotating it makes existing -# encrypted rows unreadable). If unset, secrets are stored in plaintext. +# Use a long random value. +# Existing ciphertext requires the same key; key rotation is unsupported. +# Legacy plaintext rows remain readable; saving them again encrypts them when +# the key exists. If unset, new non-empty +# secret writes and secret-bearing Custom Builder operations are rejected rather +# than stored as plaintext. # Must NOT reuse WORKFLOW_PAYLOAD_KEY (that one is shared with GitHub). #SECRET_ENCRYPTION_KEY= diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0532d99 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,133 @@ +name: Build + +on: + push: + branches: [main] + paths: + - "api/**" + - "admin-ui/**" + - "server/**" + - "libs/**" + - "go.mod" + - "go.work" + - "go.work.sum" + - "docker/**" + - ".dockerignore" + - "Dockerfile" + - "docker-compose.yml" + - "docker-compose.yaml" + - "compose.yml" + - "compose.yaml" + - ".github/workflows/**" + pull_request: + paths: + - "api/**" + - "admin-ui/**" + - "server/**" + - "libs/**" + - "go.mod" + - "go.work" + - "go.work.sum" + - "docker/**" + - ".dockerignore" + - "Dockerfile" + - "docker-compose.yml" + - "docker-compose.yaml" + - "compose.yml" + - "compose.yaml" + - ".github/workflows/**" + workflow_dispatch: + +permissions: + contents: read + +jobs: + go: + name: Go checks + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: api + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: api/go.mod + cache: true + cache-dependency-path: | + api/go.sum + go.work.sum + - run: go test ./... + - run: go vet ./... + - run: go build -o /tmp/deskforge-apimain ./cmd/apimain.go + + admin-ui: + name: Admin UI build + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: admin-ui + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 20 + - run: npm ci + - run: npm run build + + rust: + name: Rust checks + runs-on: ubuntu-24.04 + env: + DATABASE_URL: sqlite:///tmp/deskforge-sqlx.sqlite3 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - run: rustup toolchain install 1.92.0 --profile minimal --component rustfmt + - run: cargo +1.92.0 fmt --all -- --check + working-directory: server + - run: cargo +1.92.0 fmt --all -- --check + working-directory: libs/hbb_common + - name: Prepare SQLx SQLite schema + run: | + python3 - <<'PY' + import sqlite3 + + with sqlite3.connect("/tmp/deskforge-sqlx.sqlite3") as connection: + connection.executescript( + """ + create table if not exists peer ( + guid blob primary key not null, + id varchar(100) not null, + uuid blob not null, + pk blob not null, + created_at datetime not null default(current_timestamp), + user blob, + status tinyint, + note varchar(300), + info text not null + ) without rowid; + create unique index if not exists index_peer_id on peer (id); + create index if not exists index_peer_user on peer (user); + create index if not exists index_peer_created_at on peer (created_at); + create index if not exists index_peer_status on peer (status); + """ + ) + - run: cargo +1.92.0 check --locked + working-directory: server + - run: cargo +1.92.0 test --locked + working-directory: server + + docker: + name: Docker build + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - run: docker build -f docker/Dockerfile -t deskforge:ci . diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml new file mode 100644 index 0000000..269bdf6 --- /dev/null +++ b/.github/workflows/build_test.yml @@ -0,0 +1,77 @@ +name: Build Test + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + focused: + name: Focused monorepo validation + runs-on: ubuntu-24.04 + env: + DATABASE_URL: sqlite:///tmp/deskforge-sqlx.sqlite3 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: api/go.mod + cache: true + cache-dependency-path: | + api/go.sum + go.work.sum + - name: Go tests and vet + working-directory: api + run: | + go test ./... + go vet ./... + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 20 + - name: Admin UI build + working-directory: admin-ui + run: | + npm ci + npm run build + - run: rustup toolchain install 1.92.0 --profile minimal --component rustfmt + - name: Rust format check + working-directory: server + run: cargo +1.92.0 fmt --all -- --check + - name: Shared Rust format check + working-directory: libs/hbb_common + run: cargo +1.92.0 fmt --all -- --check + - name: Prepare SQLx SQLite schema + run: | + python3 - <<'PY' + import sqlite3 + + with sqlite3.connect("/tmp/deskforge-sqlx.sqlite3") as connection: + connection.executescript( + """ + create table if not exists peer ( + guid blob primary key not null, + id varchar(100) not null, + uuid blob not null, + pk blob not null, + created_at datetime not null default(current_timestamp), + user blob, + status tinyint, + note varchar(300), + info text not null + ) without rowid; + create unique index if not exists index_peer_id on peer (id); + create index if not exists index_peer_user on peer (user); + create index if not exists index_peer_created_at on peer (created_at); + create index if not exists index_peer_status on peer (status); + """ + ) + PY + - name: Rust check + working-directory: server + run: cargo +1.92.0 check --locked + - name: Rust tests + working-directory: server + run: cargo +1.92.0 test --locked diff --git a/AGENTS.md b/AGENTS.md index 3f8e360..59d9248 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ Everything in one Docker image via s6-overlay. - **Rust servers** (`server/`): hbbs (ID/signaling, TCP/UDP 21116) + hbbr (relay, TCP 21117) - **Go API** (`api/`): Gin on port 21114. GORM (SQLite/MySQL/PostgreSQL). JWT, LDAP, OIDC. - **Admin UI** (`admin-ui/`): Vue 3 + Element Plus. Served at `/admin/`. REST + WebSocket. -- **rdgen** (`rdgen/`): vendored reference workflow (not a service). +- **rdgen** (`rdgen/`): vendored historical/reference workflow material (not a service; frozen). - **Shared lib** (`libs/hbb_common`): Rust crate shared between hbbs and hbbr. ## Tech stack @@ -18,7 +18,7 @@ Everything in one Docker image via s6-overlay. | Component | Stack | | ------------- | ----------------------------------------------------------------- | | Rust (server) | 2021 edition, axum 0.5, sqlx 0.6, tokio, sodiumoxide, openssl | -| Go (api) | 1.23, gin 1.9, gorm 1.25, swag, cobra/viper, jwt, ldap, OIDC | +| Go (api) | 1.25, gin 1.9, gorm 1.25, swag, cobra/viper, jwt, ldap, OIDC | | Admin UI | Vue 3.5, Element Plus 2.8, Vite 6, Pinia 2.2, vue-router 4, axios| | Python (rdgen)| Django (vendored reference, not a service) | | Infra | Docker + s6-overlay, docker compose | @@ -49,14 +49,26 @@ admin-ui/ — Vue 3 admin panel ├── src/styles/ — SCSS (design tokens, light/dark) └── src/utils/ — auth, request, export, i18n (en/ru/zh_CN) -rdgen/ — vendored reference workflow (patches, generator-*.yml) -libs/hbb_common/ — shared Rust library (submodule) +rdgen/ — ❄️ frozen vendored historical/reference workflow material (patches, generator-*.yml) +libs/hbb_common/ — tracked shared Rust library in DeskForge (not a submodule here) docker/ — Dockerfile + compose + entrypoint scripts -github-build/ — active CI workflow for client builds -win-builder/ — ❄️ frozen standalone Windows builder -offline-kit/ — ❄️ frozen sovereign build kit +github-build/ — reference/documentation only; no executable workflow copies +win-builder/ — ❄️ frozen manual/historical-only standalone builder +offline-kit/ — ❄️ frozen dependency-freeze tool; verification is incomplete ``` +DeskForge tracks `libs/hbb_common/` directly. The configured RustDesk fork has its +own `libs/hbb_common` git submodule, currently recorded against upstream +`rustdesk/hbb_common`; its local state is dirty and unpublished, so clean fork +provenance is an explicit reproducibility gate. The fork's `.github/workflows/` files +are the sole executable source for active client-build workflows; `github-build/` and +`rdgen/` are reference/frozen material only. No current repository `vendor/` tree, +`rustdesk-deps/` archive, or client-release directory is tracked here. +The published RustDesk client source/ref is 1.4.8 and the published DeskForge API +schema is `DatabaseVersion` 272. The local uncommitted corrective worktree targets +API schema 282; that local schema target is not published or live-provider evidence. +MySQL/PostgreSQL migration and read/write coverage remain unverified. + ## Build / dev commands ### Docker (primary) @@ -65,7 +77,7 @@ offline-kit/ — ❄️ frozen sovereign build kit cd docker docker compose build # full build docker compose up -d # start -docker compose -f docker-compose-dev.yaml up -d # dev +cd ../api && docker compose -f docker-compose-dev.yaml up -d # dev API stack ``` ### Rust @@ -77,7 +89,12 @@ cd server && cargo build --release && cargo clippy && cargo test ### Go ```bash -cd api && go build -o release/apimain cmd/apimain.go && go vet ./... && go test ./... +cd api && go build -o release/apimain cmd/apimain.go +# Full local checks: +GOWORK=off go vet ./... +GOWORK=off go test ./... +# Redis integration tests and benchmarks are opt-in. Configure +# DESKFORGE_TEST_REDIS_ADDR to run them; no live Redis endpoint is assumed. ``` ### Admin UI @@ -91,6 +108,8 @@ cd admin-ui && npm install && npm run dev && npm run build | Variable | Purpose | Used by | |----------|---------|---------| | `RELAY` | Relay server address | Rust hbbr | +| `HBBR_PORT` | Relay server port | Rust hbbr | +| `HBBS_PORT` | ID/Rendezvous server port | Rust hbbs | | `ENCRYPTED_ONLY` | Only encrypted connections | Rust | | `MUST_LOGIN` | Require login before connect | Rust | | `RUSTDESK_API_RUSTDESK_ID_SERVER` | ID server address | Go API | @@ -98,9 +117,13 @@ cd admin-ui && npm install && npm run dev && npm run build | `RUSTDESK_API_RUSTDESK_API_SERVER` | API server URL | Go API | | `RUSTDESK_API_KEY_FILE` | Path to public key file | Go API | | `RUSTDESK_API_JWT_KEY` | JWT secret key | Go + Rust | -| `RUSTDESK_API_GORM_TYPE` | sqlite/mysql/postgres | Go API | +| `RUSTDESK_API_GORM_TYPE` | sqlite/mysql/postgresql | Go API | | `RUSTDESK_API_LANG` | en/ru/zh-CN | Go + UI | -| `SECRET_CRYPT_KEY` | AES-GCM key for secrets at rest | Go API | +| `SECRET_ENCRYPTION_KEY` | AES-GCM key for secrets at rest | Go API | + +New non-empty secret writes and secret-bearing Custom Builder operations require this +key and are rejected rather than stored as plaintext when it is missing. Legacy plaintext +rows remain readable; saving them again encrypts them when the key exists. ## Key integration points @@ -109,7 +132,7 @@ cd admin-ui && npm install && npm run dev && npm run build - Go reads public key from `RUSTDESK_API_KEY_FILE` (`/data/id_ed25519.pub`) - Go connects to hbbs/hbbr via `RUSTDESK_API_RUSTDESK_ID_SERVER`/`RUSTDESK_API_RUSTDESK_RELAY_SERVER` - JWT: Go generates, Rust validates (`jwt.rs`) -- WebSocket bridge: port 21118 +- WebSocket bridge: port 21118; relay WebSocket: port 21119 ### Admin UI ↔ Go API @@ -121,6 +144,12 @@ cd admin-ui && npm install && npm run dev && npm run build ## Agent constraints - Do not modify upstream directly (`rustdesk/rustdesk-server`, `lejianwen/rustdesk-api`) — only forks. +- Active third-party upstream dependencies remain in the Rust/Go manifests; a complete + upstream-independent or offline dependency bundle is not currently verified. +- The combined DeskForge distribution is identified as AGPL-3.0 for the covered work; + separate API/UI/reference components retain their applicable upstream licenses and notices. + The license inventory is incomplete; no signatures or attestations are recorded, and no + full sovereignty claim is made. - Keep Docker entrypoint scripts in sync with the services they supervise. - Never log or commit secrets. - Document env vars in README + docker-compose. @@ -136,7 +165,9 @@ cd admin-ui && npm install && npm run dev && npm run build - **Clean layered (Go):** Controller → Service → Model. Do not mix layers. - **Embedded UI:** Go embeds `admin-ui/dist/` and `web/`. -- **Multi-DB:** GORM, no raw SQL. +- **Configured multi-DB:** GORM with SQLite/MySQL/PostgreSQL drivers, no raw SQL; + SQLite is locally exercised, while MySQL/PostgreSQL migration and read/write + coverage remain unverified. - **OAuth/LDAP:** configured via admin panel → DB. Falls back to local users. - **Server commands:** allowlist in `serverCmd.go`. @@ -151,4 +182,4 @@ cd admin-ui && npm install && npm run dev && npm run build ## New upstream version workflow -See [PLAN.md §7](PLAN.md#7-workflow-new-upstream-rustdesk-client-release). +See [PLAN.md §7](PLAN.md#7-historical-fork-maintenance-notes-for-a-new-upstream-rustdesk-client-release). diff --git a/BUGS.md b/BUGS.md index f2b7ef5..02c986f 100644 --- a/BUGS.md +++ b/BUGS.md @@ -3,54 +3,64 @@ > Tracker for issues found in the build-custom-agent end-to-end flow. > Backend: `api/http/controller/admin/custom_build.go`, `api/service/custom_build.go`, > `api/service/github_build_config.go`. Frontend: `admin-ui/src/views/custom-client/index.vue`, -> `admin-ui/src/views/server/github-build.vue`. Workflow: `github-build/rustqs-windows-min-test.yml`. +> `admin-ui/src/views/server/github-build.vue`. Active Windows workflow source: branch +> `rustqs/workflows`, workflow `.github/workflows/rustqs-windows.yml`, artifact +> `rustqs-windows`; local `github-build/` workflow files are reference material only. > > Status legend: `[ ]` open · `[x]` fixed · `[~]` partial · `[skip]` won't fix (owner decision). > -> Last audit: 2026-06-23 (fixed items removed; tracker lists only open work). +> Last audit: 2026-06-23. Current file contents are a bounded tracker snapshot: +> 1 fixed, 4 partial, and 1 open entry; older aggregate counts remain in dated +> changelog history. +> Current-state reconciliation: 2026-08-10. Historical findings below are labeled +> where the current provider-only path supersedes the old queue behavior; no live +> provider or clean-build evidence is implied. Older branch and workflow names below +> are retained as historical records and do not describe the current executable path. --- -## Architectural mismatch with PLAN.md §3 +## Historical architectural mismatch with PLAN.md §3 -PLAN.md declares the standalone / Docker build agents **frozen as fallback** (§8.3, §8.4). -Reality: `custom_build.go::submitBuild` still routes every non-Windows platform — and even -`windows-x86` and Windows when GitHub config is absent — into the file queue -(`/rdgen-data/jobs/{id}.json`). Owner decision (2026-06-20): +The original audit recorded a mismatch with the standalone / Docker build agents marked +**frozen as fallback** (§8.3, §8.4). The current source now checks provider readiness +before persistence and does not route production submissions into the file queue. The +queue scripts remain frozen historical material. Owner decision (2026-06-20): -1. Treat Docker `build-linux` and `build-win` containers as **frozen manual fallback**, not the +1. Treat Docker `build-linux` and `build-win` containers as **frozen manual/historical-only** material, not the default route. They stay on disk but should not be started by `docker compose up`. 2. Remove `windows-x86` (32-bit) as a build target everywhere — UI option, form defaults, any router branches. 2026; not worth maintaining. -3. Build a **GitHub Actions workflow for Linux + Android** mirroring the windows-min-test - pipeline, and re-route `submitBuild` accordingly. Until those workflows ship, non-Windows - platforms should be hidden in the UI to stop users from creating phantom builds. +3. Keep the fork-owned Linux + Android workflow mappings, but do not re-expose those + platforms until PR11 has real end-to-end evidence. Until then, non-Windows platforms + remain gated in the UI/API to prevent phantom builds. The bugs below are grouped by where they leak into user-visible breakage. --- -## CRITICAL — workflow is silently broken end-to-end - -### [~] B-001 · File-queue jobs never propagate `done` status back to the DB -**Deferred on branch `fix/build-custom-agent` (2026-06-20):** UI now restricts platforms to -Windows-via-GitHub (B-013), so the file-queue path is unreachable from the default flow -even though `submitBuild` still has the branch. `docker/docker-compose.yml` moved -`build-linux` and `build-win` services behind a `fallback` profile so they don't start by -default. Full fix (status mirror) only matters once Linux/Android workflows land (B-012), -at which point we'd rather route them through GitHub too. - -**Where:** `api/http/controller/admin/custom_build.go:172-201` (writes job), -`docker/entrypoint-linux.sh:37,49,76,...` and `docker/entrypoint-win.sh:33,45,210` (write `output_dir/status`). -**Symptom:** Linux/Android (and Windows when GitHub config is missing) builds sit at -`Status=pending` forever. `DownloadByKey` returns HTTP 409. The Download button never appears -in the UI (`v-if="row.status === 'done'"`, `custom-client/index.vue:306`). +## Historical critical finding — workflow was silently broken end-to-end + +### [~] B-001 · Historical file-queue jobs do not propagate `done` status back to the DB +**Current state:** provider readiness is checked before a production build row is persisted, +so the file-queue path is not a production fallback. `docker/docker-compose.yml` keeps +`build-linux` and `build-win` behind a `fallback` profile. The frozen scripts still have +the status-mirror limitation if an operator runs them manually; no live provider evidence +is implied by this closure boundary. + +**Current path:** `api/http/controller/admin/custom_build.go:1246-1261` dispatches +production builds through the configured provider; the old queue references remain +only in the frozen scripts below. **Historical queue locations:** +`docker/entrypoint-linux.sh:37,49,76,...` and `docker/entrypoint-win.sh:33,45,210` +write `output_dir/status`. +**Historical symptom:** Linux/Android (and Windows when GitHub config was missing) builds +sat at `Status=pending` forever. `DownloadByKey` returned HTTP 409. The Download button +did not appear in the UI (`v-if="row.status === 'done'"`, `custom-client/index.vue:306`). **Root cause:** no Go-side watcher reads `/rdgen-data/output/{id}/status`. The build agent's status file is dead-letter. **Fix path (per owner direction):** -- Short term: hide all non-Windows-via-GitHub options in the UI (B-002, B-013). -- Long term: replace the file queue for the supported platforms with GitHub Actions dispatch - (linux/android workflows, B-012). +- Current: keep all non-Windows-via-GitHub options gated in the UI/API (B-002, B-013). +- Future: enable Linux/Android only after fork workflow, artifact, embedding, and download + evidence is recorded under B-012/PR11. ## LOW — dead code / cleanup @@ -80,6 +90,12 @@ Left in place because it's a documented capability URL and may have third-party ## STRUCTURAL — to enable B-001/B-002/B-013 fixes ### [~] B-012 · Build Linux + Android GitHub Actions workflows +**Current state:** the API has fork-owned filename mappings, but its production capability +gate rejects Linux and Android until PR11 validates the complete workflow and artifact path. +No live provider/workflow run or clean build is recorded in the current canonical plan. + +**Historical audit record:** + **Backend:** merged (PR #44 backend routing: `submitBuild` dispatches `platform=linux`/`android` by workflow constant; `tryGithubDispatch` picks `rustqs-linux.yml`/`rustqs-android.yml`; `pollAndDownload` selects artifact by platform). @@ -88,6 +104,8 @@ by workflow constant; `tryGithubDispatch` picks `rustqs-linux.yml`/`rustqs-andro and `rustqs/min-test` (execution) — all three are indexed (HTTP 200): `rustqs-windows-min-test.yml`, `rustqs-linux.yml`, `rustqs-android.yml`. Filenames in Go constants match fork filenames exactly. +These deployment observations are historical; they are not current provider-run, +artifact, package, or support evidence. **Critical dependency:** `bridge.yml` must also exist on both branches — all three `rustqs-*.yml` reference it as a reusable workflow. Without it, dispatch succeeds @@ -96,11 +114,15 @@ but the run fails with a parse error (422). Still open: - validate `rustqs-linux.yml` and `rustqs-android.yml` on real Actions runs (build steps: vcpkg/flutter/build.py/packaging/artifact paths — need CI iteration like windows-min-test did) -- Android `custom_.txt` embedding is best-effort, needs verification +- Android `custom_.txt` runtime-path and fail-closed packaging checks have local static + evidence; no live APK/package/install/runtime evidence exists - re-expose Linux/Android in the UI (B-013) behind a feature flag once runs are green -**Where:** `github-build/rustqs-linux.yml`, `github-build/rustqs-android.yml`. Reference templates: +**Where (active source):** the configured RustDesk fork's +`.github/workflows/rustqs-linux.yml` and `.github/workflows/rustqs-android.yml`. +**Historical/reference templates:** former local `github-build/` workflow references and `rdgen/.github/workflows/generator-linux.yml`, `rdgen/.github/workflows/generator-android.yml`. +The `github-build/` and `rdgen/` workflow material is not the executable source. **Symptoms (historical):** before the push, dispatch returned HTTP 404 because workflow files were not on `master` (default branch); submit went to the deprecated file queue (B-001). Resolved by pushing workflow files to both `master` and `rustqs/min-test`. @@ -119,7 +141,10 @@ can create/alter peers and inject audit entries. `/api/shared-peer` also does an `(*j)["share_token"].(string)` assertion (`webClient.go:57`) → 500 on missing token. **Fix:** needs RustDesk protocol design confirmation (the PC client hits these before auth). -### [ ] AU-L-010 · Hardcoded version list in Custom Client UI +### [x] AU-L-010 · Hardcoded version list in Custom Client UI +Resolved in the current source by the provider-derived version catalog; unavailable +provider catalog data returns an empty/error state rather than an obsolete hardcoded +version fallback. Live provider catalog evidence remains unverified. ## rdgen generator — open findings (consolidated from the removed `AUDIT.md`) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d0ef7..0566f5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Current-state reconciliation — 2026-08-10 +- The published RustDesk client source/ref is **1.4.8** and the published DeskForge API + schema is `DatabaseVersion` **272**. The local uncommitted corrective worktree targets + API schema 282; that local schema target is not a current published-schema claim. + Older 1.4.7/272 references in the dated entries below remain historical release/schema notes. +- The current custom-client path is owned-fork dispatch, provider polling, exact artifact + retrieval, and local validation/publication. Runner callbacks, local file-queue fallback, + and sole-artifact selection are not current production behavior. +- Dispatch `return_run_details=true` plus an exact HTTP 200 run-details response is the + project/provider contract under test. Standard 204 is intentionally unsupported because + it does not provide an accepted exact run correlation; normal GitHub operation is not verified. +- Windows is the only production capability admitted by the API gate. Linux/Android + workflow mappings exist but remain gated pending PR11 evidence; no live provider run or + clean-environment build proof is claimed. +- Canonical plan state: PR9 is `verified-with-notes`; PR10 and PR11 remain + `in-progress`, while PR12 is `verified-with-notes` for docs only. This does not claim + live provider execution, clean builds, platform support, sovereignty, or release publication. +- Follow-up verification on 2026-08-11: `GOWORK=off go vet ./...` and + `GOWORK=off go test ./...` pass after test-only cache diagnostics and opt-in Redis + test changes. Redis integration tests and benchmarks run only when + `DESKFORGE_TEST_REDIS_ADDR` is configured; no live Redis run is recorded. + MySQL/PostgreSQL, live provider, and the other release/build gates remain unverified. + ### Fixed (custom-client: relay_server port stripping + api-server key) - **custom-client: relay_server port stripped on blur, submit, and prefill** — now consistent with `host`: hostname only, no port. The client appends default relay port (21117) automatically. @@ -40,7 +63,11 @@ across three migration steps; `AutoMigrate` is idempotent so all schema addition 37 Rust test-code alerts dismissed as test-only. (PRs #24, #25, #26, #28, #29) - **api + admin-ui: encrypted secrets at rest** — `CustomBuild`, `CustomPreset`, `GithubBuildConfig` now store sensitive fields wrapped with AES-GCM via - `api/utils/secretcrypt`. Key from `SECRET_CRYPT_KEY` env var; absence fails closed at boot. + `api/utils/secretcrypt`. Uses the canonical `SECRET_ENCRYPTION_KEY` env var. + Without it, new non-empty secret writes and secret-bearing Custom Builder operations + are rejected rather than stored as plaintext. Legacy plaintext remains readable, and + re-saving encrypts it when the key exists. Existing ciphertext requires the same key; + key rotation is unsupported. - **api: OAuth delete guard** — last-enabled OAuth provider cannot be deleted while `oauth-only` registration is on (would lock the system out). - **rdgen: production startup guard** — refuses to boot with default `SECRET_KEY` / @@ -52,13 +79,13 @@ across three migration steps; `AutoMigrate` is idempotent so all schema addition - **api: capability-URL TTL** — public download links (`/build/k/{key}` and `/build/dl/{key}`) now return 410 Gone after expiry; check centralized in `findBuildByDownloadKey`. (B-006) -- **api: GitHub artifact name fallback** — `DownloadArtifact` falls back to the sole - artifact of the run if the expected name doesn't match, so renaming the workflow no - longer breaks `tryGithubDownload`. (AU-L-011) -- **api: Linux + Android via GitHub Actions** — `submitBuild` routes `platform=linux` and - `platform=android` to `rustqs-linux.yml` / `rustqs-android.yml` workflow_dispatch when - GitHub config is present, matching the Windows path (B-012). Artifact name is now - dynamic (`rustdesk-min-test-{windows,linux,android}`). +- **Historical implementation note (superseded): api: GitHub artifact name fallback** — + the former `DownloadArtifact` behavior fell back to the sole artifact of the run when + the expected name did not match. The current path requires exact artifact identity. + (AU-L-011) +- **Historical implementation note (superseded): api: Linux + Android via GitHub Actions** — + routing and platform-specific artifact naming were added, but current production + capability remains gated until PR11 evidence validates the complete path. (B-012) - **admin-ui: build history auto-refresh** — the custom-client list polls every 5 s while any row is in `pending`/`running`, stops when all are terminal — no manual reload. - **admin-ui: dispatch test payload** — `Test dispatch` button on `github-build.vue` now @@ -108,7 +135,8 @@ across three migration steps; `AutoMigrate` is idempotent so all schema addition AU-M-022, AU-L-007/010/011/015 carried forward). ### Reference -- See `BUGS.md` for the current tracker (25 fixed, 4 partial, 2 open). +- Historical tracker snapshot (2026-06-22): 25 fixed, 4 partial, 2 open. The + current bounded `BUGS.md` snapshot contains 1 fixed, 4 partial, and 1 open entry. - See PR #30 for the batch-merge detail and the DatabaseVersion conflict resolution. --- @@ -323,12 +351,13 @@ The work surfaced four real bugs that would have broken the actual GUI build flo succeeded in about 33 minutes. Final artifact: **one file `rustqs.exe`, 23.2 MB** instead of a small launcher plus a DLL folder. Exe metadata is `rustqs` and `custom_.txt` is packed inside the self-extracting exe. -- First attempt [27462157839](https://github.com/bashrusakh/rustdesk/actions/runs/27462157839) +- **Historical implementation note (superseded):** First attempt + [27462157839](https://github.com/bashrusakh/rustdesk/actions/runs/27462157839) failed almost immediately with `bad decrypt` in `Resolve build config` because `WORKFLOW_PAYLOAD_KEY` in the fork had diverged from local `offline-kit/artifacts/workflow-payload.key`. Open inputs were used as a temporary bypass to - validate §8.10. Remaining TODO: resync the key, either from the UI (`Push to GitHub Secrets`) or - by replacing the local file. + validate §8.10; the active workflow no longer permits that fallback. Remaining TODO at the + time: resync the key, either from the UI (`Push to GitHub Secrets`) or by replacing the local file. ### Fixed (Docker build) - Added root **`.dockerignore`** to exclude `node_modules/`, `.git/`, `data/`, `rdgen-data/`, @@ -360,9 +389,10 @@ The work surfaced four real bugs that would have broken the actual GUI build flo Axios request with a 95-minute timeout and shows `Build running...` -> success/failure. ### Fixed (GitHub fork cleanup) -- Removed **10 upstream workflows** from `bashrusakh/rustdesk@master`: +- Historical cleanup removed **10 upstream workflows** from `bashrusakh/rustdesk@master`: `bridge.yml`, `ci.yml`, `clear-cache.yml`, `fdroid.yml`, `flutter-build.yml`, `flutter-ci.yml`, `flutter-nightly.yml`, `flutter-tag.yml`, `playground.yml`, `wf-cliprdr-ci.yml`. + These are deleted legacy references, not active workflow evidence. Kept only the needed ones: `rustqs-windows-min-test.yml` and `third-party-RustDeskTempTopMostWindow.yml`. - Restored **`bridge.yml`** after cleanup because `rustqs-windows-min-test.yml` uses it as a reusable workflow. Without it, dispatch failed with HTTP 422 / workflow parse error. Restored from upstream `rustdesk/rustdesk@1.4.7`. @@ -375,6 +405,11 @@ The work surfaced four real bugs that would have broken the actual GUI build flo ## [0.4.0] - 2026-06-11 +> **Historical/superseded release entry (2026-06-11):** The offline-kit, SMB +> builder, and L1 sovereignty statements below describe that dated state only. +> They are not current proof of offline completeness, active SMB operation, +> upstream independence, or sovereignty; see the current-state reconciliation above. + ### Changed (Architecture - Sovereign Build Strategy) - Fully rewrote **`PLAN.md`** as the single source of truth. It now documents the sovereign build model (3 levels of independence: sources / build / toolchain), fork map (`rustdesk` + `hbb_common` + @@ -400,7 +435,9 @@ The work surfaced four real bugs that would have broken the actual GUI build flo - Captured the full list of Windows `vcpkg` dependencies from `vcpkg.json`: `aom`, `libjpeg-turbo`, `opus`, `libvpx`, `libyuv`, `mfx-dispatch`, **`ffmpeg`** (`amf`/`nvcodec`/`qsv` for `hwcodec`). -### Done (Offline kit frozen) +### Done (Offline kit frozen — historical/superseded, 2026-06-11) +> This dated offline-kit record is historical/superseded and is not current proof of +> offline completeness, release readiness, upstream independence, or sovereignty. - Ran `freeze.sh` on 2026-06-11 in `docker-build-linux-1`. Frozen into `rustdesk-cache` volume: 1.4.7 bundle, `vendor` (2.7G, all `rustdesk-org/*` + `hbb_common`), Flutter engine, Flutter SDK for win+linux, `vcpkg` at baseline, Rust 1.75.0 MSI. Manifest with sha256 stored in `artifacts/MANIFEST.txt`. @@ -423,7 +460,9 @@ The work surfaced four real bugs that would have broken the actual GUI build flo - Added `.gitignore` to exclude secrets (private key `id_ed25519`, DB, `data/`, `.env`), build output, `node_modules`, `offline-kit/artifacts`, `.claude/`. Secret scan found no leaks in source. -### Changed (Windows builder: container -> native, owner decision) +### Changed (Windows builder: container -> native, owner decision — historical/superseded, 2026-06-11) +> The SMB queue and standalone builder described here are historical/frozen material, +> not the current provider-backed API workflow. - Final decision: build the Windows client **natively** on a separate Windows Server, no Docker. - API <-> agent channel is an **SMB job queue folder**. Linux hosts Samba, Windows mounts it. - Added `win-builder/setup.ps1` (toolchain, `-KitPath` support), `win-builder/agent.ps1` @@ -443,10 +482,13 @@ The work surfaced four real bugs that would have broken the actual GUI build flo - Generated 43-char `WORKFLOW_PAYLOAD_KEY` and stored it in fork GitHub Secrets. Important pitfall: `gh secret set ... --body -` via pipe adds a trailing newline under PowerShell, causing `bad decrypt` on the runner. Fix: use `--body $secret` without a pipe. -- Refactored the workflow to take `enc_payload` and resolve it through `Resolve build config` - (OpenSSL AES-256-CBC + PBKDF2 + `jq` -> env vars), while still supporting open inputs as fallback. +- **Historical implementation note (superseded):** Refactored the workflow to take + `enc_payload` and resolve it through `Resolve build config` (OpenSSL AES-256-CBC + PBKDF2 + + `jq` -> env vars), while the then-current version still supported open inputs as a fallback. + The active workflow now accepts only authenticated `DFP1` payloads. - Migrated L1/L2/L3 steps from `inputs.X` to `RQS_*` env vars and masked sensitive values with `::add-mask::`. -- Verified with successful runs for both open-inputs and encrypted payload. +- **Historical validation note (superseded):** Verified successful runs for both open-inputs + and encrypted payload. This does not validate the current DFP1-only path. **§8.8.5 Go API - SCAFFOLD** - Added `model/github_build_config.go`: singleton with `Token`, `PayloadKey`, and safe view. @@ -468,7 +510,8 @@ The work surfaced four real bugs that would have broken the actual GUI build flo `pollAndDownload` loop (30-second poll, 90-minute timeout). - On success it downloads `rustdesk-min-test-windows.zip`, unpacks it, stores `{appname}.exe` + DLLs + `custom_.txt` into `/rdgen-data/output/{id}/`, and updates `CustomBuild.Status`. -- Falls back to file-queue mode for Linux/Android. +- **Historical implementation note (superseded):** the earlier API fell back to + file-queue mode for Linux/Android; the current production path fails closed instead. **Self review §8.8.5** - Prevented panic in background goroutine `pollAndDownload` via `defer recover()`. @@ -519,10 +562,11 @@ The work surfaced four real bugs that would have broken the actual GUI build flo (Hyper-V VM / physical / cloud), long paths, antivirus exclusions, SMB, service agent, first end-to-end test, and security. -### Verified / Fixed (offline-kit) -- Proved **L1 sovereignty**: `cargo metadata --offline` on the vendored tree resolves all 1049 crates +### Verified / Fixed (offline-kit — historical/superseded; not current proof) +- **Historical L1 claim (superseded):** `cargo metadata --offline` on the vendored tree resolved all 1049 crates from `vendor` with no network access. -- Rebuilt and verified the **bundle** on full history (70M). Clone-back on tag 1.4.7 succeeded. +- **Historical bundle check (superseded):** rebuilt and verified the **bundle** on full history (70M). + Clone-back on the dated 1.4.7 tag succeeded. Fixed the earlier shallow-clone defect (`remote did not send all necessary objects`). ### Notes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 276eee9..52003fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,8 +111,11 @@ Co-Authored-By: Claude ## License & attribution (AGPL-3.0) -DeskForge is distributed under **AGPL-3.0** (because `server/` is AGPL-3.0 and it's -the strongest copyleft in the bundle). +The covered DeskForge distribution is identified as **AGPL-3.0** because `server/` is +AGPL-3.0 and is the strongest copyleft in the covered bundle. This does not relicense +separate or independent components; preserve each component's applicable license and +notice. The license inventory is incomplete; no signatures or attestations are recorded, +and no full sovereignty claim is made. When you add new files derived from upstream sources: @@ -132,12 +135,21 @@ To run the server stack: ```bash cd docker -docker compose build server # rebuilds Go code; admin-ui needs pre-built dist -docker compose up -d server -docker compose logs -f server +docker compose build rustdesk # build the combined server image +docker compose up -d rustdesk +docker compose logs -f rustdesk ``` -For the GitHub-based Windows client builder workflow — see [PLAN.md](PLAN.md) §8.8. +For GitHub-based client-build workflow maintenance, the configured RustDesk fork's +`.github/workflows/` files are the sole active executable workflow source. See +[PLAN.md](PLAN.md) §7 for the historical maintenance notes. `github-build/` is +frozen reference/documentation material only, and `rdgen/` is frozen vendored +historical/reference material; neither is an active workflow source. + +The published RustDesk client source/ref is 1.4.8 and the published DeskForge API +schema is `DatabaseVersion` 272. The local uncommitted corrective worktree targets +API schema 282; that local schema target is not published. Live provider execution and +MySQL/PostgreSQL migration/read/write coverage remain unverified. ## What goes where @@ -148,9 +160,15 @@ For the GitHub-based Windows client builder workflow — see [PLAN.md](PLAN.md) | `admin-ui/` | Vue 3 admin panel. MIT. | | `libs/` | Shared Rust libs. | | `docker/` | Dockerfiles + compose. | -| `github-build/` | Workflow + docs for building Windows client via GitHub Actions. | -| `win-builder/` | Native Windows build agent (fallback path, frozen). | -| `offline-kit/` | Frozen toolchain + sources (sovereign build kit). | -| `rdgen/` | Vendored reference: rdgen workflow patches (not running as a service). GPL-3.0. | +| `github-build/` | Frozen reference/documentation for fork workflows; no executable workflow copies. | +| `win-builder/` | Frozen manual/historical-only Windows build material; not the API path. | +| `offline-kit/` | Frozen dependency-freeze tool; verification and license inventory are incomplete, with no signature/attestation or full-sovereignty claim. | +| `rdgen/` | Frozen vendored historical/reference workflow material, not the active source; not running as a service. GPL-3.0. | | `PLAN.md` | Single source of truth for the project plan. | | `CHANGELOG.md` | Chronological log of changes. | + +The current repository has no tracked `vendor/` tree, `rustdesk-deps/` archive, or +client-release directory. Rust and Go manifests still use active third-party upstream +dependencies, and the configured RustDesk fork's `hbb_common` submodule is currently +upstream-referenced, dirty, and unpublished; upstream independence and MySQL/PostgreSQL +cross-database support therefore remain unverified. diff --git a/NOTICE b/NOTICE index b61240e..19a6860 100644 --- a/NOTICE +++ b/NOTICE @@ -20,7 +20,10 @@ This product includes software developed by: https://github.com/bryangerlach/rdgen Used as reference: build workflow patches (not as a running service). -The combined work is distributed under AGPL-3.0 (because server/ is AGPL-3.0). -Per-component LICENSE files retained in their respective subdirectories. +The covered DeskForge work is identified as AGPL-3.0 because `server/` is AGPL-3.0. +This does not relicense separate or independent components; their applicable licenses +and notices remain in force. The current license inventory is incomplete, with no +signatures or attestations recorded and no full sovereignty claim. +Per-component LICENSE files are retained in their respective subdirectories. This is a modified version. Original RustDesk: https://github.com/rustdesk/rustdesk diff --git a/PLAN.md b/PLAN.md index 97a8e40..0d0f7f8 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,21 +1,51 @@ # PLAN.md — DeskForge: Single Source of Truth -> Last updated: 2026-06-28 +> Last updated: 2026-08-11 > Related: [CHANGELOG.md](CHANGELOG.md) · [BUGS.md](BUGS.md) · [CONTRIBUTING.md](CONTRIBUTING.md) +> **Evidence labels (2026-08-10):** The published RustDesk client source/ref is +> 1.4.8 and the published DeskForge API schema is `DatabaseVersion` 272. The local +> uncommitted corrective worktree targets API schema 282; that local schema target is +> not a public current-schema or release claim. +> The current API path is provider dispatch → exact run/artifact retrieval → local +> validation and publication. Runner callbacks, local file-queue fallback, and +> sole-artifact selection are not current production behavior. Windows is the only +> production capability admitted by the API gate; Linux/Android remain gated pending +> PR11 evidence. The dispatch contract under test requires +> `return_run_details=true` and an exact HTTP 200 run-details response containing +> the run identity; standard 204 is intentionally unsupported because it cannot +> correlate the exact run. No normal GitHub operation is verified. The Go API owns +> port 21114; Rust services use 21115–21119, with relay WebSocket on 21119; current +> production Compose exposure remains +> 21114–21118. No live provider run or clean-environment build proof is claimed. +> Workflow approval requires a provider-derived verified annotated tag, the +> aggregate of all applicable active protected-tag rulesets with effective update +> and deletion protections, no bypass actors, and rejection of tag/branch label +> collisions. The local gate is not +> live-provider evidence. Its pending → building identity write is atomic, but +> provider dispatch and that database write are not an end-to-end atomic +> transaction: there is no durable outbox or distributed lease for the +> post-dispatch database-failure window. + --- ## 0. Project goal Self-hosted RustDesk server (hbbs/hbbr + API + admin panel) + **custom client builder** -that works without `rustdesk/rustdesk`, `rustdesk-org/*`, or `rustdesk.com`. +that is intended to reduce dependence on `rustdesk/rustdesk`, `rustdesk-org/*`, and +`rustdesk.com`; current source manifests still reference active third-party upstream +repositories, so full upstream independence is not verified. -**Active client build path:** GitHub Actions in the rustdesk fork. `win-builder/` -and `linux-build` are frozen fallbacks. +**Active client build path:** the API dispatches an owned workflow in the +configured RustDesk fork, polls the provider run, downloads the exact artifact +through the provider API, then validates, extracts, and publishes it locally. +`github-build/` and `rdgen/` are reference/documentation material only; +`win-builder/` and `linux-build` are manual/historical-only builders outside the +production API path. GitHub-first because: - free Windows runners -- fork is ready, min-test is green +- fork workflow is configured; the min-test result below is historical - standalone requires a separate Windows Server, not deployed --- @@ -25,46 +55,94 @@ GitHub-first because: ``` bashrusakh/ ├── DeskForge ← this repo (server, api, admin, docker) -├── rustdesk ← fork of rustdesk/rustdesk, tag 1.4.7 → 1.4.8 -│ ├── vendor/ ← cargo vendor (L1, ~20 rustdesk-org deps) -│ ├── .github/workflows/ ← rustqs-windows-min-test.yml, rustqs-linux.yml, rustqs-android.yml -│ └── releases/ ← offline-assets-1.4.7 (engine, usbmmidd, drivers) -├── hbb_common ← fork of rustdesk/hbb_common (required submodule) -└── rustdesk-deps/ ← archive of ~20 rustdesk-org repos (L1 backup) +├── rustdesk ← owned RustDesk fork and active workflow source +│ └── .github/workflows/ ← rustqs-windows.yml, rustqs-linux.yml, rustqs-android.yml +└── libs/hbb_common ← tracked shared source in DeskForge; not a submodule here ``` -**Current versions:** fork at 1.4.7 (tag), workflow updated for 1.4.8 (chore/bump-client-1.4.8). +**Published client source/ref:** 1.4.8, aligned with `offline-kit/versions.env`. The +local uncommitted corrective worktree targets API schema 282; the published DeskForge +schema remains `DatabaseVersion` 272. +The configured RustDesk fork's `libs/hbb_common` is a separate git submodule currently +recorded against upstream `rustdesk/hbb_common`; its local checkout is dirty and +unpublished. Active RustDesk and DeskForge manifests also reference third-party upstream +repositories/modules. Provider-side live execution and clean-build proof are not recorded +here. On 2026-08-11, `GOWORK=off go vet ./...` and `GOWORK=off go test ./...` +passed after test-only cache diagnostics and opt-in Redis test changes. Redis +integration tests and benchmarks run only when `DESKFORGE_TEST_REDIS_ADDR` is +configured; no live Redis run is recorded. MySQL/PostgreSQL, live provider, and +the other release/build gates remain unverified. DeskForge does not currently track a repository `vendor/` tree, client-release +directory, or `rustdesk-deps/` archive. + +The combined DeskForge distribution is identified as AGPL-3.0 for the covered work; +separate or independent API/UI/reference components retain their applicable upstream +licenses and notices. The license inventory is incomplete; no signatures or attestations +are recorded, and no full sovereignty claim is made. See [LICENSE](LICENSE) and [NOTICE](NOTICE). --- ## 2. Architecture -### Active path (GitHub Actions) +### Current path (GitHub Actions) ``` admin-ui (Custom Client form) - ↓ POST /custom_build + ↓ custom-build request Go API (DeskForge) - ↓ workflow_dispatch + enc_payload (AES-256-CBC + PBKDF2) -GitHub Actions [rustdesk fork, windows-2022] + ↓ workflow_dispatch + authenticated DFP1 enc_payload (AES-256-CBC + PBKDF2 + HMAC) +GitHub Actions [configured RustDesk fork, owned platform workflow] ↓ L1: config.rs (server + key) ↓ L2: custom_.txt (permanent password, allowCustom patch) ↓ L3: branding (rustqs, portable-packer) - ↓ POST /api/save_custom_client (encrypted) -Go API → /rdgen-data/output/{id}/ → admin-ui Download +provider run ← Go API polls provider status +provider artifact API → Go API downloads the exact artifact +Go API validates/extracts/publishes locally → admin-ui Download ``` **Security:** password never published — `enc_payload`, decrypted inside runner via -GitHub Secret `WORKFLOW_PAYLOAD_KEY`. Binary goes to your server, not a public release. - -### Fallback (frozen, do not deploy) +GitHub Secret `WORKFLOW_PAYLOAD_KEY`. The runner does not callback to the API; +the API retrieves the artifact through the provider API and publishes it locally, +not to a public release. + +### Workflow approval and schema evidence + +Before a provider-backed build can run, the API accepts only a provider-derived +verified annotated tag. The provider must report an accepted verification reason, +the aggregate of all applicable active protected tag rulesets for that label with +effective update and deletion protections, and no bypass actors. A tag/branch +collision with the same label is +rejected. The API rechecks the provider policy and exact workflow contents at the +resolved immutable commit before dispatch; raw refs, SHAs, credentials, and +workflow internals are not normal UI inputs. + +The published DeskForge schema is `DatabaseVersion` **272**. This uncommitted +corrective worktree targets local schema **282**, which is not published or live- +provider evidence. The additive fields are **280** +(`workflow_ref_approved`, approval status), **281** +(`workflow_ref_provider_verified`, provider-policy status), and **282** +(`workflow_ref_approval_sha`, the provider-resolved approval commit). Legacy +metadata-only configuration saves remain compatible with plaintext secret rows: +they can preserve legacy values without `SECRET_ENCRYPTION_KEY`; new or replaced +non-empty secret writes require the key, and a resave with the key encrypts legacy +plaintext values. + +Focused fake-transport and SQLite checks do not prove live GitHub/provider +approval, dispatch, polling, or artifact delivery. Dispatch requires the exact +HTTP 200 run-details contract (`return_run_details=true`), not a standard 204 or +latest-run inference. The local identity write is atomic, but no durable outbox +or distributed lease makes provider dispatch plus the database write one atomic +operation; a post-dispatch database failure remains a documented limitation. + +### Historical/frozen builders (not used by the current API) ``` admin-ui → Go API → jobs/{id}.json → SMB share → standalone Windows builder or Docker linux-build ``` -Not active: `win-builder/` untested (no Windows host), `build-linux` behind `--profile fallback`. +These local-queue builders are not a production fallback for the current API. +They remain only as historical/frozen material and must not be treated as an +alternative workflow source. --- @@ -72,14 +150,16 @@ Not active: `win-builder/` untested (no Windows host), `build-linux` behind `--p | Component | Status | Notes | | ------------------------------ | -------------- | ------------------------------------ | -| hbbs/hbbr (Rust) | ✅ running | ports 21114-21118 | +| hbbs/hbbr (Rust) | ✅ running | ports 21115-21119; relay WebSocket is 21119 | | Go API | ✅ running | users, address book, OAuth, LDAP, audit | | Admin UI (Vue 3) | ✅ running | 16 pages, 3 locales, DataTable, FilterBar | -| GitHub build (Windows) | ✅ active | min-test green, 3 layers, encryption | -| GitHub build (Linux) | 🟡 draft | workflow exists, not CI-validated | -| GitHub build (Android) | 🟡 draft | workflow exists, not CI-validated | -| win-builder standalone | ❄️ frozen | do not deploy, no Windows host | -| linux-build (Docker) | ❄️ frozen | manual fallback, `--profile fallback` | +| GitHub build (Windows) | 🟡 implemented | API path is provider-backed; no current live provider/clean-build proof | +| GitHub build (Linux) | 🟡 gated | workflow mapping exists; production capability remains disabled | +| GitHub build (Android) | 🟡 gated | workflow mapping exists; production capability remains disabled | +| `github-build/` | 📘 reference | documentation only; no executable workflow copies | +| `rdgen/` | 📘 reference | vendored historical/reference material, not the active source | +| win-builder standalone | ❄️ frozen | manual/historical only; not API path | +| linux-build (Docker) | ❄️ frozen | manual/historical only; not API path | | offline-kit | ❄️ frozen | re-freeze when client version changes | --- @@ -92,33 +172,42 @@ Not active: `win-builder/` untested (no Windows host), `build-linux` behind `--p | L2 | quick-support password| `custom_.txt` (signature checked — `allowCustom.py` patch removes check) | | L3 | branding (rustqs) | `Cargo.toml`, `Runner.rc`, portable-packer (`libs/portable/generate.py`) | -Full recipe: `rdgen/.github/workflows/generator-windows.yml` (vendored reference). +Historical reference: `rdgen/.github/workflows/generator-windows.yml`. +It is not the executable workflow source and must not be copied into the fork. --- ## 5. ✅ Completed milestones -- [x] Forks of rustdesk + hbb_common (1.4.7) -- [x] Offline kit: L1+L3, 11 artifacts, 5 GB (frozen) -- [x] GitHub min-test Windows: green, ~33 min, single-binary rustqs.exe -- [x] Go API: workflow_dispatch, poll, download, capability-URL TTL +- [x] Published RustDesk client source/ref is 1.4.8; local corrective API schema target is + `DatabaseVersion 282`, while the published DeskForge schema remains 272 +- [x] Offline-kit freeze/verify tooling and local artifact set retained; real-kit + verification remains blocked under PR10 +- [x] GitHub min-test Windows: historical green run, ~33 min, single-binary rustqs.exe +- [x] Go API: provider workflow dispatch and polling; provider API artifact download; + local validation, extraction, publication, and capability-URL TTL - [x] Admin UI redesign: design tokens, DataTable, AppDialog, FilterBar, 16 pages - [x] Security: encrypted-at-rest (AES-GCM), OAuth delete guard, audit, TTL - [x] Rust server: atomic blocklist, aur-fix, JWT -- [x] Database: ~272 migrations, SQLite/MySQL/PostgreSQL +- [x] Local schema target: `DatabaseVersion 282`; SQLite exercised locally, + MySQL/PostgreSQL configured but migration/read-write support remains unverified --- ## 6. Open roadmap -- [ ] **Linux + Android GitHub workflows** — CI validation + platform picker in UI +- [ ] **Linux + Android capability validation** — live workflow/artifact evidence + guarded platform picker - [ ] **Full client rebrand** — About, URLs, icons — in workflow, not in the fork - [ ] **Smoke test** for built binary (`--version`) - [ ] **Ballast cleanup** — remove MinGW leftovers, test containers --- -## 7. Workflow: new upstream rustdesk-client release +## 7. Historical fork maintenance notes for a new upstream rustdesk-client release + +The steps below are historical maintenance notes for the owned RustDesk fork. They are not +the DeskForge build execution path; active workflow files live only in that +fork. Do not copy workflow files from `github-build/` or `rdgen/`. When `rustdesk/rustdesk` publishes a new tag (e.g. 1.5.0), follow these steps: @@ -147,7 +236,7 @@ git commit -m "chore: point hbb_common to v1.5.0" git push origin v1.5.0 ``` -### 7.3. Update vendor +### 7.3. Update vendor (historical procedure; not current repository state) ```bash # On a machine with Rust: @@ -156,7 +245,8 @@ git add vendor/ && git commit -m "chore: vendor deps for v1.5.0" git push origin v1.5.0 ``` -Or if vendor is too heavy — upload `vendor-1.5.0.tar.gz` as a release asset. +Or if vendor is too heavy — historically, upload `vendor-1.5.0.tar.gz` as a release +asset. DeskForge does not currently contain that vendor tree or release asset. ### 7.4. Update offline-kit @@ -166,7 +256,7 @@ cd DeskForge/offline-kit bash freeze.sh source vendor engine ``` -### 7.5. Update offline-assets release +### 7.5. Update offline-assets release (historical operator procedure) ```bash # Upload engine/usbmmidd/driver to the fork: @@ -176,13 +266,14 @@ gh release create offline-assets-1.5.0 --repo bashrusakh/rustdesk \ artifacts/rustdesk_printer_driver_v4-1.4.zip artifacts/printer_driver_adapter.zip ``` -> **Note:** After publishing the release, version `1.5.0` will automatically appear in the admin UI -> (the `GET /api/admin/custom_build/versions` endpoint fetches fork releases tagged -> `offline-assets-*`). No hardcoded values in UI or YAML need to be changed. +> **Note:** After publishing the release, the version catalog can expose it in +> the admin UI when the configured repository contains the matching +> `offline-assets-*` release and source tag. The catalog is repository-derived; +> there is no hardcoded repository or obsolete version fallback list. ### 7.6. Adapt workflow -Compare upstream `build-for-windows-flutter` with `rustqs-windows-min-test.yml`: +Compare upstream `build-for-windows-flutter` with `rustqs-windows.yml`: - New system dependencies? - Changed `build.py` flags? - Changed `config.rs` / `custom_.txt` format? @@ -192,35 +283,12 @@ Port changes to the fork workflow. > **Important:** `bridge.yml` must stay **without `inputs.version`** — same as upstream. > Bridge and build must work from the same code (the fork). Do not add `repository:` to checkout. -### 7.7. Deploy workflows to fork (3 branches) +### 7.7. Workflow ownership -Workflow files live on three fork branches. After changes, update all: - -```bash -# 1) rustqs/min-test — execution (all dispatches go here) -git checkout rustqs/min-test -cp /path/to/DeskForge/github-build/rustqs-*.yml .github/workflows/ -cp /path/to/DeskForge/rdgen/.github/workflows/bridge.yml .github/workflows/ -git add .github/workflows/ -git commit -m "feat: update rustqs-* workflows for v1.5.0" -git push origin rustqs/min-test - -# 2) master — API discovery (workflow must exist on default branch) -git checkout master -cp /path/to/DeskForge/github-build/rustqs-*.yml .github/workflows/ -cp /path/to/DeskForge/rdgen/.github/workflows/bridge.yml .github/workflows/ -git add .github/workflows/ -git commit -m "feat: update rustqs-* workflows for v1.5.0" -git push origin master - -# 3) rustqs/master-workflows — mirror (backup for upstream sync) -git checkout rustqs/master-workflows -cp /path/to/DeskForge/github-build/rustqs-*.yml .github/workflows/ -cp /path/to/DeskForge/rdgen/.github/workflows/bridge.yml .github/workflows/ -git add .github/workflows/ -git commit -m "feat: update rustqs-* workflows for v1.5.0" -git push origin rustqs/master-workflows -``` +The `.github/workflows/` files in the owned RustDesk fork are the sole +executable source. `github-build/` is reference/documentation only, and the +vendored `rdgen` workflows are historical/reference material. Maintain active +workflow logic in the fork; do not create deployment copies in this repository. ### 7.8. Verify @@ -244,12 +312,14 @@ git push origin rustqs/master-workflows | Entity | What it is | Where stored | | ----------------------- | ---------------------------------------------------- | ------------------------------------- | | `offline-kit/` | Scripts (`freeze.sh`) + config (`versions.env`) | In git, in this repo | -| `offline-kit/artifacts/`| Output of freeze.sh: vendor.tar.gz, engine, SDK, MSI | Locally, **not in git** | -| `offline-assets-{tag}` | GitHub Release with binaries for CI | GitHub Releases of the rustdesk fork | - -**Why:** without this insurance, if `rustdesk/rustdesk` gets deleted or `rustdesk.com` -goes down, building a custom client becomes impossible. The kit freezes everything -while upstream is still available. +| `offline-kit/artifacts/`| Local freeze output (ignored/untracked) | Local operator storage only | +| `offline-assets-{tag}` | Provider-side asset naming convention | Availability and release proof unverified | + +**Why:** without this insurance, loss of upstream source or services can block a custom +client build. The kit is frozen local operator material; active third-party upstream +dependencies remain in the source manifests, and its current incomplete verification +does not prove upstream independence, sovereignty, a network-denied full build, a +signature, or release readiness. --- diff --git a/README.md b/README.md index 073a58d..52a45f0 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ git clone https://github.com/bashrusakh/DeskForge.git cd DeskForge/docker # docker-compose.yml: replace your-server, your-secret-jwt-key-change-this docker compose up -d -# Get the public key: -docker compose logs | grep "Public Key" +# Read the public key from the mounted server data path: +docker compose exec rustdesk cat /data/id_ed25519.pub ``` **Admin:** `http://your-server:21114/admin/` — login `admin`, password in logs. -**RustDesk client:** ID Server `your-server:21116`, Relay `:21117`, API `http://your-server:21114`, Key — from logs. +**RustDesk client:** ID Server `your-server:21116`, Relay `your-server:21117`, API `http://your-server:21114`, Key — paste the public-key contents returned by `docker compose exec rustdesk cat /data/id_ed25519.pub`; the file is at `/data/id_ed25519.pub` inside the container. --- @@ -31,6 +31,15 @@ docker compose logs | grep "Public Key" | 21116 | TCP/UDP | ID Server (hbbs) | | 21117 | TCP | Relay Server (hbbr) | | 21118 | TCP | WebSocket | +| 21119 | TCP | Relay WebSocket (hbbr) | + +The protocol uses 21119 for the relay WebSocket path. The production Compose +file currently publishes 21114–21118; expose 21119 separately when that public +WebSocket path is required. + +The published RustDesk client source/ref is 1.4.8 and the published DeskForge API +schema is `DatabaseVersion` 272. The local uncommitted corrective worktree targets +API schema 282; that local schema target is not published or live-provider evidence. --- @@ -39,6 +48,8 @@ docker compose logs | grep "Public Key" | Variable | Purpose | | ---------------------------------- | --------------------------------- | | `RELAY` | Relay server address | +| `HBBR_PORT` | Relay server port | +| `HBBS_PORT` | ID/Rendezvous server port | | `ENCRYPTED_ONLY` | Encrypted connections only | | `MUST_LOGIN` | Require login before connect | | `RUSTDESK_API_RUSTDESK_ID_SERVER` | ID server (hbbs) | @@ -46,9 +57,19 @@ docker compose logs | grep "Public Key" | `RUSTDESK_API_RUSTDESK_API_SERVER` | API server URL | | `RUSTDESK_API_KEY_FILE` | Path to public key file | | `RUSTDESK_API_JWT_KEY` | JWT secret | -| `RUSTDESK_API_GORM_TYPE` | sqlite / mysql / postgres | +| `RUSTDESK_API_GORM_TYPE` | sqlite / mysql / postgresql | | `RUSTDESK_API_LANG` | en / ru / zh-CN | -| `SECRET_CRYPT_KEY` | AES-GCM key for secrets at rest | +| `SECRET_ENCRYPTION_KEY` | AES-GCM key for secrets at rest | +| `SOURCE_DATE_EPOCH` | Trusted Docker build timestamp (optional) | + +New non-empty secret writes and secret-bearing Custom Builder operations require this +key and are rejected rather than stored as plaintext when it is missing. Legacy plaintext +rows remain readable; saving them again encrypts them when the key exists. + +Rust build metadata uses `SOURCE_DATE_EPOCH` when supplied. Without it, active +builds use the deterministic value `unknown`; wall-clock metadata is available +only for explicitly non-reproducible local debug builds with +`RUSTDESK_NON_REPRODUCIBLE_DEBUG=1`. --- @@ -56,17 +77,27 @@ docker compose logs | grep "Public Key" **Server (Rust + Go):** user CRUD, JWT, OAuth (GitHub/Google/OIDC), LDAP, groups, tags, address book (personal + shared with collections), peer-UUID binding, audit (login/connection/file-transfer), -server commands with persistence and audit log, encrypted-at-rest secrets, SQLite/MySQL/PostgreSQL, -captcha + brute-force protection. +server commands with persistence and audit log, encrypted-at-rest secrets, and configured +SQLite/MySQL/PostgreSQL drivers. SQLite is locally exercised; MySQL/PostgreSQL migration and +read/write coverage remain unverified. Captcha and brute-force protection are also included. **Admin UI (Vue 3):** Login, Dashboard, Devices, Users, Groups, Tags, OAuth, Server Config, Audit, Custom Client Builder, Profile, My Workspace, Guest Sharing. 3 locales (en/ru/zh_CN). Light/Dark/Auto themes. Shared UI: DataTable, AppDialog, AppDrawer, FilterBar, ActionsToolbar. -**Custom client:** GitHub Actions → rustdesk fork → `rustqs.exe` (Windows). -Builds with your server, key, permanent password. Single-binary via portable-packer (23 MB). -Linux/Android — in development. +**Custom client:** the API dispatches an owned workflow in the configured RustDesk +fork and retrieves the exact provider artifact for local validation/publication. +The published client source/ref is **1.4.8**. The local uncommitted corrective +worktree targets API schema `DatabaseVersion 282`; the published DeskForge schema +remains 272. +Windows is the only capability admitted today; +Linux and Android mappings remain gated pending PR11 evidence. No live provider +run or clean-environment build is claimed. Local workflow manifests, bridge/helper +source, Android app-ID/runtime-path checks, and package assertions are static evidence +only; no APK/package/install/runtime evidence exists. +The local schema target is `DatabaseVersion 282`; the published DeskForge schema is +272, and SQLite-only checks do not establish cross-database verification. **Not implemented (vs RustDesk Pro):** 2FA, RBAC, session recording, device policy, remote script, HA, backup/restore. @@ -85,38 +116,79 @@ HA, backup/restore. server/ — Rust hbbs/hbbr (signal + relay) api/ — Go REST API (Gin + GORM) admin-ui/ — Vue 3 + Element Plus admin panel -libs/hbb_common/ — shared Rust library (submodule) +libs/hbb_common/ — tracked shared Rust library in DeskForge docker/ — Dockerfile + compose + entrypoint -github-build/ — active client build workflow -win-builder/ — ❄️ FROZEN: standalone Windows builder -offline-kit/ — ❄️ FROZEN: dependency freeze tool (insurance against upstream death) -rdgen/ — vendored reference workflow (not a service) +github-build/ — reference/documentation for fork workflows (no executable copies) +win-builder/ — ❄️ FROZEN: manual/historical-only standalone builder +offline-kit/ — ❄️ FROZEN: dependency-freeze tool; current verification is incomplete +rdgen/ — ❄️ FROZEN: vendored historical/reference workflow material ``` +The configured RustDesk fork's `.github/workflows/` files are the sole executable +source for active client builds. `github-build/` and `rdgen/` contain reference or +frozen material only. + +The repository does not currently contain a `vendor/` tree, `rustdesk-deps/` archive, +or current client-release directory. The RustDesk fork and DeskForge server manifests +still reference active third-party upstream repositories/modules; upstream independence +is therefore a goal and boundary, not a verified current property. + --- ## Building ```bash cd docker -docker compose build # full build -docker compose up -d # start +docker compose build rustdesk # full server image +docker compose up -d rustdesk # start the rustdesk service +``` + +For client-build workflow maintenance, edit and validate the configured fork's +`.github/workflows/` files. `github-build/` contains reference documentation only; +it is not a deployment source. For the API development stack: + +```bash +cd api +docker compose -f docker-compose-dev.yaml up -d +``` + +### Go verification + +The broad local checks pass with the workspace disabled: + +```bash +cd api +GOWORK=off go vet ./... +GOWORK=off go test ./... ``` +Redis integration tests and benchmarks are opt-in and run only when +`DESKFORGE_TEST_REDIS_ADDR` is configured. No live Redis run is recorded; +MySQL/PostgreSQL, live provider, and the other release/build gates remain +unverified. + --- ## Forks (for custom client builds) -- [`bashrusakh/rustdesk`](https://github.com/bashrusakh/rustdesk) — fork of rustdesk/rustdesk @ 1.4.7 -- [`bashrusakh/hbb_common`](https://github.com/bashrusakh/hbb_common) — fork of rustdesk/hbb_common +- [`bashrusakh/rustdesk`](https://github.com/bashrusakh/rustdesk) — owned RustDesk fork at published client source/ref 1.4.8; local corrective API schema target is 282 +- [`bashrusakh/hbb_common`](https://github.com/bashrusakh/hbb_common) — intended fork of + `rustdesk/hbb_common`; the configured RustDesk fork currently records the upstream + `rustdesk/hbb_common` submodule, and its local checkout is dirty/unpublished, so clean + fork provenance is not verified -See [PLAN.md §7](PLAN.md#7-workflow-new-upstream-rustdesk-client-release) for the upstream update workflow. +See [PLAN.md §7](PLAN.md#7-historical-fork-maintenance-notes-for-a-new-upstream-rustdesk-client-release) for the upstream update workflow. --- ## License -AGPL-3.0 (server) + MIT (api/admin-ui). See [LICENSE](LICENSE) and [NOTICE](NOTICE). +The combined DeskForge distribution is identified as **AGPL-3.0** for the covered +DeskForge work. This does not relicense separate or independent components: component-level +notices and upstream licenses remain documented in [LICENSE](LICENSE) and [NOTICE](NOTICE), +and the API/admin UI retain their upstream MIT notices where applicable. The license +inventory is incomplete; no signatures or attestations are recorded, and no full +sovereignty claim is made. Based on: - [rustdesk/rustdesk-server](https://github.com/rustdesk/rustdesk-server) (AGPL-3.0) diff --git a/admin-ui/src/api/custom_client.js b/admin-ui/src/api/custom_client.js index f756f4f..3023336 100644 --- a/admin-ui/src/api/custom_client.js +++ b/admin-ui/src/api/custom_client.js @@ -23,9 +23,10 @@ export function remove (data) { }) } -export function detailByKey (key) { +export function download (id) { return request({ - url: '/custom_build/public/detailByKey/' + key, + url: `/custom_build/download/${id}`, + responseType: 'blob', }) } diff --git a/admin-ui/src/api/github_build_config.js b/admin-ui/src/api/github_build_config.js index 4fe1a65..d663b66 100644 --- a/admin-ui/src/api/github_build_config.js +++ b/admin-ui/src/api/github_build_config.js @@ -8,6 +8,18 @@ export function save (data) { return request({ url: '/github_build_config/save', method: 'post', data }) } +export function getWorkflowTags () { + return request({ url: '/github_build_config/workflow_tags' }) +} + +export function approveWorkflowRef (workflowTag) { + return request({ + url: '/github_build_config/approve_workflow_ref', + method: 'post', + data: { confirm: true, workflow_tag: workflowTag }, + }) +} + export function generateKey () { return request({ url: '/github_build_config/generate_key', method: 'post' }) } @@ -20,10 +32,6 @@ export function syncSecret () { return request({ url: '/github_build_config/sync_secret', method: 'post' }) } -export function syncPat () { - return request({ url: '/github_build_config/sync_pat', method: 'post' }) -} - export function dispatchTest () { // B-009: confirm=true — это реальный билд (тратит минуты Actions), не дешёвый чек. return request({ url: '/github_build_config/dispatch_test', method: 'post', data: { confirm: true } }) diff --git a/admin-ui/src/api/user.js b/admin-ui/src/api/user.js index 2e40595..bc5e3e7 100644 --- a/admin-ui/src/api/user.js +++ b/admin-ui/src/api/user.js @@ -8,6 +8,16 @@ export function login (data) { }) } +export function logout () { + return request({ + url: '/logout', + method: 'post', + timeout: 5000, + skipErrorMessage: true, + skipAuthRedirect: true, + }) +} + export function current () { return request({ url: '/user/current', diff --git a/admin-ui/src/components/changePwdDialog.vue b/admin-ui/src/components/changePwdDialog.vue index b3a19d6..3d11556 100644 --- a/admin-ui/src/components/changePwdDialog.vue +++ b/admin-ui/src/components/changePwdDialog.vue @@ -116,8 +116,8 @@ ElMessageBox.alert(T('OperationSuccess'), T('ChangePassword'), { autofocus: true, confirmButtonText: 'OK', - callback: (action) => { - userStore.logout() + callback: async (action) => { + await userStore.logout() router.push('/login') }, }) diff --git a/admin-ui/src/layout/components/setting/index.vue b/admin-ui/src/layout/components/setting/index.vue index 30ce037..aec28bb 100644 --- a/admin-ui/src/layout/components/setting/index.vue +++ b/admin-ui/src/layout/components/setting/index.vue @@ -47,14 +47,29 @@ import { ref } from 'vue' import { T } from '@/utils/i18n' import ThemeSwitch from '@/components/ui/ThemeSwitch.vue' + import { useRouter } from 'vue-router' + import { ElMessage } from 'element-plus' const userStore = useUserStore() const user = userStore const appStore = useAppStore() + const router = useRouter() + let logoutPromise = null const logout = () => { - userStore.logout() - window.location.reload() + if (logoutPromise) { + return logoutPromise + } + + logoutPromise = (async () => { + const logoutSucceeded = await userStore.logout() + await router.replace('/login') + if (!logoutSucceeded) { + ElMessage.error(T('LogoutFailed')) + } + })() + + return logoutPromise } const changePwdVisible = ref(false) diff --git a/admin-ui/src/permission.js b/admin-ui/src/permission.js index 6730570..31e6aa6 100644 --- a/admin-ui/src/permission.js +++ b/admin-ui/src/permission.js @@ -36,7 +36,7 @@ router.beforeEach(async (to, from, next) => { if (!userStore.route_names.length) { const info = await userStore.info() if (!info) { - userStore.logout() + await userStore.logout() next(`/login?redirect=${to.path}`) } else { next({ ...to, replace: true }) diff --git a/admin-ui/src/store/user.js b/admin-ui/src/store/user.js index a647e77..833bc29 100644 --- a/admin-ui/src/store/user.js +++ b/admin-ui/src/store/user.js @@ -1,10 +1,12 @@ import { defineStore, acceptHMRUpdate } from 'pinia' -import { current, login } from '@/api/user' -import { setToken, removeToken, setCode, removeCode } from '@/utils/auth' +import { current, login, logout as logoutRequest } from '@/api/user' +import { getToken, setToken, removeToken, setCode, removeCode } from '@/utils/auth' import { useRouteStore } from '@/store/router' import { useAppStore } from '@/store/app' import { oidcAuth, oidcQuery } from '@/api/login' +let pendingLogout = null + export const useUserStore = defineStore({ id: 'user', state: () => ({ @@ -18,10 +20,32 @@ export const useUserStore = defineStore({ }), actions: { - logout () { - removeToken() - removeCode() - this.$reset() + async logout () { + if (pendingLogout) { + return pendingLogout + } + + const token = this.token || getToken() + pendingLogout = (async () => { + let revoked = true + try { + if (token) { + await logoutRequest() + } + } catch (_) { + // Local cleanup is the fallback when the server cannot revoke the session. + revoked = false + } finally { + removeToken() + removeCode() + localStorage.removeItem('user_info') + this.$reset() + pendingLogout = null + } + return revoked + })() + + return pendingLogout }, saveUserData (userData) { diff --git a/admin-ui/src/utils/auth.js b/admin-ui/src/utils/auth.js index 0a6d674..40fdb35 100644 --- a/admin-ui/src/utils/auth.js +++ b/admin-ui/src/utils/auth.js @@ -12,7 +12,8 @@ export function setToken (token) { } export function removeToken () { - return localStorage.removeItem(TokenKey) + localStorage.removeItem(TokenKey) + return localStorage.removeItem('wc-option:local:access_token') } // 设置 code,并存储当前时间戳(单位:毫秒) diff --git a/admin-ui/src/utils/i18n/en.json b/admin-ui/src/utils/i18n/en.json index 882ba62..a657a90 100644 --- a/admin-ui/src/utils/i18n/en.json +++ b/admin-ui/src/utils/i18n/en.json @@ -5,6 +5,9 @@ "Logout": { "One": "Logout" }, + "LogoutFailed": { + "One": "Session revocation failed; you were signed out locally." + }, "Register": { "One": "Register" }, @@ -56,6 +59,72 @@ "Platform": { "One": "Platform" }, + "PlatformWindows": { + "One": "Windows 64-bit" + }, + "PlatformLinuxUnavailable": { + "One": "Linux x64 — experimental / unavailable (PR11)" + }, + "PlatformAndroidUnavailable": { + "One": "Android arm64 — experimental / unavailable (PR11)" + }, + "ProductionPlatformUnavailable": { + "One": "This platform is experimental and unavailable for production builds until PR11 evidence is complete." + }, + "WorkflowApprovalTitle": { + "One": "Workflow approval" + }, + "WorkflowApprovalDescription": { + "One": "Build workflows require a provider-derived, verified annotated tag and current live-provider evidence before approval." + }, + "WorkflowApprovalProtectedExplanation": { + "One": "The provider must confirm an active protected-tag policy for this label: updates and deletions are explicitly blocked, no bypass actors are present, and no branch has the same label. The provider must also verify the annotated tag and exact workflow at its resolved commit. Only the tag label is selectable; refs, SHAs, credentials, and workflow internals are never entered here. Local checks alone do not prove approval." + }, + "WorkflowApprovalState": { + "One": "Approval state" + }, + "WorkflowApprovalStatusApprovalRequired": { + "One": "Approval required" + }, + "WorkflowApprovalStatusProviderPolicyUnverified": { + "One": "Provider policy unverified" + }, + "WorkflowApprovalStatusApproved": { + "One": "Approved" + }, + "WorkflowApprovalTagLabel": { + "One": "Verified workflow tag" + }, + "WorkflowApprovalTagPlaceholder": { + "One": "Choose a provider-verified tag" + }, + "WorkflowApprovalCurrentTag": { + "One": "Current approved tag: {param}" + }, + "WorkflowApprovalLoading": { + "One": "Loading provider-verified tags..." + }, + "WorkflowApprovalLoadError": { + "One": "Provider-verified workflow tags could not be loaded." + }, + "WorkflowApprovalConfigError": { + "One": "Workflow approval state could not be loaded." + }, + "WorkflowApprovalEmpty": { + "One": "No provider-verified workflow tags are available." + }, + "WorkflowApprovalRetry": { + "One": "Retry" + }, + "WorkflowApprovalApprove": { + "One": "Approve selected tag" + }, + "WorkflowApprovalApproving": { + "One": "Approving..." + }, + "WorkflowApprovalRequestFailed": { + "One": "Approval was not recorded. Try again after reviewing the provider status." + }, "Status": { "One": "Status" }, @@ -736,8 +805,11 @@ "Host": { "One": "Host" }, - "HostnameOnlyHint": { - "One": "Hostname only, no port. Client appends the default port automatically." + "HostEndpointHint": { + "One": "Optional. Accepts a hostname or IP address with an optional port. RustDesk uses port 21116 only when the port is omitted." + }, + "HostEndpointPlaceholder": { + "One": "e.g. your-server.com or your-server.com:21116 (default when omitted)" }, "ApiServer": { "One": "API Server" @@ -745,6 +817,12 @@ "RelayServer": { "One": "Relay Server" }, + "RelayEndpointHint": { + "One": "Optional. Accepts a hostname or IP address with an optional port. RustDesk uses port 21117 only when the port is omitted." + }, + "RelayEndpointPlaceholder": { + "One": "e.g. your-server.com or your-server.com:21117 (default when omitted)" + }, "Key": { "One": "Key" }, @@ -871,6 +949,12 @@ "Building": { "One": "Building" }, + "Downloading": { + "One": "Downloading" + }, + "Extracting": { + "One": "Extracting" + }, "Done": { "One": "Done" }, diff --git a/admin-ui/src/utils/i18n/ru.json b/admin-ui/src/utils/i18n/ru.json index fe677ee..60e561a 100644 --- a/admin-ui/src/utils/i18n/ru.json +++ b/admin-ui/src/utils/i18n/ru.json @@ -5,6 +5,9 @@ "Logout": { "One": "Выход" }, + "LogoutFailed": { + "One": "Не удалось отозвать сессию; локальный выход выполнен." + }, "Register": { "One": "Регистрация" }, @@ -56,6 +59,72 @@ "Platform": { "One": "Платформа" }, + "PlatformWindows": { + "One": "Windows 64-bit" + }, + "PlatformLinuxUnavailable": { + "One": "Linux x64 — экспериментальная / недоступна (PR11)" + }, + "PlatformAndroidUnavailable": { + "One": "Android arm64 — экспериментальная / недоступна (PR11)" + }, + "ProductionPlatformUnavailable": { + "One": "Эта платформа экспериментальна и недоступна для production-сборок до подтверждения в PR11." + }, + "WorkflowApprovalTitle": { + "One": "Подтверждение workflow" + }, + "WorkflowApprovalDescription": { + "One": "Для одобрения workflow требуется полученный от провайдера проверенный аннотированный тег и актуальное подтверждение от провайдера." + }, + "WorkflowApprovalProtectedExplanation": { + "One": "Провайдер должен подтвердить действующую политику защиты тега для этой метки: обновление и удаление явно запрещены, обходных участников нет, а ветка с такой же меткой отсутствует. Он также должен проверить аннотированный тег и точный workflow на разрешённом коммите. Выбирается только метка тега; refs, SHA, учётные данные и внутренние данные workflow здесь не вводятся. Одних локальных проверок недостаточно для одобрения." + }, + "WorkflowApprovalState": { + "One": "Состояние подтверждения" + }, + "WorkflowApprovalStatusApprovalRequired": { + "One": "Требуется подтверждение" + }, + "WorkflowApprovalStatusProviderPolicyUnverified": { + "One": "Политика провайдера не подтверждена" + }, + "WorkflowApprovalStatusApproved": { + "One": "Подтверждено" + }, + "WorkflowApprovalTagLabel": { + "One": "Подтверждённый workflow-тег" + }, + "WorkflowApprovalTagPlaceholder": { + "One": "Выберите подтверждённый провайдером тег" + }, + "WorkflowApprovalCurrentTag": { + "One": "Текущий подтверждённый тег: {param}" + }, + "WorkflowApprovalLoading": { + "One": "Загрузка подтверждённых провайдером тегов..." + }, + "WorkflowApprovalLoadError": { + "One": "Не удалось загрузить подтверждённые провайдером workflow-теги." + }, + "WorkflowApprovalConfigError": { + "One": "Не удалось загрузить состояние подтверждения workflow." + }, + "WorkflowApprovalEmpty": { + "One": "Нет доступных подтверждённых провайдером workflow-тегов." + }, + "WorkflowApprovalRetry": { + "One": "Повторить" + }, + "WorkflowApprovalApprove": { + "One": "Подтвердить выбранный тег" + }, + "WorkflowApprovalApproving": { + "One": "Подтверждение..." + }, + "WorkflowApprovalRequestFailed": { + "One": "Подтверждение не записано. Проверьте состояние провайдера и повторите попытку." + }, "Status": { "One": "Статус" }, @@ -571,6 +640,12 @@ "StartBuild": { "One": "Запустить сборку" }, + "Downloading": { + "One": "Загрузка артефакта" + }, + "Extracting": { + "One": "Распаковка артефакта" + }, "VersionListLoading": { "One": "Список версий загружается, подождите..." }, @@ -606,5 +681,17 @@ }, "Upload": { "One": "Загрузить" + }, + "HostEndpointHint": { + "One": "Необязательное поле. Принимает имя хоста или IP-адрес с необязательным портом. RustDesk использует порт 21116 только если порт не указан." + }, + "HostEndpointPlaceholder": { + "One": "например, your-server.com или your-server.com:21116 (по умолчанию, если порт не указан)" + }, + "RelayEndpointHint": { + "One": "Необязательное поле. Принимает имя хоста или IP-адрес с необязательным портом. RustDesk использует порт 21117 только если порт не указан." + }, + "RelayEndpointPlaceholder": { + "One": "например, your-server.com или your-server.com:21117 (по умолчанию, если порт не указан)" } } diff --git a/admin-ui/src/utils/i18n/zh_CN.json b/admin-ui/src/utils/i18n/zh_CN.json index 2f9e36e..07931ea 100644 --- a/admin-ui/src/utils/i18n/zh_CN.json +++ b/admin-ui/src/utils/i18n/zh_CN.json @@ -5,6 +5,9 @@ "Logout": { "One": "退出登录" }, + "LogoutFailed": { + "One": "会话撤销失败;已在本地退出登录。" + }, "Register": { "One": "注册" }, @@ -56,6 +59,72 @@ "Platform": { "One": "平台" }, + "PlatformWindows": { + "One": "Windows 64 位" + }, + "PlatformLinuxUnavailable": { + "One": "Linux x64 — 实验性 / 暂不可用(PR11)" + }, + "PlatformAndroidUnavailable": { + "One": "Android arm64 — 实验性 / 暂不可用(PR11)" + }, + "ProductionPlatformUnavailable": { + "One": "此平台为实验性平台,在 PR11 完成验证前不可用于生产构建。" + }, + "WorkflowApprovalTitle": { + "One": "Workflow 审批" + }, + "WorkflowApprovalDescription": { + "One": "审批需要服务商提供的、已验证的注释标签,以及当前实时服务商证据。" + }, + "WorkflowApprovalProtectedExplanation": { + "One": "服务商必须确认该标签受到有效的受保护标签策略约束:明确禁止更新和删除、没有绕过参与者,并且不存在同名分支。服务商还必须验证注释标签及其解析到的确切 workflow 提交。这里只能选择标签名称;不能输入 ref、SHA、凭据或 workflow 内部信息。仅有本地检查不足以证明审批。" + }, + "WorkflowApprovalState": { + "One": "审批状态" + }, + "WorkflowApprovalStatusApprovalRequired": { + "One": "需要审批" + }, + "WorkflowApprovalStatusProviderPolicyUnverified": { + "One": "服务商策略未验证" + }, + "WorkflowApprovalStatusApproved": { + "One": "已审批" + }, + "WorkflowApprovalTagLabel": { + "One": "已验证的 workflow 标签" + }, + "WorkflowApprovalTagPlaceholder": { + "One": "选择服务商验证的标签" + }, + "WorkflowApprovalCurrentTag": { + "One": "当前已审批标签:{param}" + }, + "WorkflowApprovalLoading": { + "One": "正在加载服务商验证的标签..." + }, + "WorkflowApprovalLoadError": { + "One": "无法加载服务商验证的 workflow 标签。" + }, + "WorkflowApprovalConfigError": { + "One": "无法加载 workflow 审批状态。" + }, + "WorkflowApprovalEmpty": { + "One": "没有可用的服务商验证 workflow 标签。" + }, + "WorkflowApprovalRetry": { + "One": "重试" + }, + "WorkflowApprovalApprove": { + "One": "审批所选标签" + }, + "WorkflowApprovalApproving": { + "One": "审批中..." + }, + "WorkflowApprovalRequestFailed": { + "One": "审批未记录。请查看服务商状态后重试。" + }, "Status": { "One": "状态" }, @@ -584,6 +653,12 @@ "StartBuild": { "One": "开始构建" }, + "Downloading": { + "One": "正在下载构建产物" + }, + "Extracting": { + "One": "正在解压构建产物" + }, "VersionListLoading": { "One": "版本列表加载中,请稍候..." }, @@ -619,5 +694,17 @@ }, "Upload": { "One": "上传" + }, + "HostEndpointHint": { + "One": "可选字段。支持主机名或 IP 地址,可选择是否指定端口。仅在未指定端口时,RustDesk 使用默认端口 21116。" + }, + "HostEndpointPlaceholder": { + "One": "例如 your-server.com 或 your-server.com:21116(未填写端口时使用默认端口)" + }, + "RelayEndpointHint": { + "One": "可选字段。支持主机名或 IP 地址,可选择是否指定端口。仅在未指定端口时,RustDesk 使用默认端口 21117。" + }, + "RelayEndpointPlaceholder": { + "One": "例如 your-server.com 或 your-server.com:21117(未填写端口时使用默认端口)" } } diff --git a/admin-ui/src/utils/request.js b/admin-ui/src/utils/request.js index fc173a1..a409181 100644 --- a/admin-ui/src/utils/request.js +++ b/admin-ui/src/utils/request.js @@ -60,6 +60,34 @@ service.interceptors.response.use( * You can also judge the status by HTTP Status Code */ response => { + // Binary downloads do not use the normal JSON response envelope. + if (response.config.responseType === 'blob') { + const contentType = response.headers?.['content-type'] || '' + if (!contentType.includes('application/json')) { + return response + } + + // Auth/API failures can still arrive as a JSON envelope with HTTP 200. + // Parse those responses so a failed download is not saved as a .zip file. + return response.data.text().then(text => { + const res = JSON.parse(text) + if (res.code !== 0) { + ElMessage({ + message: res.message || 'error', + type: 'error', + duration: 5 * 1000, + }) + + if (res.code === 403) { + removeToken() + window.location.reload() + } + return Promise.reject(res) + } + return response + }).catch(error => Promise.reject(error)) + } + const res = response.data // for the endpoint /login-options @@ -70,13 +98,15 @@ service.interceptors.response.use( // if the custom code is not 20000, it is judged as an error. if (res.code !== 0) { - ElMessage({ - message: res.message || 'error', - type: 'error', - duration: 5 * 1000, - }) + if (!response.config.skipErrorMessage) { + ElMessage({ + message: res.message || 'error', + type: 'error', + duration: 5 * 1000, + }) + } - if (res.code === 403) { + if (res.code === 403 && !response.config.skipAuthRedirect) { removeToken() window.location.reload() } @@ -90,11 +120,13 @@ service.interceptors.response.use( && error.message.indexOf('timeout') > -1) { error.message = 'Connection Time Out!' } - ElMessage({ - message: error.message, - type: 'error', - duration: 5 * 1000, - }) + if (!error.config?.skipErrorMessage) { + ElMessage({ + message: error.message, + type: 'error', + duration: 5 * 1000, + }) + } return Promise.reject(error) }, ) diff --git a/admin-ui/src/views/custom-client/index.vue b/admin-ui/src/views/custom-client/index.vue index cf3dbff..29b0bea 100644 --- a/admin-ui/src/views/custom-client/index.vue +++ b/admin-ui/src/views/custom-client/index.vue @@ -26,22 +26,24 @@ - - - + + + + - + @@ -56,10 +58,12 @@ - + @@ -79,10 +83,12 @@ - + @@ -281,7 +287,7 @@ - {{ T('StartBuild') }} + {{ T('StartBuild') }} {{ T('Reset') }} {{ T('VersionListLoading') }} {{ T('VersionListEmpty') }} @@ -315,7 +321,7 @@ {{ T(statusLabel(row.status)) }} @@ -331,12 +337,13 @@