Commit Graph
522 Commits
Author SHA1 Message Date
naiba fb5c37e983 fix(oauth2): validate Host header against allowlist for callback URL
GHSA-9rc6-8cjv-rcvx: getRedirectURL derived the OAuth2 callback URL from the
raw Host header, so a forged Host (or a provider with loose redirect-URI
matching) could divert a victim's authorization code to an attacker origin and
bind their identity. Trust the request Host only when it is an operator-declared
dashboard host (IsReservedDashboardHost, same allowlist guarding NAT routing);
otherwise fall back to the configured InstallHost.
2026-06-05 02:42:28 +00:00
naiba 78f5b014ee fix(csrf): set Secure on csrf cookie only over HTTPS
CodeQL go/cookie-secure-not-set (CWE-614) flagged the nz-csrf cookie as
missing the Secure attribute. Mirror writeOauth2StateCookie and derive
Secure from the request scheme instead of hardcoding it: forcing
Secure=true would make browsers drop the cookie on plain-HTTP intranet
deployments, breaking the double-submit CSRF pair and 403-ing every
unsafe request.
2026-06-05 01:08:00 +00:00
naiba 8285a3d99f docs(mcp): note server.exec reaps the whole process tree
Document in the tool description and protocol comment that the agent
kills the entire process group/JobObject on return or timeout, so
background jobs must fully detach (setsid/screen/tmux/systemd-run).
2026-06-02 03:31:36 +00:00
naiba 836f1db4b7 feat(service): replace EnableShowInService with HideForGuest
Mirror the server HideForGuest flag on services: rename the field
(dropping the gorm default), invert userCanViewService so a service is
visible to guests by default and hidden only when HideForGuest is set,
and flip the public-stats filter in servicesentinel accordingly. Update
the visibility and permission-matrix tests to the new semantics.
2026-06-02 03:31:36 +00:00
naiba bb38a9d4d2 fix(mcp): accept agents reporting version without v prefix and stop masking tool errors
compareSemver fell back to lexical string ordering when the numeric
segments were equal, so an agent reporting "2.1.0" compared as older than
the gate constant "v2.1.0" (0x32 < 0x76). This wrongly flagged every agent
as unsupported_agent, breaking server.exec and fs.* on all online servers.
Drop the fallback: semverParts already strips the v prefix, so equal
numeric segments mean equal versions.

The error was further masked because tool error responses shipped a
structuredContent {error_code,error} that violates the tool outputSchema
(which requires exit_code/stdout/...). Strict MCP clients validated it and
rejected the whole response with -32602, hiding the real isError text.
Omit structuredContent on error; the cause stays in content[].text.
2026-05-31 16:18:57 +00:00
naiba 083bc985c5 feat(auth): split inventory scope out of server scope
Carve a new nezha:inventory:{read,delete,*} resource family out of
nezha:server:*. Listing and deleting servers/server-groups (GET /server,
/server-group, /ws/server, batch-delete/server[-group], MCP server.list)
now require nezha:inventory:*, while nezha:server:* covers per-server
runtime operations (get, exec, fs, config, metrics, batch-move,
force-update).

Also declare MCP tool OutputSchema for exec/fs/meta/server/transfer and
wrap server.list output in {servers,count} so strict MCP clients accept
the structured result. Correct the /file all-of scope entry and the stale
server:read documentation.
2026-05-31 15:14:41 +00:00
naibaandcloudcode 401713bab5 ci(gosec): silence G101 false positives on PAT context keys and scope const
gosec G101 flags apiTokenCtxKey, apiTokenLastUsedCtxKey, and the
ScopeNotificationGroupWrite identifier as hardcoded credentials. They are
context-key names and a scope string, not secrets. Annotate them with
#nosec G101 (matching the existing JWTSecretEnvKey precedent) so the
gosec CI step passes without disabling the rule globally.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-31 08:16:55 +00:00
naibaandcloudcode 18c044d959 fix(security): set real IP from peer in ConfigUsePeerIP gRPC mode
In ConfigUsePeerIP mode ctxWithRealIP validated connectingIp but never
assigned it to ip, so CtxKeyRealIP was left empty. model.CheckIP and
model.BlockIP short-circuit on an empty IP, which silently disabled the
gRPC WAF block table and brute-force token counters for every peer-IP
deployment — including the stream interceptors this path now backs.

Assign ip = connectingIp so the WAF and BlockIP observe the source. The
empty-header path is unchanged (it intentionally opts out of IP WAF).

Add rpc_test.go covering peer-IP IPv4/IPv6, the no-peer error, and the
no-header opt-out so the behaviours cannot be re-coupled.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-31 07:45:23 +00:00
naibaandcloudcode b1d84c6b2f fix(security): harden gRPC stream WAF, MCP fs arg validation, and PAT server binding
- ServeRPC now applies real-IP + WAF interceptors to streaming RPCs
  (RequestTask, IOStream), not just unary calls; without them
  authHandler.check saw an empty real IP so brute-force BlockIP counters
  never keyed on a source and the WAF block table was bypassed at the
  stream entrypoint. Factored real-IP resolution into ctxWithRealIP shared
  by unary and stream paths.
- handleFsRead rejects negative offset/length; handleFsWrite validates
  if_match_sha256 is 64 hex chars before dispatch.
- createAPIToken always verifies each server_id exists (even for admins),
  so an admin cannot bind a PAT to a not-yet-created server id that a
  future server would auto-inherit.
- Add relay zero-length-chunk rejection regression test.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-31 05:51:32 +00:00
naibaandcloudcode 05e14981b5 fix(security): verify transfer-token HMAC and sign CSRF double-submit cookie
- consumeTransferToken now recomputes and constant-time compares the
  entry's HMAC-SHA256; previously the signature was minted but never
  checked, so the documented "防篡改" guarantee was hollow and security
  rested solely on the sync.Map key's randomness.
- CSRF switches from a raw-random double-submit cookie to a signed token
  (nonce.HMAC-SHA256 keyed by JWTSecretKey). The middleware now also
  validates the signature, defeating sibling-subdomain cookie tossing
  where a naive header==cookie pair would otherwise pass.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-31 05:48:50 +00:00
naibaandcloudcode d4837dfc7a fix(security): reserve dashboard hosts from NAT and re-check DDNS profile ownership at worker time
GHSA-x6fg-52vr-hj4w: NAT is a commonHandler any authenticated member can
create, and newHTTPandGRPCMux matches r.Host against NAT before dispatching
the dashboard/gRPC handlers, so a member could register the dashboard's own
host as a NAT Domain and hijack global routing. Add IsReservedDashboardHost
(InstallHost/ListenHost plus an operator-declared ReservedHosts list for
reverse-proxy deployments), reject reserved hosts on create/update, and drop
pre-planted records when building the startup cache.

GHSA-39g2-8x68-pmx8: bind-time CheckPermission let a member pre-bind a DDNS
profile ID that the victim would only create later. GetDDNSProvidersFromProfiles
now re-validates ownership by server owner UID and skips foreign-owned
profiles (UserID==0 is treated as a migration artifact, not an admin grant).

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-31 02:04:35 +00:00
naibaandcloudcode e8dabf5bc6 feat(auth): add PAT auth, scoped REST/MCP access, CSRF, and tenant isolation
Introduce Personal Access Tokens (nzp_*) as a stateless auth path alongside
JWT, gated per-endpoint by a scope middleware (nezha:{resource}:{verb}) with
fail-closed empty-scope defaults and a server-id whitelist. Self-management
endpoints (profile, api-tokens, oauth2 bind, refresh-token) explicitly reject
PATs to block privilege-escalation chains. A revoke registry tears down active
long-lived connections (terminal, fm, ws, transfer, mcp) the moment a PAT is
deleted, with a tombstone closing the revoke->register race.

Add an MCP endpoint that proxies tool calls (exec, fs read/write/delete,
transfer) to agents over gRPC, guarded by origin/DNS-rebinding checks, a
per-token rate limiter, audit logging, and a kill switch. Serialize all
sends through the IOStream wrapper to honour grpc-go's concurrency contract.

Add CSRF double-submit protection on unsafe cookie-authenticated methods,
exempting authenticated PAT requests by context identity (not a forgeable
Authorization header). Apply visibility/whitelist filtering consistently
across list, get-by-id, and mutate paths to enforce tenant isolation.

Migrate legacy mcp:* scopes: rewrite read/exec to nezha:* equivalents and
drop dangerous write/delete/wildcard grants.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-30 15:56:44 +00:00
naibaandcloudcode c94639e199 fix(server-group): hide guest-empty groups from guest listing
listServerGroup returned every group, including ones whose only
servers are HideForGuest=true (or that have zero members), to
unauthenticated callers. The group name itself is then leaked to
guests even though every server it references is hidden from them.

Guest visitors now only see groups that contain at least one guest-
visible server. Authenticated members and admins keep full visibility
(including their own empty groups) so management UI still works.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-26 04:07:50 +00:00
naibaandcloudcode a4f4cb1f34 fix(fm): switch create FM session to POST to defeat CSRF
GET /api/v1/file?id=<server> created an FM stream on the agent stream
and committed real state change (TaskTypeFM dispatched). With JWT
cookie SameSite=Lax a victim's browser would still send the cookie on
a top-level cross-site GET, so an attacker could trick a logged-in
user into opening an FM session on any of their own servers, consuming
resources and triggering the agent's FM machinery without consent.

Mirror the GHSA-8qhj-4f8c-j8qg fix: move the route to POST. SameSite=
Lax cookies are not sent on cross-site POST. Frontend (admin-frontend)
adjusted in a follow-up commit.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-26 04:07:49 +00:00
naibaandcloudcode a8f014838c fix(service): hide EnableShowInService=false services from sideband endpoints
GHSA-vrmh-5mmx-hjwx: GET /api/v1/server/:id/service and
GET /api/v1/service/:id/history both iterate the raw service list and
emit ServiceName / timing for any service that happens to monitor the
queried server, ignoring the owner's EnableShowInService=false flag.
Both routes are on optionalAuth, so unauthenticated visitors could
enumerate hidden services by name and timing.

Introduce userCanViewService(c, service):
- EnableShowInService=true     -> always visible
- admin                        -> always visible
- authenticated owner          -> visible (HasPermission)
- everyone else                -> hidden

getServiceHistory rejects unknown-or-invisible service with the same
'service not found' message so the endpoint cannot be used as an
oracle. listServerServices pre-filters the sorted service list.

Owners and admins keep their existing visibility into their own hidden
services.

Tests:
- TestUserCanViewServiceVisibleServiceIsPublic
- TestUserCanViewServiceHiddenServiceRejectsGuest
- TestUserCanViewServiceHiddenServiceRejectsForeignMember
- TestUserCanViewServiceHiddenServiceAllowsOwner
- TestUserCanViewServiceHiddenServiceAllowsAdmin

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-26 03:59:59 +00:00
naibaandcloudcode 9c0fa84c95 fix(cron): switch manual trigger to POST to defeat CSRF
GHSA-8qhj-4f8c-j8qg: GET /api/v1/cron/:id/manual changed shared state
on the agent stream, and the JWT cookie is SameSite=Lax so a victim's
browser would send the cookie on a top-level cross-site GET. An
attacker could trick a logged-in user into firing any of their own
cron commands.

Switch the route to POST: SameSite=Lax cookies are not sent on cross-
site POST, closing the CSRF window without introducing a new token.

Tests:
- TestCronManualTriggerRejectsCrossSiteGET locks in that GET no longer
  resolves.
- TestCronManualTriggerAcceptsSameSitePOST locks in the legitimate POST
  still works.

Frontend (admin-frontend) updated in a follow-up commit.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-26 03:59:50 +00:00
naibaandcloudcode e05700c2f0 feat(jwt): server-side session table with keyId + obfuscated uid claims
Replace the {user_id, ip} claim pair with {keyId, uid}:
- keyId is a 32-byte random id that points to a row in the new
  jwt_sessions table holding the real user id, bound IP, UA hash,
  TokenVersion and expiry.
- uid is the user id encoded through pkg/idcodec; mismatch between
  claim uid and session.UserID trips WAF block on the caller IP.
- identityHandler now rejects unknown/revoked/expired sessions, IP
  drift and stale TokenVersion. Refresh updates session.ExpiresAt.

User.TokenVersion bumps on password change and revokes outstanding
sessions, so a leaked JWT secret alone is no longer enough to forge
a token. JWTSession rows are GC'd every 10 minutes (expired + grace
or revoked >24h). OAuth2 callback shares the same issue path.

Includes regression tests for happy path, mismatched claim uid,
revoked session, TokenVersion bump, IP drift and unknown keyId.

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
2026-05-26 03:51:05 +00:00
naiba 6b88cdb012 feat: server transfer rotation 2026-05-25 10:17:34 +00:00
naibaandnaiba/CloudCode 0f7af0bcb2 fix(rpc): bind io streams to target agents
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-21 02:01:54 +00:00
naibaandnaiba/CloudCode 9d67134148 fix(controller): filter service transfer stats by viewer
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-19 00:35:02 +00:00
naibaandnaiba/CloudCode fb120074ac chore(security): pin JWT algorithm + SameSite and harden OAuth2 state cookie
- Set GinJWTMiddleware.SigningAlgorithm to "HS256" explicitly so a future
  library default change (or an alg:none confusion attempt) cannot weaken
  token validation. This matches the current gin-jwt default, so behaviour
  is unchanged.
- Set CookieSameSite to Lax: same as the modern-browser default, but
  pinned so server-side intent is clear and CSRF on cross-site POST is
  blocked while top-level GET (OAuth callback) still works.
- Move the nz-o2s OAuth2 state cookie into writeOauth2StateCookie and
  set HttpOnly=true. The frontend does not read this cookie, so HttpOnly
  is strictly an XSS-hardening win with no behaviour change.

JWT Cookie HttpOnly/Secure are intentionally left default for now: the
frontend reads \`!!document.cookie\` to display login state and many
deployments terminate TLS at an upstream proxy — flipping those would
require a coordinated frontend change.

Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-18 15:17:59 +00:00
naibaandnaiba/CloudCode 097d4f7382 fix(controller): hide foreign server IDs from forceUpdateServer response
The previous loop only checked ownership inside the
\`server != nil && server.TaskStream != nil\` branch. A foreign online
server returned permission denied for the whole batch while foreign
offline / unknown IDs silently went into the Offline bucket — the
response-shape delta let a RoleMember enumerate other users' online
server IDs by submitting them in batches.

Drop both foreign and unknown IDs silently into the Offline bucket
(without dispatching the upgrade task) so the response is indistinguishable
across those three states.

Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-18 15:16:59 +00:00
naibaandnaiba/CloudCode 36297699f5 fix(rpc): bind io_stream sessions to creator to prevent terminal/fm hijack
createTerminal and createFM correctly check server ownership before
issuing a stream UUID, but terminalStream and fmStream only verified
that the UUID existed. Any authenticated user holding a valid stream
UUID could attach to it, gaining the original creator's live shell or
file-manager session — and the UUID is exposed via URL path (referer
leaks, access logs, browser history, frontend error reporters).

Bind the creator user ID into ioStreamContext at CreateStream time,
expose StreamOwnership and IsStreamAuthorizedForUser, and check
ownership in terminalStream/fmStream before the WebSocket upgrade so a
rejected attempt does not tear down the legitimate stream via defer.

NAT streams are also routed through CreateStream(_, 0); they are not
reachable from /ws/terminal or /ws/file so a sentinel user ID is fine.

Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-18 15:16:40 +00:00
naibaandnaiba/CloudCode b2294f11dd fix(ws): scope server stream visibility to viewer ownership
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-17 03:41:28 +00:00
naibaandnaiba/CloudCode 7061651587 test: cover multi-user permission boundaries
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-17 10:24:19 +08:00
naibaandnaiba/CloudCode 2573ba7522 fix(controller): restrict waf and online-user listings to admins
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-17 10:24:19 +08:00
naibaandnaiba/CloudCode 0ca7ee38c0 fix(controller): filter listing endpoints by ownership
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-17 10:24:19 +08:00
naibaandnaiba/CloudCode 2a65a7db80 fix(controller): enforce ownership on notification group, NAT server, and batch move
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-17 10:24:19 +08:00
naibaandnaiba/CloudCode 58f98db1a0 fix(controller): validate trigger task ownership
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-05-17 10:24:19 +08:00
naibaandnaiba/CloudCode de9ab16c5a fix: 恢复被误删的 AuthCodeURL 调用,修复编译失败
上次提交误删了 url := o2conf.AuthCodeURL(...) 导致编译失败。
同时添加注释说明 Cookie Secure 属性动态设置的原因。

Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-03-01 04:05:53 +00:00
naibaandnaiba/CloudCode 16c4a6c9f3 Fix: 设置Cookie Secure属性增强安全性
- 根据运行环境动态设置Secure属性
- 开发环境兼容HTTP,生产环境强制HTTPS
- 解决CodeQL扫描发现的安全漏洞

Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
2026-03-01 03:49:20 +00:00
NikoCat233 a42ef6e8bb Filter hidden servers in guest server-group API 2026-02-27 00:53:31 +08:00
3d0c9b963c feat(v2.0.0): tsdb (#1162)
* feat: tsdb

* fix(ci): remove --parseGoList=false from swag init to fix dependency resolution

* fix(ci): fix swag init directory and temporary remove s390x support due to cgo issues

* fix(ci): fix swag init output directory to cmd/dashboard/docs

* fix(ci): set GOTOOLCHAIN=auto for gosec

* feat: add system storage maintenance for SQLite and TSDB

* shit

* feat: add s390x support and improve service monitoring

* ci: upgrade goreleaser-cross image to v1.25

* ci: add libzstd-dev:s390x for cross-compilation

* ci: build libzstd for s390x from source

* ci: add libzstd_linux_s390x.go for gozstd linking

* ci: use vendor mode for s390x gozstd build

* ci: clone zstd source for s390x build

* refactor(tsdb): rename MaxDiskUsageGB to MinFreeDiskSpaceGB and optimize queries

- Rename config to accurately reflect VictoriaMetrics behavior: minimum free disk space threshold
- Add QueryServiceHistoryByServerID for batch query optimization
- Fix hasStatus to avoid false status counting when only delay data exists
- Fix service aggregation boundary: use successCount*2 >= count
- Fix serviceID parsing with strconv.ParseUint error handling
- Add TagFiltersCacheSize for better query performance

* feat(api): add server metrics endpoint and simplify service history response

- Add /server/:id/metrics API for querying TSDB server metrics
- Simplify getServiceHistory by removing redundant data conversion
- Change AvgDelay type from float32 to float64
- Remove generated swagger docs (to be regenerated)
- Update TSDB query, writer and tests

* chore: 临时禁用不支持前端

* ci: cache zstd build for s390x to speed up CI

* fix(tsdb): fix race conditions, data correctness and optimize performance

- Fix TOCTOU race between IsClosed() and write/query by holding RLock
- Fix delay=0 excluded from stats by using hasDelay flag instead of value > 0
- Fix fmt.Sscanf -> strconv.ParseUint for server_id parsing with error logging
- Fix buffer unbounded growth by flushing inside lock when over maxSize
- Split makeMetricRow into makeServerMetricRow/makeServiceMetricRow
- Extract InitGlobalSettings() from Open() for VictoriaMetrics globals
- Remove redundant instance/GetInstance/SetInstance singleton
- Add error logging for silently skipped block decode errors
- Optimize WriteBatch* to build all rows in single write call
- Optimize downsample to use linear scan instead of map for sorted data
- Optimize query slice reuse across block iterations

* 服务添加DisplayIndex (#1166)

* 服务添加DisplayIndex

* 根据ai建议修改

---------

Co-authored-by: huYang <306061454@qq.com>

* fix(tsdb): restore SQLite fallback and monthly status reload on restart

- Restore ServiceHistory model and SQLite write fallback when TSDB is disabled
- Reload monthlyStatus (30-day) and serviceStatusToday from TSDB/SQLite on startup
- Add SQLite fallback query for /service/:id/history and /server/:id/service
- Remove breaking GET /service/:id endpoint, keep /service/:id/history only
- Add QueryServiceDailyStats to TSDB for per-day aggregation
- Add tests for monthly status and today stats loading from both TSDB and SQLite
- Migrate ServiceHistory table only when TSDB is disabled

* ci: exclude false-positive gosec rules G117, G703, G704

* feat(api): expose tsdb_enabled in setting response

* ci: restore G115 exclusion accidentally dropped in previous commit

* fix: update version numbers for OfficialAdmin and Official templates

* chore: upgrade frontend

* chore: upgrade frontend

---------

Co-authored-by: 胡说丷刂 <34758853+laosan-xx@users.noreply.github.com>
Co-authored-by: huYang <306061454@qq.com>
2026-02-15 13:13:33 +08:00
UUBulb c22446b20c feat(notification): add option to convert metric units in request body (#1156)
* feat(notification): add option to convert metric units in request body

* ignore gosec

* rename fields
2026-01-10 17:29:10 +08:00
UUBulb dc59135e5c fix: add a default error message for waf page (#1145) 2025-12-04 21:52:12 +08:00
UUBulb e0133a015c fix: use server ids in db query (#1146) 2025-12-04 21:51:33 +08:00
naiba 543d2642f3 improve: not block ip when ip changed 2025-10-10 09:53:15 +08:00
naiba fbfe28a54e chore: back comp 2025-10-09 22:01:35 +08:00
naiba 3b8d2f3736 fix: oauth2 login 2025-10-09 22:00:08 +08:00
naiba 3b2cefdd07 feat: binding ip with session
🛡️staying safe even your frontend was hacked
2025-10-09 21:36:59 +08:00
UUBulb 010ae7ef51 fix: member-created services shouldn't be applied to admin resources (#1113) 2025-08-28 22:37:44 +08:00
naiba 233bbc23b6 feat: 批量转移服务器给其他用户 2025-06-16 23:46:36 +08:00
UUBulb 04d7651f58 update dependencies (#1077)
* update dependencies

ddns: update libdns to 1.0.0

* rename var
2025-05-02 00:25:23 +08:00
TomyJan 42c128995b feat: separate real ip header of frontend/agent (#1057)
* test(deps): use self admin-frontend

* feat: separate real ip header of frontend/agent

* test(ci): test

* Revert "test(ci): test"

This reverts commit 1634c7e7d7bca2873c13955abb582ea88645844f.

* test(ci): test

* test(ci): test

* test(ci): test

* Revert "test(ci): test"

This reverts commit 8fce20a07e5a9503f665937337050d4373ca7e78.

* Revert "test(ci): test"

This reverts commit 3267cccbfb81776a711e8cb34e676536f0852d1c.

* Revert "test(ci): test"

This reverts commit 566660c0c6a9875864aa46537bcc6788769337eb.

* Revert "test(deps): use self admin-frontend"

This reverts commit 16a838b374ff040800b2c1c5f8e5ede577645669.
2025-04-13 18:41:28 +08:00
UUBulb 227a6a0cb3 fix search by id (#1047)
* fix search by id

* make SearchByID Public
2025-03-27 19:17:00 +08:00
UUBulb f34b263b69 improve transfer record logic (#1033)
* improve transfer record logic

* refactor

* modernize loops

* remove unused type conversions

* update dependencies

* script: keep .gitkeep files

* fix

* remove clear
2025-03-19 22:21:21 +08:00
UUBulb ea3739cf1c ddns: support provider hurricane electric (#1036) 2025-03-19 21:52:16 +08:00
UUBulb 49c68f0da4 ddns: retreive dns servers from context (#1034) 2025-03-17 23:11:40 +08:00
naiba 235ee05c91 feat: upgrade frontend 2025-03-15 22:31:16 +08:00
UUBulb 4ce74d9fd8 hide install_host for guest (#1029) 2025-03-15 18:01:23 +08:00