Commit Graph

457 Commits

Author SHA1 Message Date
mike e22e4f6fb6 Implement RUSTDESK_UNATTENDED_PWD_VISIBILITY to enable visibility of unattended passwords within the Admin UI even when User is logged in.
build / build-linux-amd64 (push) Successful in 1m45s
2026-05-18 18:25:29 +02:00
mike 5ec9776207 Documentation of available strategy keys (Rustdesk Client)
build / build-linux-amd64 (push) Successful in 1m48s
2026-05-18 18:23:21 +02:00
mike 8298252b06 Updated docker-compose.yml
build / build-linux-amd64 (push) Has been cancelled
2026-05-13 16:18:47 +02:00
mike 1e961cdd92 Implementing multi-language Admin UI
build / build-linux-amd64 (push) Successful in 2m2s
2026-05-09 16:58:20 +02:00
mike a7b3e83f02 Improve admin UI (remove unused functions, added hello-agent deployment
build / build-linux-amd64 (push) Successful in 2m2s
2026-05-09 14:14:58 +02:00
mike f7c359a8a0 Include WiFi and network interfaces to device details
build / build-linux-amd64 (push) Successful in 2m1s
2026-05-09 00:25:02 +02:00
mike 0dda056bda Implement asset inventory
build / build-linux-amd64 (push) Successful in 2m6s
2026-05-08 23:06:04 +02:00
mike 9d53999eea Implement password handling for unattended access
build / build-linux-amd64 (push) Successful in 2m0s
2026-05-08 11:34:07 +02:00
mike c1eaac1cb3 web UI and web client improvements.
build / build-linux-amd64 (push) Successful in 2m1s
2026-05-08 08:42:59 +02:00
mike 8ad3f43d21 ci(linux): add build workflow + Docker build instructions
build / build-linux-amd64 (push) Successful in 1m48s
2026-05-07 09:53:29 +02:00
mike 7e2c7a7e4c docs: refresh CONFIGURATION.md — TOTP self-service, new routes, bind flags
Caught up the docs to match what the dashboard actually does. Four spots
were stale enough to be misleading.

- TOTP / 2FA section rewritten. The doc still claimed admins enrolled
  TOTP from the Users action menu, but that button was removed when
  TOTP enrollment moved to the self-service profile page (two-step
  with QR + 6-digit confirmation; nothing written to user_totp_secrets
  until the user proves they have a working authenticator). Admins can
  disable a user's TOTP but can no longer enroll on someone's behalf.
  Also called out that OIDC-linked users skip local TOTP — their MFA
  lives at the IdP.

- Admin dashboard URLs table was missing nine routes that exist
  today: /admin/assets/{tailwindcss,htmx.min}.js (vendored CDN
  assets), /admin/pages/profile + four sub-routes (self-service
  profile flow), /admin/connect/:peer_id, and the two web-client SPA
  asset routes. Updated the Users-page row to mention the inline
  edit-profile + TOTP-disable controls.

- CLI flags / HTTP API & dashboard table now lists --http-listen and
  --ws-listen (they previously only appeared inside the nginx
  subsection — discoverability matters when an operator scans the
  flag tables looking for what's available). Added a one-liner about
  hbbr's matching --ws-listen flag.

- Security checklist gained a bind-flags hardening tip
  (--http-listen=127.0.0.1, --ws-listen=127.0.0.1 on both daemons
  when fronted by nginx) and a note about forwarding
  X-Forwarded-Proto: https so the dashboard generates wss:// URLs.

Sections cross-checked and confirmed accurate as-is: OIDC walk-through
+ role sync + troubleshooting, strategies, address books, recordings,
audit retention, SMTP, web client (routes / browser reqs / codec /
HUD diagnostics / build), database / backup notes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 21:27:32 +02:00
mike aa40784dc6 feat(deploy): bind-address flags for browser-facing ports + nginx docs
By default hbbs and hbbr bind every port to the wildcard, which collides
with operators wanting to put nginx/Caddy in front of the dashboard
(443) and the two browser-facing WebSocket ports (21118 rendezvous,
21119 relay) for TLS termination. Operators reported having to choose
between exposing hbbs directly (no TLS for `wss://`, breaks browsers
since the page is HTTPS) or moving the daemon to a different port.

New flags:
- hbbs `--http-listen=<HOST>` pins the HTTP API + dashboard port.
- hbbs `--ws-listen=<HOST>`   pins the WS rendezvous port (port + 2).
- hbbr `--ws-listen=<HOST>`   pins the WS relay port (port + 2).

All default to the wildcard (current behaviour). Set to `127.0.0.1` to
free up the corresponding public port for nginx.

The plain TCP/UDP ports used by desktop clients (21115 NAT test, 21116
rendezvous, 21117 relay) intentionally stay on the wildcard — desktop
clients bring their own framing + secretbox encryption and don't go
through nginx.

Implementation: a small `bind_tcp_listener(host, port)` helper in
common.rs that falls through to the existing `listen_any` when host is
empty, otherwise binds explicitly. Reused for both ws_port (rendezvous +
relay) and the http_port; the latter just builds a `SocketAddr` inline
since axum::serve takes one.

Documentation: new "TLS deployment with nginx" section in
docs/CONFIGURATION.md covering the port plan, the bind flags, full
example nginx vhost config (three server blocks: 443 dashboard,
21118 WSS rendezvous, 21119 WSS relay) with the WebSocket Upgrade
plumbing and bump-up timeouts that long sessions need, plus the
firewall list and the four common failure modes (SSL protocol error,
connection refused, 502, hung 200 instead of 101).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:43:20 +02:00
mike 4ccfe7a0e6 feat(admin): user-administration QoL — last-seen, profile page, OIDC awareness
Bundles the dashboard improvements that landed since 782e4c5 into one
commit. None of these change wire protocols or DB schema; they're all
UI + handlers on top of existing tables.

Users page (/admin/#users)
- "Last seen" column derived from MAX(tokens.last_used_at) per user
  (single GROUP BY query in users_last_seen_map). Shows relative
  short-form ("5m ago", "3h ago", "2d ago") with the absolute UTC
  timestamp in the cell title= for hover.
- Per-row dropdown gains an inline "Edit profile" form (display name,
  email, Save) so admins can edit other users' info without going
  through the self-service profile.
- "Enroll TOTP" button removed from the dropdown — TOTP enrollment is
  now self-service only, so admin-side enroll (which generated a secret
  out-of-band with no QR/confirm) is dead UX. "Disable TOTP" stays,
  shown only when the user has it enrolled, with hx-confirm.
- Per-row action popover (the ··· menu) now closes on outside click,
  via a global handler in index.html that targets details.relative.
  Deploy page's collapsible help section is unaffected (no `relative`).

Self-service profile page (/admin/#profile)
- New page accessible to any signed-in user (no admin gate). Sections:
  * Profile info — display name, email
  * Change password — requires current password + new + confirm
  * Two-factor authentication — enroll/disable
- TOTP enrollment is two-step with QR confirmation. POST .../totp/start
  generates a fresh secret, renders a server-side SVG QR code (new
  `qrcode` crate dependency, no_std SVG renderer) plus the manual-entry
  base32 secret. The secret rides in a hidden form field; nothing is
  written to user_totp_secrets until the user submits a valid 6-digit
  code at .../totp/confirm. Wrong code re-renders the same QR with a
  "code didn't match" notice so the user can retry without re-scanning.
- TOTP removal requires the current password.
- Sidebar now has a "My profile" link at the bottom.

OIDC linkage awareness
- UserRow exposes oidc_subject (was already in schema, just not surfaced
  in the struct). UserRow::is_oidc_linked() returns true for non-empty.
- Admin Users page: for OIDC-linked rows the password-set form is
  replaced by a small italic note ("Linked to OIDC — password sign-in
  is disabled."). Server-side, reset_password also rejects with the
  same message — UI hide is cosmetic; the handler check is the actual
  guarantee.
- TOTP column doubles as an auth-path indicator: OIDC-linked users get
  a cyan "OIDC" badge instead of (or in preference to) the violet
  "enrolled" badge.
- Self-service profile page: change-password and TOTP sections become
  short notes ("Your account signs in via the identity provider …" /
  "MFA is managed by your identity provider") for OIDC users.
  change_password handler also short-circuits with the same message.

Login page error fragment
- The auth handler returns 401 with an HTML body for bad credentials /
  disabled / not-admin / bad-TOTP, but HTMX skips the swap on 4xx by
  default — so login errors silently never appeared. Form now has
  hx-on::before-swap that forces shouldSwap=true and clears isError on
  4xx, but only for this form (page-level htmx:responseError handler
  that bounces 401s to /admin/login.html still applies elsewhere — it
  wouldn't loop here since this form sets isError=false).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 18:55:29 +02:00
mike 782e4c545e build(admin): vendor Tailwind + HTMX, drop CDN dependencies
The dashboard pages (index.html, login.html) were fetching tailwindcss
and htmx.org from cdn.tailwindcss.com and unpkg.com at runtime. That
leaks browser request metadata to third parties, makes the dashboard
inoperable on air-gapped deployments, and ties dashboard availability
to two SaaS CDNs the operator doesn't control.

Both files are now embedded in the hbbs binary (include_bytes!) and
served from /admin/assets/{tailwindcss.js,htmx.min.js}. Versions
pinned in source: Tailwind 3.4.16 (Play CDN JIT, the same JS the
<script src="cdn.tailwindcss.com"> tag was previously loading) and
htmx.org 1.9.10. To upgrade either: re-fetch the file at the same
path and rebuild hbbs.

Asset routes are unauthenticated so the login page can load them,
and served with Cache-Control: public, max-age=31536000, immutable
since version bumps roll with binary upgrades anyway.

Bundle size impact: +500 KB in the hbbs binary, fully cached on the
client after first load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:59:11 +02:00
mike 4308a2f112 feat: M6 web client QoL — clipboard, multi-monitor, quality, reconnect, H.264, docs
Builds on the M6a-g MVP (d07e98e) with five user-facing features that
take the in-browser remote-control client from "demo toy" to "workable
for daily use", plus user-facing documentation.

M6h — Text clipboard sync (both directions)
- Host → browser via Clipboard{format: Text} → navigator.clipboard.writeText.
- Browser → host via Cmd-V/Ctrl-V keydown intercept → navigator.clipboard
  .readText → Message{clipboard}, sent BEFORE the V keystroke so the
  host's paste hotkey lands on the freshly-synced text.
- Handles both single-format Clipboard (older peers) and MultiClipboards
  wrapper (peers ≥ 1.3.0; gated by clipboard.rs:is_support_multi_clipboard).
- Switched printable-with-modifier hotkeys (Ctrl-C etc.) from Unicode to
  Chr payload — host's process_unicode does key_sequence(char) which
  ignores modifiers, breaking copy/cut; process_chr respects them.
- Firefox refuses navigator.clipboard.readText() by default — accepted as
  a known browser limitation, host → browser direction works regardless.

M6i — Multi-monitor switching
- HUD picker shown when peer_info.displays > 1.
- On change: SwitchDisplay + CaptureDisplays{set:[idx]} two-message dance
  — required for clients ≥ 1.2.4 (we send "1.4.0"). Without the follow-
  up, switch_display_to leaves both video services subscribed and
  switching display 0 → 1 → 0 doesn't restore display 0.
- Mouse coords offset by the active display's virtual-desktop origin
  (DisplayInfo.x, .y). Without this, clicks on display 2 landed on
  display 1 because both share canvas (0,0) but only display 1 has
  origin (0,0) in virtual-desktop space.

M6j — Quality / FPS / mute controls
- Image quality preset (Low/Balanced/Best) → Misc{option: {image_quality}}.
- Custom FPS (15/30/60) → Misc{option: {custom_fps}}; host caps at 30
  unless allow_more_fps is advertised.
- Mute toggle additionally sends Misc{option: {disable_audio: Yes/No}}
  so the peer stops encoding audio while muted (saves CPU + bandwidth).

M6k — Auto-reconnect on transient drops
- session.recv() throw → reconnect with exponential backoff: 1s, 2s, 4s,
  8s, 16s, 30s, 30s, capped at 30s, max 10 attempts.
- Dim overlay sits on top of the canvas during retry; canvas keeps
  last-known frame for visual continuity.
- Auth errors (password/signature) bail immediately — no point retrying.
- User options (mute, image_quality, custom_fps, current display)
  re-applied to host on each successful reconnect, since host treats
  every session as fresh and resets to defaults.
- Architecture: `session` is a let-binding mutated on reconnect; HUD
  button closures read it at click-time so they automatically retarget.
  Input modules (mouse/keyboard/clipboard) get a Proxy that forwards
  method calls to whatever session is current — avoids re-binding
  window/canvas listeners on each reconnect.

M6l — H.264 video decode (Annex-B + SPS-derived codec string)
- decode/bitstream.ts: iterate Annex-B NAL units, derive avc1.PPCCLL
  from the keyframe's inline SPS (host's hwcodec defaults to high
  profile; a hardcoded baseline string would make WebCodecs refuse the
  stream).
- Defer H.264 decoder configure until first keyframe arrives.
- VP9 codec string corrected from level 1.0 (vp09.00.10.08) to level
  5.0 (vp09.00.50.08) — wrong level was probably forcing software
  decode in some browsers.
- Default prefer flipped to VP8 (cheapest software encoder; H.264 path
  stays implemented for hosts with hwcodec/nvenc).

M6m — docs/CONFIGURATION.md "Web client" section: routes, browser
matrix, network requirements (relay reachability + reverse-proxy WS
upgrade), feature status table, codec selection rationale, the
recv/dec/draw HUD diagnostic, build commands.

Bundle: 535 KB / ~75 KB gzipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:43:23 +02:00
mike d07e98e607 feat: M6 web client — view + control + audio in the dashboard
Adds a TypeScript SPA embedded in hbbs that lets a logged-in admin click
"Connect (web client)" on a Devices row and remote-control the peer from
the browser, no desktop client install required. View, mouse/keyboard
control, and host-audio playback all work end-to-end.

Architecture
------------
Pure browser app, no server-side WS proxy:

  Browser  ──ws://hbbs:21118── rendezvous (PunchHole + RequestRelay)
       │
       └──ws://hbbr:21119──── relay (paired by uuid)
                                  │
                                  └── peer (RustDesk desktop, any platform)

Same wire path the desktop client takes via `Client::request_relay` and
`Client::create_relay`. Browser-relay-only — no NAT punching, so we send
nat_type=SYMMETRIC in PunchHoleRequest to make the peer skip the direct
attempt and go straight to relay (initiate=true with a host-generated uuid
that we then use for our own relay leg).

The 5 wire steps:
  1. PunchHoleRequest → harvests signed peer Ed25519 sign-pk + relay host
     + the peer's session uuid from RelayResponse
  2. Verify the signed pk against /admin/connect's `id_ed25519.pub`
  3. Open WS to hbbr:21119, send RequestRelay with that uuid
  4. Read peer's Message{signed_id}, verify with peer_sign_pk, extract
     Curve25519 box pk; box-seal a fresh secretbox key under it; send
     Message{public_key} unencrypted
  5. Secretbox-encrypted stream from here. Hash → LoginRequest →
     LoginResponse with PeerInfo. Mode = Legacy (Translate silently drops
     ControlKey/Unicode payloads on the host side).

New Rust surface
----------------
- `admin_ui/connect.html` — SPA shell with `{{CUSTOM_CONFIG}}` placeholder
- `src/api/admin/pages/connect.rs` — gates on AuthedUser, injects per-request
  config (rendezvous host, relay host, server pubkey, peer_id, admin name)
  into the `<script id="custom-config">` tag, serves bundle.{js,css} via
  include_bytes!
- 3 routes added: GET /admin/connect/:peer_id and the two assets
- Devices dropdown gains a sky-blue "Connect (web client)" link that opens
  in a new tab

New TypeScript SPA (`web_client/`)
----------------------------------
Stack: pure DOM/TS, no React/Vue. Bundled by esbuild → `dist/bundle.{js,css}`
which is committed (cargo build needs no Node toolchain).

  src/main.ts                 boot + password retry loop + receive dispatch
  src/crypto.ts               tweetnacl wrapper (sign_open, box, secretbox)
                              + @noble/hashes/sha2 (works on plain http;
                              SubtleCrypto requires a secure context)
  src/proto/generated.{js,d.ts}  pbjs static-module from
                              libs/hbb_common/protos/{rendezvous,message}.proto
  src/transport/rendezvous.ts WS to hbbs; PunchHole + RequestRelay
  src/transport/relay.ts      WS to hbbr; duplex frame transport
  src/transport/session.ts    secure-handshake state machine + Hash/Login
                              + 8-byte LE secretbox sequence counter
                              (PRE-increment, send/recv independent —
                              matches libs/hbb_common/src/tcp.rs:317-320)
                              + preloginExtras stash for AudioFormat that
                              arrives before LoginResponse
  src/decode/video.ts         WebCodecs VideoDecoder (vp09.00.10.08 today;
                              h264/h265/av1/vp8 codec strings ready for M6f)
  src/decode/audio.ts         WebCodecs AudioDecoder (opus) → AudioContext;
                              detects f32 vs f32-planar AudioData layout
                              and deinterleaves when needed; gap-less
                              scheduling via a sliding playhead
  src/ui/canvas.ts            <canvas> with object-fit: contain letterbox;
                              auto-resizes on resolution change; FPS counter
  src/input/mouse.ts          MouseEvent → MouseEvent proto. Mask layout:
                              (button << 3) | type (0=move,1=down,2=up,
                              3=wheel). Letterbox-aware viewport→peer
                              coord mapping. Right-click suppresses the
                              browser context menu; left-click does NOT
                              preventDefault (would block focus)
  src/input/keyboard.ts       Window-level keydown/keyup → KeyEvent proto
                              in Legacy mode. Special keys → ControlKey
                              enum; printable → unicode codepoint (down
                              only, host's process_unicode does a single
                              key_click). Browser shortcuts allowlisted
                              (Cmd-T/N/W/R, Tab) so the user keeps tab
                              control. Ctrl+Alt+Del HUD button (host-side
                              `send_sas` is `#[cfg(windows)]`; no-op on
                              Mac/Linux hosts but present for parity)

Bundle size: 529 KB raw / ~74 KB gzipped. Tree-shaken protobufjs +
tweetnacl + @noble/hashes only.

Deployment notes
----------------
- WebCodecs and SubtleCrypto are gated to "secure context" origins —
  HTTPS, or http://localhost. Plain http://lan-ip won't work. Open via
  http://localhost during dev, or terminate TLS in front of hbbs (Caddy
  / nginx / Traefik) for production access.
- `--relay-servers <host>` on hbbs must point at a host where TCP/WS
  21119 is reachable from end-user browsers.

Wire-format gotchas this commit nails (each one was a session of bisecting)
--------------------------------------------------------------------------
- Hash.salt / Hash.challenge are proto `string` fields used as raw UTF-8
  bytes in the SHA-256 chain. NOT base64-decoded. `pwd_hash =
  SHA256(pwd_text || salt_utf8)`, `resp = SHA256(pwd_hash || challenge_utf8)`.
- Translate keyboard mode silently drops Unicode + ControlKey payloads on
  the host (input_service.rs:2022 has `// Do not handle unicode for now.`).
  Only Seq + Chr work in Translate. Use Legacy (mode=0) for everything.
- Browser is forced to relay path by sending nat_type=SYMMETRIC. The peer
  generates its OWN uuid in handle_punch_hole's symmetric branch; use that
  uuid (carried back in RelayResponse) for the relay leg, not a fresh one.
- Misc{audio_format} fires from the host's audio_service first-snapshot
  BETWEEN add_connection and login_response, so it lands on the wire
  before our session.recv() loop is set up. Session.open() captures
  pre-login messages into preloginExtras for the caller to replay.
- protobufjs static-module sets unpopulated oneof fields to JS `null`,
  not `undefined`. A `if (msg.cursor_id !== undefined)` cursor branch
  swallowed every other message type including Misc; switched to loose
  `!= null` comparison.
- WebCodecs AudioDecoder for opus emits `f32` (interleaved) AudioData —
  must deinterleave into separate AudioBuffer channels before playback.
- VideoDecoder/AudioDecoder/SubtleCrypto are SecureContext-only; need
  http://localhost or https:// on the *page origin*, not the WS targets.
- libsodium-wrappers ESM ships a broken relative import (`./libsodium.mjs`
  in a sibling package); switched to tweetnacl which has no such problem.
- WebCrypto's SubtleCrypto.digest() doesn't accept SharedArrayBuffer-backed
  Uint8Arrays in newer TS lib types; doesn't matter — we use @noble/hashes
  for sha256 anyway since Subtle is secure-context-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 13:55:40 +02:00
mike 8b0219a877 Prefill deploy parameters by defaults. 2026-05-02 18:49:28 +02:00
mike 4730c46f46 build: point hbb_common submodule at gitea fork
The submodule was carrying a local cherry-pick (proto tags 27/28 for
HttpProxyRequest/Response) on top of upstream rustdesk/hbb_common.
That commit (0c49f9a) only existed on the dev machine, so cloning
elsewhere and running `git submodule update` failed with
"upload-pack: not our ref" because github upstream doesn't have it.

Repoint the submodule URL at gitea.cstudio.ch/mike/hbb_common which
hosts the cherry-picked branch (pro-features-httpproxy) and a mirror
of main. Anyone cloning from now on can `git submodule update --init`
without further setup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:26:03 +02:00
mike 98b55e138e feat(admin): OIDC sign-in, role sync, deploy/delete UX, and docs
This commit lights up the missing pieces of the admin dashboard and the
OIDC flow that the desktop client already speaks. It bundles several
independent fixes that share enough touch points (oidc/callback,
admin/mod, database schema) that splitting was more churn than help.

OIDC — desktop client
- /api/login: when TOTP is enrolled, return type:"email_check"
  + tfa_type:"tfa_check" instead of type:"tfa_check". The Flutter
  client's switch only branches on access_token / email_check; the
  prior shape silently fell into "bad response from server".
- /api/login dispatcher: route the second leg to login_tfa_code when
  tfaCode + secret are both present, regardless of the declared type.
  The desktop client sends type:"email_code" for both email-code AND
  TOTP second legs and distinguishes by which field is set.
- /api/oidc/auth-query: drop the bogus extra {"body": "..."} envelope.
  The desktop client's http_request_sync already wraps every response
  in {status_code, headers, body}, and HbbHttpResponse::parse expects
  the auth payload at that level. Our extra envelope made the parser
  fail silently as DataTypeFormat and the poll loop spun until the
  180 s client timeout.
- UserPayload: add a required info: {} field; the Rust-side polling
  deserializer at src/hbbs_http/account.rs expects it (no
  #[serde(default)]). Without it the AuthBody parse failed on every
  poll, producing the same forever-pending symptom as above.
- Add an always-on info-level log line at the poll handler so this
  family of "client never advances" bugs is observable from hbbs.log.

OIDC — admin dashboard
- New unauthenticated entry points:
    GET /admin/oidc/providers      JSON list for login.html
    GET /admin/login/oidc/:provider 302 → IdP authorization endpoint
  The session is marked admin-flow via a sentinel ("__admin_ui__") in
  client_id_str / client_uuid so the existing /oidc/callback can tell
  it apart from a desktop device flow.
- /oidc/callback finishes admin sessions by setting the
  rd_admin_session cookie + 303 to /admin/. Non-admin users get a
  helpful error page instead of a session.
- Admin-flow callbacks SKIP device_claim() so the dashboard sign-in
  no longer inserts a phantom "__admin_ui__" device row in
  device_sysinfo, and the token's peer_id / peer_uuid columns stay
  blank instead of carrying the sentinel.
- admin_ui/login.html fetches the providers list on load and renders
  one button per enabled provider beneath the password form.

OIDC — role-based admin sync
- New per-provider config fields admin_role + roles_claim (in
  oidc.toml AND oidc_providers, via soft ALTER TABLE). When set, the
  callback evaluates the userinfo claim and forces users.is_admin
  accordingly on every login. Promotion AND demotion at the IdP
  propagate. Two claim shapes supported:
    - object key match  (Zitadel:
        urn:zitadel:iam:org:project:roles -> { "admin": {...} })
    - string-array contains (generic: roles -> ["admin","user"])
- user_upsert_oidc gains a desired_admin: Option<bool> arg so the same
  upsert path handles "leave admin alone" (desired_admin = None) and
  "force from IdP" (Some(bool)). Three unit tests cover both shapes
  plus the missing-claim case.

Admin dashboard — Address books
- Full CRUD for shared books from the dashboard:
  create, list shares, add/upgrade/remove a per-user share with
  read / read+write / full rules, delete the book.
- Personal books also get a Delete action — confirms with a stronger
  message that the user's desktop client will recreate an empty
  personal book on next sync if it's still signed in (deletion is
  effectively "reset to empty", not "permanently revoked"). Use in
  combination with user-delete to fully revoke.
- New DB methods: ab_create_shared, ab_delete (cascades peers/tags/
  peer_tags/shares), ab_get_owner_kind, ab_list_shares, ab_share_set
  (idempotent upsert), ab_share_remove.

Admin dashboard — Devices
- Delete action in the per-row menu. device_delete cascades through
  device_sysinfo, peer (rendezvous identity), heartbeat_commands and
  peer-scoped strategy_assignments. Audit logs, recordings, and AB
  entries that reference the peer are intentionally preserved
  (historical/manual data).

Admin dashboard — Deploy page
- New page that generates the unsigned CustomServer blob the desktop
  client accepts via `rustdesk --config <blob>` (see
  rustdesk/src/custom_server.rs:get_custom_server_from_config_string;
  the unsigned-JSON path is a real codepath, no Pro signing key
  needed). Form prefills the public key from id_ed25519.pub in CWD.
- Also emits the equivalent renamed-installer filename
  (rustdesk-host=...,key=... .exe). Strips api= from the filename
  when it equals the default http://<host>:21114 (Windows can't store
  : or / in filenames); warns when the API URL is non-default.

Login form fixes
- TOTP form field: rename serde wire field from tfa_code to tfaCode
  so the dashboard's HTMX form (input name="tfaCode") actually
  populates it. The previous mismatch silently dropped the code and
  the server kept asking for it.
- TOTP redirect guard: only redirect on empty 2xx body (real login).
  The TOTP-required path returns 2xx with an HTML prompt fragment
  that must NOT be redirected away from.

Docs
- New docs/CONFIGURATION.md covering all CLI flags, OIDC setup
  (generic + Zitadel walk-through), role-based admin sync, TOTP,
  strategies, address books, dashboard URL map, DB notes, and a
  pre-prod security checklist.

Schema
- soft ALTER TABLE oidc_providers ADD COLUMN admin_role / roles_claim
  (guarded by the duplicate-column-name swallower for SQLite < 3.35).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 01:05:52 +02:00
mike e183b386a1 fix(admin): show TOTP prompt instead of redirecting on 2fa-required login
The login form's hx-on::after-request redirected to /admin/ on any 2xx
response. The TOTP-required path also returns 2xx — with an HTML
fragment that unhides the TFA section — so the redirect fired before
the user ever saw the code input, locking out anyone who had enrolled
TOTP.

Only redirect when the 2xx body is empty (the real-login signal). When
the body is non-empty it's the prompt fragment, which htmx swaps into
#err and whose inline <script> reveals #tfa-section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:37:49 +02:00
mike 940b407560 fix(admin): drop overflow-hidden from action-dropdown tables
The Users and Devices tables had `overflow-hidden` on the wrapper div for
clean rounded corners. That same clipping was hiding the bottom half of
the per-row action menu (a `<details>`/`<summary>` popover absolutely
positioned inside the last cell). Removing `overflow-hidden` lets the
dropdown extend past the table edge — the popover already has its own
border + shadow, so the loss in corner aesthetics is negligible.

The other read-only tables (audit, recordings, oidc, address_books) keep
`overflow-hidden` since they don't host popovers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:26:43 +02:00
mike 5b288d671c feat: M5 admin dashboard (HTMX + Tailwind CDN, embedded HTML)
A web admin UI for the rustdesk-server, mounted at /admin/* on the
existing HTTP API listener. Single-binary deploy preserved — the two
HTML files live in admin_ui/ and are pulled into the binary via
include_str! at build time, so there's nothing extra to ship.

================================================================================
Architecture
================================================================================
- Stack: HTMX 1.9 + Tailwind play CDN. No SPA, no Node toolchain. Pages
  are server-rendered HTML fragments returned by Rust handlers via
  Html<String>; the index.html shell uses hx-get to drop a fragment into
  the main pane and hx-push-url for back-button history.
- Auth: same Bearer-token table the API uses. The dashboard log-in form
  POSTs username + password (+ optional TOTP) to /admin/login; on success
  the server mints a token and pins it in an HttpOnly + SameSite=Strict
  cookie (`rd_admin_session`). The AuthedUser extractor was extended to
  accept either the Authorization: Bearer header (curl, desktop client)
  OR the session cookie (browser).
- Embedding: src/api/admin/mod.rs has `include_str!("../../../admin_ui/index.html")`
  + login.html. No tower_http::ServeDir wildcard — we ran into axum 0.5
  routing conflicts between literal /admin/login routes and an /admin/*
  catch-all, so each HTML file is its own explicit route.

================================================================================
M5a — foundation
================================================================================
Files:
  admin_ui/index.html   page shell + sidebar + HTMX + 401-bounces-to-login
  admin_ui/login.html   credentials + TOTP form, posts to /admin/login
  src/api/admin/mod.rs  router + include_str! + Cache-Control: no-cache
  src/api/admin/auth.rs /admin/login POST (form-encoded), /admin/logout POST
  src/api/admin/me.rs   sidebar fragment ("Signed in as <name>")
  src/api/middleware.rs `AuthedUser` now reads either Bearer OR cookie
  src/api/state.rs      `admin_ui_dir` (informational; UI is embedded)
  src/main.rs           --admin-ui-dir flag (empty disables the dashboard)

The login flow asks for TOTP transparently in the same form when the
target user has a secret enrolled, so the dashboard inherits the TOTP
gate from the API auth surface for free.

================================================================================
M5b — full CRUD pages
================================================================================
- Users (src/api/admin/pages/users.rs) — list, create, password reset,
  toggle admin / status, TOTP enroll / unenroll, delete. TOTP enroll
  surfaces the secret + otpauth URL once, on a dismissible banner above
  the table.
- Devices (devices.rs) — list with hostname/OS/last-heartbeat/conn count,
  force-disconnect (queues `heartbeat_commands` row consumed at the next
  /api/heartbeat tick), force-sysinfo refresh.
- Device groups (groups.rs) — list / create / delete / add member /
  remove member. Per-group section, with an add-member dropdown of users
  not yet in the group.
- Strategies (strategies.rs) — list / create / edit config_options /
  delete. config_options is validated as a JSON object on the server side
  before persist; bad JSON is reflected to the page with a friendly
  error notice.
- Address books (address_books.rs) — read-only overview of all books
  with owner, kind (personal / shared badge), peer count, GUID.
- OIDC providers (oidc.rs) — read-only list of what's configured. Editing
  remains operator-side via --oidc-config TOML or direct SQL.

================================================================================
M5c — audit + recordings browsers
================================================================================
- Audit log (audit.rs) — three tabs (Connections / File transfers /
  Alarms), each capped at the latest 200 rows. Tab pills are HTMX links
  with hx-get + hx-target="#main" so the tab switch is a single fetch.
- Recordings (recordings.rs) — read-only list with peer / size / state /
  start / finish. Streaming download is a follow-up; for now operators
  pull files from --recording-dir directly.

================================================================================
DB methods added
================================================================================
- Users:    users_list_all, user_set_status, user_set_admin,
            user_set_password, user_delete, user_has_totp,
            raw_update_user_email
- Devices:  devices_list_all, device_sysinfo_get_conns,
            heartbeat_command_queue (also used elsewhere; surfaced)
- Groups:   device_groups_list_all, device_group_members,
            device_group_create, device_group_delete,
            device_group_add_member, device_group_remove_member
- Strategy: strategies_list_all, strategy_create,
            strategy_update_config, strategy_delete
- Audit:    audit_conn_list, audit_file_list, audit_alarm_list
- Misc:     ab_list_all_with_owner, recordings_list

All use the runtime sqlx::query("...") form (matching the project-wide
convention) so the SQLite compile-time-check macros don't require these
new tables to pre-exist in the dev DB.

================================================================================
Conventions enforced
================================================================================
- Every page handler gates on require_admin(&AuthedUser) — non-admin
  users get an HTTP 403 + JSON envelope, which the SPA shell catches and
  bounces back to the login form.
- HTML fragments are produced via `format!`-with-named-args; html_escape
  is centralized in src/api/admin/pages/shared.rs and applied to every
  user-supplied string before it lands in the DOM.
- All mutations return either the updated table fragment OR
  notice_html(kind, msg) + the table — same pattern across pages, so
  HTMX swap targets stay simple (always #region innerHTML).
- Cookie carries no path restriction so it also authorizes /api/* calls
  the dashboard might want to make from the browser; HttpOnly +
  SameSite=Strict mitigates XSS / CSRF; Max-Age tracks ApiConfig's
  session_ttl_secs (30 days).

================================================================================
Verification
================================================================================
1. cargo build --release — clean.
2. End-to-end smoke test:
   - /admin/ serves index.html (4406 bytes), /admin/login.html serves
     login.html (2598 bytes).
   - POST /admin/login with valid creds returns 200 + Set-Cookie
     `rd_admin_session=…; HttpOnly; Path=/; SameSite=Strict; Max-Age=…`.
   - All eight /admin/pages/* fragments return 200 with cookie.
   - Users CRUD round-trip: create alice → toggle admin → disable →
     reset password → enroll TOTP (32-char secret displayed once) →
     unenroll → delete; self-action guard rejects suicide deletes.
   - Groups CRUD: create engineering → add alice as member → SQL
     confirms the row.
   - Strategies: valid JSON accepted, invalid JSON rejected with a
     friendly notice.
   - Audit tabs: all three render 200; empty-state messages appear when
     no rows.
   - /admin/logout clears the cookie; subsequent /admin/me returns 401.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:13:35 +02:00
mike 8ecf05b106 feat: HTTP-over-rendezvous fallback (HttpProxyRequest)
Closes the M4 plan. When `OPTION_USE_RAW_TCP_FOR_API=Y` (typical in
locked-down networks where direct HTTPS to port 21114 is blocked), the
client wraps every /api/* request in an HttpProxyRequest protobuf and
ships it over the already-encrypted rendezvous TCP channel. We now decode
those messages on hbbs and dispatch them through the *same* axum Router
the HTTPS listener uses — so every existing handler (login, AB, audit,
TOTP, OIDC, devices/cli, plugin-sign, …) is reachable through this path
with zero per-route plumbing.

Components
==========
- libs/hbb_common (submodule, pro-features-httpproxy branch): backports
  HeaderEntry / HttpProxyRequest / HttpProxyResponse + union tags 27/28
  from upstream @87b11a7 onto our pinned @83419b6. Proto-only — the rest
  of hbb_common is unchanged so we keep the tokio 1.x / axum 0.5 / pinned
  reqwest fork intact (a full submodule bump risked breaking those).
- src/api/http_proxy.rs: the dispatch shim. Holds a `Mutex<Option<Router>>`
  populated by `api::serve` before the HTTPS listener starts, builds an
  `http::Request<Body>` from the proto fields (sanitizing hop-by-hop
  headers, defaulting Content-Type: application/json), runs it through
  `router.oneshot(req)`, and serializes the response into HttpProxyResponse.
  Tower added as a direct dep with the `util` feature for ServiceExt.
- src/api/mod.rs: pub mod http_proxy; install_router(app.clone()) before
  axum::Server::bind to share the router.
- src/rendezvous_server.rs::handle_tcp: new match arm right before the
  catch-all that decodes HttpProxyRequest and replies with an
  HttpProxyResponse via the existing Sink::TcpStream(..., Encrypt) path.
  The reply is automatically secretbox-sealed by `send_to_sink`, so the
  end-to-end channel is encrypted symmetrically with secure_tcp.
- examples/http_proxy_test.rs: end-to-end smoke test that opens a TCP
  connection, walks the secure_tcp handshake by hand (read server's
  signed box pubkey, derive symmetric key, send sealed reply), then
  ships an HttpProxyRequest GET /api/login-options and verifies the
  response is 200 + ["account"]. Used as the validation gate.

New crate deps
==============
- tower = "0.4"     (features = ["util"]) — for ServiceExt::oneshot
- http-body = "0.4" — for the Body trait import in dispatch

Verification
============
1. cargo build --release — clean.
2. examples/http_proxy_test against a fresh hbbs:
   [ok] secure_tcp handshake complete
   [ok] sent HttpProxyRequest GET /api/login-options
   [ok] response status = 200
   [ok] response body   = ["account"]
   [pass] full HTTP-over-rendezvous round trip verified
3. hbbs log confirms the secure_tcp handshake completed and the dispatch
   went through the standard axum router.

Notes on cherry-pick vs submodule bump
======================================
The plan flagged the bump as the riskiest M4 item because newer
hbb_common pulls newer tokio that breaks axum 0.5. The proto-only cherry
pick keeps everything stable; the upstream-divergence cost is one extra
commit in the hbb_common submodule that we own.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 19:29:46 +02:00
mike 3e89d61566 feat: add Pro-equivalent management API on top of OSS hbbs
Brings the rustdesk-server up to feature parity with RustDesk Server Pro for
the API surface the desktop client expects (CONSOLE_API.md). Implemented as
an in-process axum router mounted by hbbs alongside its existing
rendezvous + relay TCP/UDP/WS listeners; everything persists in the existing
SQLx + SQLite database via additional CREATE TABLE IF NOT EXISTS migrations.

================================================================================
M1 — Auth foundation + heartbeat + sysinfo
================================================================================
- New tables: users, tokens, device_sysinfo.
- Endpoints: HEAD+GET /api/login-options, POST /api/login, POST /api/logout,
  POST /api/currentUser, POST /api/heartbeat, POST /api/sysinfo_ver,
  POST /api/sysinfo.
- Bearer-token auth: tokens are 32 random bytes (base64url); only the
  sha256 of the token is stored. `tokens.last_used_at`/`expires_at` slide
  forward on every authenticated request (30-day TTL by default).
- Bcrypt-cost-10 password hashing, always wrapped in
  tokio::task::spawn_blocking to keep the runtime responsive.
- New CLI flags --http-port, --bootstrap-admin-username,
  --bootstrap-admin-password.
- Heartbeat returns the `sysinfo: true` flag on first contact and after
  cfg.sysinfo_ver bumps; sysinfo upload returns the bare-string body
  ("SYSINFO_UPDATED" / "ID_NOT_FOUND") the client expects.

================================================================================
M2 — Address book, device groups, accessible peers
================================================================================
- New tables: address_books, address_book_shares, address_book_peers,
  address_book_tags, address_book_peer_tags, device_groups,
  device_group_members. Soft-ALTER adds device_sysinfo.user_id (the
  binding from a device to its enrolled user, set by /api/login).
- Endpoints: POST /api/ab/settings, POST /api/ab/personal,
  POST /api/ab/shared/profiles, POST /api/ab/peers, POST /api/ab/tags/{guid},
  POST /api/ab/peer/add/{guid}, PUT /api/ab/peer/update/{guid},
  DELETE /api/ab/peer/{guid}, POST /api/ab/tag/add/{guid},
  PUT /api/ab/tag/rename/{guid}, PUT /api/ab/tag/update/{guid},
  DELETE /api/ab/tag/{guid}, GET+POST /api/ab (legacy single-blob fallback),
  GET /api/device-group/accessible, GET /api/users, GET /api/peers.
- Share-rule enforcement (1=read, 2=read/write, 3=full) at the top of every
  AB mutation. Owners are full; other rules come from
  address_book_shares (direct or via device_group). Rejection is HTTP 200 +
  {"error":"read-only"} so the client doesn't yank the session.
- New CLI flags --ab-legacy-mode, --ab-max-peers-per-book.
- Action endpoints (peer add/update/delete, tag CRUD) return HTTP 200 with
  EMPTY body on success — matches the Flutter _jsonDecodeActionResp at
  ab_model.dart:2002 which treats {} as the literal error string "null".

================================================================================
M3 — Audit, recording, strategy push
================================================================================
- New tables: audit_conn (PK guid echoed back to client),
  audit_file, audit_alarm, recordings, strategies, strategy_assignments,
  heartbeat_commands.
- Endpoints: POST /api/audit/conn (returns {"guid":"..."}),
  POST /api/audit/file, POST /api/audit/alarm, PUT /api/audit (note update),
  POST /api/record?type={new|part|tail|remove}.
- Recording uploader: filesystem state machine under --recording-dir;
  filenames sanitized to a single Normal path component to block traversal;
  `tail` writes the first ≤1024 bytes at offset 0 after all `part` chunks.
- Heartbeat extended to:
  * resolve a per-peer strategy (peer > device-group > user, highest
    priority wins) and emit `strategy.config_options` + `extra` +
    `modified_at`.
  * read-and-delete heartbeat_commands rows so an admin can queue
    `disconnect: [conn_id]` or force `sysinfo: true` via SQL and have it
    delivered on the next 15-second tick.
- New CLI flags --recording-dir (default ./recordings),
  --recording-max-size-mb, --audit-retention-days.

================================================================================
secure_tcp on the rendezvous TCP listener (M3 polish)
================================================================================
A logged-in client conditionally calls secure_tcp() on its TCP rendezvous
connection (src/client.rs:427-431, gated on `key && token` both non-empty).
OSS hbbs had no KeyExchange handler at all on TCP rendezvous, so the
client's secure_tcp_impl read timed out with "Failed to secure tcp:
deadline has elapsed". Added:
- A try_secure_tcp_handshake helper that, on every accepted TCP connection,
  generates an ephemeral box keypair, signs the box public key with the
  server's Ed25519 sk (already loaded for relay-response signing), sends
  KeyExchange, then waits 5s for the client's reply.
  - Reply is KeyExchange[client_box_pk, sealed_sym_key] -> decrypt the
    sealed key, install Encrypt on both halves of the stream.
  - Reply is any other RendezvousMessage -> buffer it and replay through
    the normal handle_tcp dispatcher (plain-mode clients filter unsolicited
    KeyExchange via get_next_nonkeyexchange_msg, so our preceding KX is
    harmless).
  - Reply never comes (timeout) -> fall through to plain mode.
- Sink::TcpStream now carries an Option<Encrypt>; outgoing writes are
  sealed when keyed. Symmetric Encrypt is cloned for inbound (`dec`) and
  outbound (`enc`) so the two directions track independent counters.

================================================================================
M4 — Advanced auth (TOTP, email-code, OIDC), CLI assign, plugin signing
================================================================================
- New tables: user_totp_secrets, pending_tfa_challenges,
  pending_email_codes, oidc_providers, oidc_sessions. Soft-ALTER adds
  users.oidc_subject.
- /api/login extended:
  * type:"account" (existing) — issues an `tfa_check` challenge (5-min
    nonce in `secret`) when the user has TOTP enrolled.
  * type:"tfa_code" — verifies the nonce + the 6-digit TOTP code against
    user_totp_secrets.secret_b32.
  * type:"email_code" — passwordless. First leg mints a 6-digit code and
    sends it via SMTP (or logs to stdout when --smtp-host is empty);
    second leg verifies. Brute-force capped at 5 attempts per code, then
    the row is purged.
- /api/oidc/auth + GET /oidc/callback + GET /api/oidc/auth-query implement
  the standard OAuth2 authorization-code flow with userinfo. Discovery via
  <issuer>/.well-known/openid-configuration with an in-memory cache.
  --oidc-config TOML upserts providers at startup; --public-base-url builds
  the redirect_uri.
- New endpoints: POST /api/2fa/enroll (admin-only, returns secret_b32 +
  otpauth_url), POST /api/2fa/unenroll, POST /api/devices/cli (used by
  `rustdesk --assign`; binds device to user, ensures device-group, adds
  AB entry, attaches peer-scoped strategy), POST /lic/web/api/plugin-sign
  (Ed25519 over the request body using the same id_ed25519 secret).
- /api/login-options is now dynamic: returns ["account"], plus "email_code"
  when SMTP or ALLOW_DEV_EMAIL_CODE is set, plus an "oidc/<name>" entry
  per enabled provider in oidc_providers.
- New CLI flags --smtp-host, --smtp-port, --smtp-user, --smtp-pass,
  --smtp-from, --smtp-tls, --public-base-url, --oidc-config.
- New crate deps: tokio (fs/io-util features), totp-rs, lettre (rustls +
  builder + smtp-transport, no defaults), toml.

================================================================================
Code organization
================================================================================
- src/api/                 axum router + shared state + error envelope
  ├── ab/                  address book endpoints (settings/profiles/peers/
  │                        tags/legacy/rules)
  ├── audit/               conn/file/alarm/note
  ├── oidc/                providers/discovery/auth/callback/poll
  ├── record/              storage state machine + handler
  ├── strategy/            resolver wrapper around DB
  ├── auth.rs              login/logout/currentUser
  ├── devices_cli.rs       /api/devices/cli
  ├── email.rs             SMTP transport (lettre) + dev-mode stdout fallback
  ├── error.rs             ApiError enum -> HTTP 200/401/403/404 + JSON envelope
  ├── groups.rs            /api/device-group/accessible
  ├── heartbeat.rs         /api/heartbeat
  ├── middleware.rs        AuthedUser extractor (Bearer -> sha256 -> token row)
  ├── pagination.rs        Page<T> + PageQuery
  ├── peers.rs             /api/peers
  ├── plugin_sign.rs       /lic/web/api/plugin-sign
  ├── state.rs             AppState + ApiConfig (incl. EmailConfig)
  ├── sysinfo.rs           /api/sysinfo, /api/sysinfo_ver
  ├── twofa.rs             /api/2fa/enroll, /unenroll
  └── users.rs             UserPayload + /api/users + bcrypt helpers

================================================================================
Conventions enforced throughout
================================================================================
- All new SQL uses the runtime sqlx::query("...") form (NOT the query!
  macro) so first-time builds don't require DATABASE_URL to point at a DB
  containing the new tables.
- Soft-ALTER helper (try_alter) swallows "duplicate column name" errors so
  schema migrations are idempotent across re-runs and existing-DB upgrades.
- Bcrypt compares always via spawn_blocking.
- Tokens (Bearer access_token, TFA challenge nonce, OIDC poll handle) are
  always 24-32 random bytes from sodiumoxide::randombytes; the Bearer is
  stored only as its sha256.
- Constant-time hash comparison for email codes.
- Action endpoints return HTTP 200 with empty body on success; HTTP 200 +
  {"error": "..."} for business errors so the client doesn't get logged
  out; 401 only from the auth middleware.

Tested end-to-end via curl + a stock RustDesk client (M1-M2 verified
against two laptops; M3 verified against the strategy-push and
force-disconnect paths; M4 verified via direct flow tests + a mock IdP for
OIDC). Stock client connect now works whether the user is signed in or
not (the secure_tcp regression that blocked logged-in connect is fixed).

The remaining piece on the M4 plan — HttpProxyRequest, the TCP-over-
rendezvous fallback for clients with OPTION_USE_RAW_TCP_FOR_API=Y — is
gated on bumping the OSS server's vendored hbb_common to a commit that
includes proto tags 27 and 28. That work lives on a separate branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 19:07:01 +02:00
WC3D 815c728837 fix(security): update mio from 0.8.5 to 0.8.11 (#633)
Co-authored-by: aikido-autofix[bot] <119856028+aikido-autofix[bot]@users.noreply.github.com>
2026-02-17 14:28:35 +08:00
RustDesk bd7bc52c43 Update build.yaml 2026-01-13 10:26:46 +08:00
rustdesk 34cbd57e71 bump version 2026-01-13 10:20:40 +08:00
rustdesk 46e9ecd47d fix pr 2026-01-12 17:28:25 +08:00
rustdesk d0770e6c06 high rustc 2026-01-12 15:08:01 +08:00
rustdesk f87372ead0 GHCR manifest 2026-01-12 14:59:45 +08:00
RustDesk 04efb7033c Update README.md 2026-01-12 14:54:50 +08:00
RustDesk 9bae9f2f39 Update build.yaml 2026-01-12 13:28:04 +08:00
rustdesk 7c2057c360 remove annoying fmt check 2026-01-10 16:33:46 +08:00
rustdesk f61b31f04e 1.1.15 debain log 2026-01-10 16:07:16 +08:00
rustdesk bb455a3eb7 merge ghcr to build.yml 2026-01-10 15:51:06 +08:00
RustDesk 631618a6be Update build.yaml 2026-01-10 14:47:34 +08:00
rustdesk 34946219c3 update workflow 2026-01-09 19:12:49 +08:00
rustdesk 5d1b4ba78a connection log query 2026-01-06 23:59:47 +08:00
Nawer 7e921be539 chore: Added kubernetes example file (#623) 2026-01-06 17:58:33 +08:00
lrin 716041bac1 libs: Fix hbb_common Submodule (#553)
The hbb_common Submodule was accidentally deleted in https://github.com/rustdesk/rustdesk-server/commit/c650217919ce6b67bf62a3d0c092e41f0d37b18b
2025-11-22 15:24:50 +08:00
Guiorgy 6b72cefbc5 define the HOME env to allow running rootless (#612) 2025-11-22 15:20:15 +08:00
Geert Stappers 386c83874c debian/changelog more like the first two (#573)
* debian/changelog more like the first two

Added "Who and When" lines, added empty lines as separator.
The time stamps where retrieved from the git commit log.

All entries look now like:

rustdesk-server (1.1.7) UNRELEASED; urgency=medium

  * ipv6 support

 -- rustdesk <info@rustdesk.com>  Wed, 11 Jan 2023 11:27:00 +0800

rustdesk-server (1.1.6) UNRELEASED; urgency=medium

  * Initial release

 -- open-trade <info@rustdesk.com>  Fri, 15 Jul 2022 12:27:27 +0200

* debian/changelog: reformat a date stamp

The "wrong format" was discovered by Lintian.
2025-11-03 13:57:06 +08:00
Geert Stappers 5cc309dc9f new file: debian/README.source (#592)
Describes how to build
2025-11-03 13:56:00 +08:00
rustdesk 4e8d6d08e2 fix https://github.com/rustdesk/rustdesk-server/issues/435 2025-11-03 13:03:19 +08:00
c8a5d41906 chore: Update to Windows 2022 runner image (#568) 2025-07-13 07:33:30 +08:00
rustdesk 2c57e51ec5 higher default bandwidth 2025-06-22 15:03:13 +09:00
RustDesk 796a75af5c Update README.md 2025-06-16 14:46:51 +08:00
rustdesk f09b79c6da README-ZH.md 2025-06-16 00:23:50 +08:00
rustdesk 1e84478837 simplify doc 2025-06-16 00:23:33 +08:00
RustDesk 2417c7ddd0 Update build.yaml 2025-05-11 20:54:24 +08:00