diff --git a/cli.go b/cli.go index f8b96c4..72f0958 100644 --- a/cli.go +++ b/cli.go @@ -438,7 +438,8 @@ var signerSignCmd = &cobra.Command{ return fmt.Errorf("reading firmware file: %w", err) } - signature := ed25519.Sign(ed25519.PrivateKey(privateKey), fileData) + fileHash := sha256.Sum256(fileData) + signature := ed25519.Sign(ed25519.PrivateKey(privateKey), fileHash[:]) sigPath := filePath + ".sig" if err := os.WriteFile(sigPath, signature, 0644); err != nil { @@ -512,13 +513,13 @@ var signerVerifyCmd = &cobra.Command{ return fmt.Errorf("invalid signature size: got %d bytes, expected %d", len(sigBytes), ed25519.SignatureSize) } - if !ed25519.Verify(publicKey, fileData, sigBytes) { + fileHash := sha256.Sum256(fileData) + if !ed25519.Verify(publicKey, fileHash[:], sigBytes) { return fmt.Errorf("VERIFICATION FAILED: signature is invalid") } - hash := sha256.Sum256(fileData) fmt.Fprintf(os.Stderr, "VERIFICATION OK: signature is valid\n") - fmt.Fprintf(os.Stderr, "SHA256: %s\n", hex.EncodeToString(hash[:])) + fmt.Fprintf(os.Stderr, "SHA256: %s\n", hex.EncodeToString(fileHash[:])) return nil }, } diff --git a/jsonrpc.go b/jsonrpc.go index 23ef0f4..1c4762a 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -535,6 +535,14 @@ func rpcTryUpdate() error { return nil } +func rpcUpdateSignatures() (*SignatureUpdateResult, error) { + result, err := UpdateSignatures(context.Background()) + if err != nil { + logger.Warn().Err(err).Msg("failed to update signatures") + } + return result, err +} + func rpcGetCustomUpdateBaseURL() (string, error) { return customUpdateBaseURL, nil } @@ -1657,6 +1665,7 @@ var rpcHandlers = map[string]RPCHandler{ "getUpdateStatus": {Func: rpcGetUpdateStatus}, "getSelfSignatureStatus": {Func: rpcGetSelfSignatureStatus}, "tryUpdate": {Func: rpcTryUpdate}, + "updateSignatures": {Func: rpcUpdateSignatures}, "getCustomUpdateBaseURL": {Func: rpcGetCustomUpdateBaseURL}, "setCustomUpdateBaseURL": {Func: rpcSetCustomUpdateBaseURL, Params: []string{"baseURL"}}, "getUpdateDownloadProxy": {Func: rpcGetUpdateDownloadProxy}, diff --git a/network.go b/network.go index 47dc0ae..f68c93d 100644 --- a/network.go +++ b/network.go @@ -167,3 +167,16 @@ func rpcRenewDHCPLease() error { func rpcRequestDHCPAddress(ip string) error { return networkState.RpcRequestDHCPAddress(ip) } + +const ethernetMacAddressPath = "/userdata/ethaddr.txt" + +func rpcSetEthernetMacAddress(macAddress string) (interface{}, error) { + normalized, err := networkState.SetMACAddress(macAddress) + if err != nil { + return nil, err + } + if err := os.WriteFile(ethernetMacAddressPath, []byte(normalized+"\n"), 0644); err != nil { + return nil, fmt.Errorf("failed to write %s: %w", ethernetMacAddressPath, err) + } + return networkState.RpcGetNetworkState(), nil +} diff --git a/network_mac.go b/network_mac.go deleted file mode 100644 index 09750e0..0000000 --- a/network_mac.go +++ /dev/null @@ -1,20 +0,0 @@ -package kvm - -import ( - "fmt" - "os" -) - -const ethernetMacAddressPath = "/userdata/ethaddr.txt" - -func rpcSetEthernetMacAddress(macAddress string) (interface{}, error) { - normalized, err := networkState.SetMACAddress(macAddress) - if err != nil { - return nil, err - } - if err := os.WriteFile(ethernetMacAddressPath, []byte(normalized+"\n"), 0644); err != nil { - return nil, fmt.Errorf("failed to write %s: %w", ethernetMacAddressPath, err) - } - return networkState.RpcGetNetworkState(), nil -} - diff --git a/ota.go b/ota.go index 0b9b65e..e6480a0 100644 --- a/ota.go +++ b/ota.go @@ -1296,6 +1296,7 @@ func cleanupUpdateTempFiles(logger *zerolog.Logger) { "/userdata/picokvm/bin/kvm_app.sig.unverified", "/userdata/picokvm/update_system.zip.unverified", "/userdata/picokvm/update_system.zip.sig.unverified", + "/userdata/picokvm/update_system.zip", "/userdata/picokvm/update_system.tar.unverified", "/userdata/picokvm/update_system.tar.sig.unverified", "/userdata/picokvm/update_system.tar", @@ -1543,6 +1544,7 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err } if rebootNeeded { + cleanupUpdateTempFiles(&scopedLogger) scopedLogger.Info().Msg("System Rebooting in 10s") time.Sleep(10 * time.Second) cmd := exec.Command("reboot") @@ -1671,7 +1673,8 @@ func verifyFileSignature( return true, fmt.Errorf("error reading file for signature verification: %w", err) } - if !ed25519.Verify(publicKey, fileBytes, sigBytes) { + fileHash := sha256.Sum256(fileBytes) + if !ed25519.Verify(publicKey, fileHash[:], sigBytes) { return true, fmt.Errorf("Ed25519 signature verification failed for %s", unverifiedPath) } @@ -1696,5 +1699,53 @@ func verifyLocalFileSignature(filePath string, sigPath string, publicKey ed25519 if err != nil { return false } - return ed25519.Verify(publicKey, fileBytes, sigBytes) + fileHash := sha256.Sum256(fileBytes) + return ed25519.Verify(publicKey, fileHash[:], sigBytes) +} + +type SignatureUpdateResult struct { + AppSignatureUpdated bool `json:"appSignatureUpdated"` + SystemSignatureUpdated bool `json:"systemSignatureUpdated"` + AppSignatureValid bool `json:"appSignatureValid"` + SystemSignatureValid bool `json:"systemSignatureValid"` + Error string `json:"error,omitempty"` +} + +func UpdateSignatures(ctx context.Context) (*SignatureUpdateResult, error) { + result := &SignatureUpdateResult{} + + remoteMetadata, err := fetchUpdateMetadata(ctx, "", false) + if err != nil { + result.Error = fmt.Sprintf("failed to fetch remote metadata: %v", err) + return result, fmt.Errorf("failed to fetch remote metadata: %w", err) + } + + publicKey := getOTAPublicKey() + + appBinPath := "/userdata/picokvm/bin/kvm_app" + appSigPath := appBinPath + ".sig" + + if strings.TrimSpace(remoteMetadata.AppSigUrl) != "" { + err := downloadFile(ctx, appSigPath, remoteMetadata.AppSigUrl, nil, nil) + if err != nil { + result.Error = fmt.Sprintf("failed to download app signature: %v", err) + return result, fmt.Errorf("failed to download app signature: %w", err) + } + result.AppSignatureUpdated = true + + sigUnverified := appSigPath + ".unverified" + if _, statErr := os.Stat(sigUnverified); statErr == nil { + _ = os.Remove(appSigPath) + if renameErr := os.Rename(sigUnverified, appSigPath); renameErr != nil { + result.Error = fmt.Sprintf("failed to rename app signature: %v", renameErr) + return result, fmt.Errorf("failed to rename app signature: %w", renameErr) + } + } + + if publicKey != nil { + result.AppSignatureValid = verifyLocalFileSignature(appBinPath, appSigPath, publicKey) + } + } + + return result, nil } diff --git a/ui/package-lock.json b/ui/package-lock.json index c5a75b1..5d874ef 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -509,12 +509,6 @@ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", "license": "MIT" }, - "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": { - "version": "0.9.0", - "resolved": "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT" - }, "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -540,12 +534,6 @@ "stylis": "4.2.0" } }, - "node_modules/@emotion/cache/node_modules/@emotion/memoize": { - "version": "0.9.0", - "resolved": "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT" - }, "node_modules/@emotion/cache/node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz", @@ -572,18 +560,18 @@ "license": "MIT" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", "license": "MIT", "dependencies": { - "@emotion/memoize": "^0.8.1" + "@emotion/memoize": "^0.9.0" } }, "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", "license": "MIT" }, "node_modules/@emotion/react": { @@ -629,12 +617,6 @@ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", "license": "MIT" }, - "node_modules/@emotion/serialize/node_modules/@emotion/memoize": { - "version": "0.9.0", - "resolved": "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT" - }, "node_modules/@emotion/serialize/node_modules/@emotion/unitless": { "version": "0.10.0", "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.10.0.tgz", @@ -1778,9 +1760,9 @@ } }, "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -3145,12 +3127,6 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/stylis": { - "version": "4.2.5", - "resolved": "https://registry.npmmirror.com/@types/stylis/-/stylis-4.2.5.tgz", - "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", - "license": "MIT" - }, "node_modules/@types/validator": { "version": "13.15.0", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.0.tgz", @@ -3323,9 +3299,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -3894,9 +3870,9 @@ } }, "node_modules/babel-plugin-macros/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "license": "ISC", "engines": { "node": ">= 6" @@ -3915,9 +3891,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -4260,9 +4236,9 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/cva": { @@ -5403,9 +5379,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "license": "ISC" }, "node_modules/focus-trap": { @@ -5872,9 +5848,9 @@ } }, "node_modules/immutable": { - "version": "3.8.2", - "resolved": "https://registry.npmmirror.com/immutable/-/immutable-3.8.2.tgz", - "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.3.tgz", + "integrity": "sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6743,9 +6719,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.castarray": { @@ -7420,9 +7396,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -7442,9 +7418,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "funding": [ { "type": "opencollective", @@ -7461,7 +7437,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -8821,12 +8797,6 @@ "node": ">= 0.4" } }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9158,20 +9128,15 @@ } }, "node_modules/styled-components": { - "version": "6.1.19", - "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-6.1.19.tgz", - "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.4.1.tgz", + "integrity": "sha512-ADu2dF53esUzzM4I0ewxhxFtsDd6v4V6dNkg3vG0iFKhnt06sJneTZnRvujAosZwW0XD58IKgGMQoqri4wHRqg==", "license": "MIT", "dependencies": { - "@emotion/is-prop-valid": "1.2.2", - "@emotion/unitless": "0.8.1", - "@types/stylis": "4.2.5", + "@emotion/is-prop-valid": "1.4.0", "css-to-react-native": "3.2.0", - "csstype": "3.1.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.2", - "tslib": "2.6.2" + "csstype": "3.2.3", + "stylis": "4.3.6" }, "engines": { "node": ">= 16" @@ -9181,56 +9146,23 @@ "url": "https://opencollective.com/styled-components" }, "peerDependencies": { + "css-to-react-native": ">= 3.2.0", "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" - } - }, - "node_modules/styled-components/node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", - "license": "MIT" - }, - "node_modules/styled-components/node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "react-dom": ">= 16.8.0", + "react-native": ">= 0.68.0" }, - "engines": { - "node": "^10 || ^12 || >=14" + "peerDependenciesMeta": { + "css-to-react-native": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } } }, - "node_modules/styled-components/node_modules/stylis": { - "version": "4.3.2", - "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", - "license": "MIT" - }, - "node_modules/styled-components/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "license": "0BSD" - }, "node_modules/stylis": { "version": "4.3.6", "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz", @@ -9301,9 +9233,9 @@ } }, "node_modules/tar": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", - "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", + "version": "7.5.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", + "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -9392,9 +9324,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -9743,9 +9675,9 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -9866,9 +9798,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -10031,9 +9963,9 @@ } }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", "license": "ISC", "optional": true, "peer": true, @@ -10042,6 +9974,9 @@ }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yocto-queue": { diff --git a/ui/src/components/PopoverButton.tsx b/ui/src/components/PopoverButton.tsx index e98a1ec..a188b06 100644 --- a/ui/src/components/PopoverButton.tsx +++ b/ui/src/components/PopoverButton.tsx @@ -11,6 +11,7 @@ interface PopoverButtonProps { align?: "left" | "right"; buttonClassName?: string; panelClassName?: string; + style?: React.CSSProperties; } const BottomPopoverButton: React.FC = ({ @@ -18,6 +19,7 @@ const BottomPopoverButton: React.FC = ({ buttonIconNode, panelContent, align = "left", + style, }) => { const setDisableFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); return ( @@ -28,7 +30,7 @@ const BottomPopoverButton: React.FC = ({ <> + style={{ display: "flex", justifyContent: "center", alignItems: "center", ...style }}>
{ setDisableFocusTrap(true); diff --git a/ui/src/layout/components_setting/network/NetworkContent.tsx b/ui/src/layout/components_setting/network/NetworkContent.tsx index a89eb3c..3d08851 100644 --- a/ui/src/layout/components_setting/network/NetworkContent.tsx +++ b/ui/src/layout/components_setting/network/NetworkContent.tsx @@ -546,7 +546,7 @@ export default function SettingsNetwork() {
@@ -559,7 +559,7 @@ export default function SettingsNetwork() { /> @@ -572,7 +572,7 @@ export default function SettingsNetwork() { /> diff --git a/ui/src/layout/components_setting/version/VersionContent.tsx b/ui/src/layout/components_setting/version/VersionContent.tsx index 9e567c1..cdd10cc 100644 --- a/ui/src/layout/components_setting/version/VersionContent.tsx +++ b/ui/src/layout/components_setting/version/VersionContent.tsx @@ -750,10 +750,43 @@ function SystemUpToDateState({ versionInfo: SystemVersionInfo | null; }) { const { $at } = useReactAt(); + const [send] = useJsonRpc(); + const [sigUpdateLoading, setSigUpdateLoading] = useState(false); + const [sigUpdateResult, setSigUpdateResult] = useState(null); const hasAbsentSig = versionInfo?.appSignatureAbsent; const hasInvalidSig = versionInfo?.appSignatureInvalid; const hasNoPublicKey = versionInfo?.appNoPublicKey; + const handleUpdateSignatures = useCallback(() => { + setSigUpdateLoading(true); + setSigUpdateResult(null); + send("updateSignatures", {}, resp => { + setSigUpdateLoading(false); + if ("error" in resp) { + setSigUpdateResult(`Failed: ${resp.error.data || "Unknown error"}`); + notifications.error(`Signature update failed: ${resp.error.data || "Unknown error"}`); + } else { + const result = resp.result as { + appSignatureUpdated: boolean; + systemSignatureUpdated: boolean; + appSignatureValid: boolean; + systemSignatureValid: boolean; + error?: string; + }; + if (result.error) { + setSigUpdateResult(`Failed: ${result.error}`); + notifications.error(`Signature update failed: ${result.error}`); + } else { + const parts: string[] = []; + if (result.appSignatureUpdated) parts.push("App signature updated"); + if (result.systemSignatureUpdated) parts.push("System signature updated"); + setSigUpdateResult(parts.join(", ") || "No signatures to update"); + notifications.success("Signatures updated successfully"); + } + } + }); + }, [send]); + return (
@@ -797,17 +830,6 @@ function SystemUpToDateState({
)} - {versionInfo?.signatureVerified && ( -
-

- {$at("Signature Verified")} -

-

- {$at("Firmware signature has been verified and is valid.")} -

-
- )} -
{$at("Check Again")} @@ -816,6 +838,32 @@ function SystemUpToDateState({ {$at("Back")}
+ +

+ {$at("Update Signatures")} +

+

+ {$at("Update the signature of kvm_app to the latest version. If the current version is not up to date, signature verification will fail.")} +

+ + {sigUpdateResult && ( +
+

+ {$at("Signature Update Result")} +

+

+ {sigUpdateResult} +

+
+ )} + +
+
+ + {$at("Update")} + +
+
); @@ -839,6 +887,40 @@ function UpdateAvailableState({ onSaveUpdateDownloadProxy: () => void; }) { const { $at } = useReactAt(); + const [send] = useJsonRpc(); + const [sigUpdateLoading, setSigUpdateLoading] = useState(false); + const [sigUpdateResult, setSigUpdateResult] = useState(null); + + const handleUpdateSignatures = useCallback(() => { + setSigUpdateLoading(true); + setSigUpdateResult(null); + send("updateSignatures", {}, resp => { + setSigUpdateLoading(false); + if ("error" in resp) { + setSigUpdateResult(`Failed: ${resp.error.data || "Unknown error"}`); + notifications.error(`Signature update failed: ${resp.error.data || "Unknown error"}`); + } else { + const result = resp.result as { + appSignatureUpdated: boolean; + systemSignatureUpdated: boolean; + appSignatureValid: boolean; + systemSignatureValid: boolean; + error?: string; + }; + if (result.error) { + setSigUpdateResult(`Failed: ${result.error}`); + notifications.error(`Signature update failed: ${result.error}`); + } else { + const parts: string[] = []; + if (result.appSignatureUpdated) parts.push("App signature updated"); + if (result.systemSignatureUpdated) parts.push("System signature updated"); + setSigUpdateResult(parts.join(", ") || "No signatures to update"); + notifications.success("Signatures updated successfully"); + } + } + }); + }, [send]); + return (
@@ -888,6 +970,32 @@ function UpdateAvailableState({
+ +

+ {$at("Update Signatures")} +

+

+ {$at("Update the signature of kvm_app to the latest version. If the current version is not up to date, signature verification will fail.")} +

+ + {sigUpdateResult && ( +
+

+ {$at("Signature Update Result")} +

+

+ {sigUpdateResult} +

+
+ )} + +
+
+ + {$at("Update")} + +
+
diff --git a/ui/src/layout/components_side/Clipboard/Clipboard.tsx b/ui/src/layout/components_side/Clipboard/Clipboard.tsx index 3df8237..f2b6e0b 100644 --- a/ui/src/layout/components_side/Clipboard/Clipboard.tsx +++ b/ui/src/layout/components_side/Clipboard/Clipboard.tsx @@ -379,7 +379,7 @@ export default function Clipboard() { className={`${isMobile ? "w-full" : ""}`} onClick={handleOpenOcr} > - {$at("Open OCR")} + {$at("Open")} diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json index 2b66a2f..2a4aa7f 100644 --- a/ui/src/locales/en.json +++ b/ui/src/locales/en.json @@ -104,6 +104,14 @@ "f9099fc033": "Renew DHCP Lease", "6e188f5984": "IPv6 Information", "cfdffa4fc7": "Link-local", + "a588b1abc5": "Copied", + "c5505be6c3": "No text detected", + "a29aaf6603": "OCR failed", + "dc67ba5f40": "Drag to select text area", + "b603b043e8": "Copy text", + "f5f78c501b": "Recognizing text...", + "5df17e67f5": "Review the recognized text before copying.", + "4dfb4a614f": "Please wait while OCR is running.", "d6e24388b4": "Continue Uploading", "62a5e49088": "Hide", "b34e855501": "USB Expansion Function", @@ -173,6 +181,24 @@ "c40a72ad93": "KVM Terminal", "dd110018d2": "Serial Console", "7b277018e4": "Not connected", + "02d2f33ec9": "Reinitialize USB Gadget", + "c8e875e1c9": "Version Manager", + "63a6a88c06": "Refresh", + "d15f6e3312": "Hide Install Actions", + "2b9b84a087": "Show Install Actions", + "81dfff9d6f": "Install Status", + "98dd43dfae": "Installed", + "ddd8eef6f8": "Not Installed", + "e2dc83997b": "Detected Version", + "e3cc7e7df9": "System Architecture", + "bc56777902": "Release Version", + "c319e3982f": "Release Asset", + "06933067aa": "Update", + "a27dfe7717": "Uninstall", + "349838fb1d": "Install", + "4a0764788d": "Install Task", + "46b5f8c58b": "Progress", + "5e4caae72b": "Installed Versions", "bf733d8a93": "Access", "70e23e7d6f": "Manage the Access Control of the device", "509820290d": "Local", @@ -195,6 +221,8 @@ "efd3cc702e": "Enable Password", "8f1e77e0d2": "Change Password", "3d0de21428": "Update your device access password", + "d6d56d5972": "WebRTC Servers", + "8bea04c48e": "STUN and TURN servers used for peer connections", "f8508f576c": "Remote", "a8bb6f5f9f": "Manage the mode of Remote access to the device", "0b86461350": "TailScale use xEdge server", @@ -267,7 +295,6 @@ "324118a672": "Input", "29c2c02a36": "Output", "67d2f6740a": "Forward", - "63a6a88c06": "Refresh", "a4d3b161ce": "Submit", "ec211f7c20": "Add", "5320550175": "Chain", @@ -288,6 +315,22 @@ "64ae5dd047": "Destination Port", "fbb3878eb7": "Submit Firewall Policies?", "1c17a7e15b": "Warning: Adjusting some policies may cause network address loss, leading to device unavailability.", + "6f20995c95": "Failed to load WebRTC servers", + "aee9784c03": "Unknown error", + "6ef7ce5b80": "Failed to save STUN", + "4cd48aed7f": "STUN server saved", + "4b5d050e51": "Failed to save TURN", + "f6f10b4517": "TURN servers saved", + "d235995f96": "STUN Server", + "fc1bd2c935": "Public STUN server for NAT traversal", + "289929755b": "Restore Default", + "0268827609": "TURN Servers", + "b3c14a0273": "Used as relay when direct peer-to-peer connection fails", + "bbc48fb751": "No TURN servers configured", + "f6039d44b2": "Username", + "03bc142e64": "Credential", + "ca3e8baee9": "Add TURN Server", + "0acde1b3e3": "Save TURN Servers", "867cee98fd": "Passwords do not match", "9864ff9420": "Please enter your old password", "14a714ab22": "Please enter a new password", @@ -328,6 +371,12 @@ "3369c97f56": "Enter your SSH public key", "81bafb2833": "The default SSH user is ", "7a941a0f87": "Update SSH Key", + "d876ff8da6": "API Key", + "96164f17cf": "API key for MCP and REST API authentication", + "538ce1893b": "Enter API key or leave empty to auto-generate", + "a55c6d580b": "Used for authenticating MCP and REST API requests.", + "e85c388332": "Save API Key", + "927c25953b": "Generate New", "fdad65b7be": "Force HTTP Transmission", "e033b5e2a3": "Force using HTTP for video streaming instead of WebRTC", "bda22ca687": "USB detection enhancement", @@ -338,7 +387,6 @@ "020b92cfbb": "Enable USB Emulation", "9f55f64b0f": "USB Gadget Reinitialize", "40dc677a89": "Reinitialize USB gadget configuration", - "02d2f33ec9": "Reinitialize USB Gadget", "f5ddf02991": "Reboot System", "1dbbf194af": "Restart the device system", "1de72c4fc6": "Reboot", @@ -347,6 +395,10 @@ "f43c0398a4": "Reset Configuration", "0031dbef48": "Reset configuration to default. This will log you out.Some configuration changes will take effect after restart system.", "0d784092e8": "Reset Config", + "115082e888": "Clear API Key?", + "78fcaed30d": "Setting the API key to empty will auto-generate a new random key.", + "81e8b4cd6b": "Make sure to update your clients with the new key after saving.", + "211730be68": "Generate New Key", "a776e925bf": "Reboot System?", "1f070051ff": "Are you sure you want to reboot the system?", "f1a79f466e": "The device will restart and you will be disconnected from the web interface.", @@ -403,6 +455,12 @@ "ef64a3770e": "Control mDNS (multicast DNS) operational mode", "be8226fe0c": "Time synchronization", "7e06bd28a6": "Configure time synchronization settings", + "a2323452ba": "HTTP Proxy", + "7fedd1ea53": "Configure program HTTP proxy (optional)", + "a30d487cba": "HTTPS Proxy", + "3f4c7e23cb": "Configure program HTTPS proxy (optional)", + "48d941841f": "ALL Proxy", + "3a2cd1e4a7": "Configure program ALL proxy (optional)", "d4dccb8ca2": "Save settings", "8750a898cb": "IPv4 Mode", "72c2543791": "Configure IPv4 mode", @@ -444,7 +502,16 @@ "5b404b3c98": "Update in Background", "3723a3f846": "System is up to date", "a6b8796d51": "Your system is running the latest version. No updates are currently available.", + "a8d2a89696": "Missing Signature File", + "480f05b41b": "The current firmware is missing signature files. Integrity cannot be fully verified.", + "323ebc9620": "Signature Verification Failed", + "21355c9ecb": "The signature file exists but does not match the firmware. This may indicate tampering.", + "0b54a6c322": "No Embedded Public Key", + "9beb932d5a": "This build does not have an OTA public key embedded. Signature verification is unavailable.", "7c81553358": "Check Again", + "3b5f9a1f01": "Update Signatures", + "d25bfbb978": "Update the signature of kvm_app to the latest version. If the current version is not up to date, signature verification will fail.", + "1bdd158a19": "Signature Update Result", "217b416896": "Update available", "f00af8c98f": "A new update is available to enhance system performance and improve compatibility. We recommend updating to ensure everything runs smoothly.", "8977c3f0b7": "Download Proxy Prefix", @@ -453,11 +520,22 @@ "cffae9918d": "Update Error", "49cba7cadf": "An error occurred while updating your device. Please try again later.", "d849d5b330": "Error details:", - "2b2f7a6d7c": "Use Ctrl+V to paste clipboard to remote", + "fcfc8da1a3": "Verifying signature...", + "4e16083ef8": "Please wait while verifying firmware signature.", + "5367acff78": "Signature Status Unavailable", + "634aac26af": "Unable to retrieve signature verification status.", + "77de342f39": "Signature Verified", + "20ac4a17cc": "Firmware signature has been verified and is valid.", + "36a41c937c": "No video signal", + "d34da01de2": "Enable paste shortcut", "2b1a1676d1": "Copy text from your client to the remote host", "604c45fbf2": "The following characters will not be pasted:", "a7eb9efa0b": "Sending text using keyboard layout:", "2593d0a3d5": "Confirm paste", + "ac7e2a9783": "Enable OCR shortcut", + "f529c51ee6": "OCR", + "607440855c": "Open OCR selection mode on the video area", + "c3bf447eab": "Open", "a0e3947a02": "Macros", "276043dbf0": "Create a new keyboard macro", "9605ef9593": "Modify your keyboard macro", @@ -487,6 +565,7 @@ "21d104a54f": "Processing...", "9844086d90": "No SD Card Detected", "b56e598918": "SD Card Mount Failed", + "d646589704": "Choose the file system for MicroSD formatting", "16eb8ed6c8": "Format MicroSD Card", "a63d5e0260": "Manage Shared Folders in KVM MicroSD Card", "1d50425f88": "Manage Shared Folders in KVM Storage", @@ -498,10 +577,15 @@ "8bf8854beb": "of", "53e61336bb": "results", "d1f5a81904": "Unmount MicroSD Card", - "827048afc2": "Formatting the SD card will erase all data. Continue?", "5874cf46ff": "SD card formatted successfully", "7dc25305d4": "Encodec Type", "41d263a988": "Stream Quality", + "d8d7a5377b": "RC Control", + "a09ff5b350": "Adjust rate control QP settings for better balance between quality and bitrate", + "b1f9d95e72": "StepQp", + "bf1af7559b": "MinQp", + "6e4e284fcc": "MinIQp", + "df1b8f72f2": "DetlpQp", "41f5283941": "NPU Application", "466990072d": "Enable NPU to Object Detection", "a6c2e30b8e": "Video Enhancement", @@ -518,6 +602,8 @@ "85a003df9b": "EDID File", "5c7a666766": "Set Custom EDID", "7dad0ba758": "Restore to default", + "7b2fb72a68": "RC Advanced Config", + "827048afc2": "Formatting the SD card will erase all data. Continue?", "556c7553f1": "KVM MicroSD Card Mount", "b99cf1ecb7": "Manage and mount images from MicroSD card", "0a01e80566": "Mounted from KVM storage", @@ -571,23 +657,5 @@ "65f1314580": "Confirm your password", "af21497286": "Set Password", "c3f88872d6": "This password will be used to secure your device data and protect against unauthorized access.", - "06a7b3bf6e": "All data remains on your local device.", - "d6d56d5972": "WebRTC Servers", - "8bea04c48e": "STUN and TURN servers used for peer connections", - "4cd48aed7f": "STUN server saved", - "f6f10b4517": "TURN servers saved", - "d235995f96": "STUN Server", - "fc1bd2c935": "Public STUN server for NAT traversal", - "289929755b": "Restore Default", - "0268827609": "TURN Servers", - "b3c14a0273": "Used as relay when direct peer-to-peer connection fails", - "bbc48fb751": "No TURN servers configured", - "f6039d44b2": "Username", - "03bc142e64": "Credential", - "ca3e8baee9": "Add TURN Server", - "0acde1b3e3": "Save TURN Servers", - "6f20995c95": "Failed to load WebRTC servers", - "6ef7ce5b80": "Failed to save STUN", - "4b5d050e51": "Failed to save TURN", - "aee9784c03": "Unknown error" -} + "06a7b3bf6e": "All data remains on your local device." +} \ No newline at end of file diff --git a/ui/src/locales/zh.json b/ui/src/locales/zh.json index 766846b..ac4462e 100644 --- a/ui/src/locales/zh.json +++ b/ui/src/locales/zh.json @@ -104,6 +104,14 @@ "f9099fc033": "重订 DHCP 租约", "6e188f5984": "IPv6 信息", "cfdffa4fc7": "本地链路", + "a588b1abc5": "已复制", + "c5505be6c3": "未检测到文本", + "a29aaf6603": "OCR 失败", + "dc67ba5f40": "拖动选择文本区域", + "b603b043e8": "复制文本", + "f5f78c501b": "正在识别文本...", + "5df17e67f5": "复制前请检查识别出的文本。", + "4dfb4a614f": "请等待 OCR 运行完成。", "d6e24388b4": "继续上传", "62a5e49088": "隐藏", "b34e855501": "USB拓展功能", @@ -173,6 +181,24 @@ "c40a72ad93": "KVM 终端", "dd110018d2": "串口控制台", "7b277018e4": "未连接", + "02d2f33ec9": "重新初始化 USB 设备", + "c8e875e1c9": "版本管理器", + "63a6a88c06": "刷新", + "d15f6e3312": "隐藏安装操作", + "2b9b84a087": "显示安装操作", + "81dfff9d6f": "安装状态", + "98dd43dfae": "已安装", + "ddd8eef6f8": "未安装", + "e2dc83997b": "检测到的版本", + "e3cc7e7df9": "系统架构", + "bc56777902": "发布版本", + "c319e3982f": "发布资源", + "06933067aa": "更新", + "a27dfe7717": "卸载", + "349838fb1d": "安装", + "4a0764788d": "安装任务", + "46b5f8c58b": "进度", + "5e4caae72b": "已安装版本", "bf733d8a93": "访问", "70e23e7d6f": "管理设备的访问控制", "509820290d": "本地", @@ -195,6 +221,8 @@ "efd3cc702e": "启用密码", "8f1e77e0d2": "更改密码", "3d0de21428": "更新设备访问密码", + "d6d56d5972": "WebRTC 服务器", + "8bea04c48e": "用于点对点连接的 STUN 和 TURN 服务器", "f8508f576c": "远程", "a8bb6f5f9f": "管理远程访问设备的方式", "0b86461350": "TailScale 使用 xEdge 服务器", @@ -267,7 +295,6 @@ "324118a672": "输入", "29c2c02a36": "输出", "67d2f6740a": "转发", - "63a6a88c06": "刷新", "a4d3b161ce": "提交", "ec211f7c20": "添加", "5320550175": "链", @@ -288,6 +315,22 @@ "64ae5dd047": "目标端口", "fbb3878eb7": "提交防火墙策略?", "1c17a7e15b": "警告:调整某些策略可能会导致网络地址丢失,导致设备不可用。", + "6f20995c95": "加载 WebRTC 服务器失败", + "aee9784c03": "未知错误", + "6ef7ce5b80": "保存 STUN 失败", + "4cd48aed7f": "STUN 服务器已保存", + "4b5d050e51": "保存 TURN 失败", + "f6f10b4517": "TURN 服务器已保存", + "d235995f96": "STUN 服务器", + "fc1bd2c935": "用于 NAT 穿透的公共 STUN 服务器", + "289929755b": "恢复默认", + "0268827609": "TURN 服务器", + "b3c14a0273": "当直接点对点连接失败时用作中继", + "bbc48fb751": "未配置 TURN 服务器", + "f6039d44b2": "用户名", + "03bc142e64": "凭据", + "ca3e8baee9": "添加 TURN 服务器", + "0acde1b3e3": "保存 TURN 服务器", "867cee98fd": "密码不一致", "9864ff9420": "请输入旧密码", "14a714ab22": "请输入新密码", @@ -328,6 +371,12 @@ "3369c97f56": "输入您的 SSH 公钥", "81bafb2833": "默认 SSH 用户是 ", "7a941a0f87": "更新 SSH 密钥", + "d876ff8da6": "API 密钥", + "96164f17cf": "用于 MCP 和 REST API 认证的 API 密钥", + "538ce1893b": "输入 API 密钥或留空以自动生成", + "a55c6d580b": "用于认证 MCP 和 REST API 请求。", + "e85c388332": "保存 API 密钥", + "927c25953b": "生成新密钥", "fdad65b7be": "强制 HTTP 传输", "e033b5e2a3": "强制使用 HTTP 传输数据替代 WebRTC", "bda22ca687": "USB 检测增强", @@ -338,7 +387,6 @@ "020b92cfbb": "启用 USB 复用", "9f55f64b0f": "USB 设备重新初始化", "40dc677a89": "重新初始化 USB 设备配置", - "02d2f33ec9": "重新初始化 USB 设备", "f5ddf02991": "重启系统", "1dbbf194af": "重启设备系统", "1de72c4fc6": "重启", @@ -347,6 +395,10 @@ "f43c0398a4": "重置配置", "0031dbef48": "重置配置,这将使你退出登录。部分配置重启后生效。", "0d784092e8": "重置配置", + "115082e888": "清除 API 密钥?", + "78fcaed30d": "将 API 密钥留空将自动生成一个新的随机密钥。", + "81e8b4cd6b": "请确保在保存后使用新密钥更新您的客户端。", + "211730be68": "生成新密钥", "a776e925bf": "重启系统?", "1f070051ff": "你确定重启系统吗?", "f1a79f466e": "设备将重启,你将从 Web 界面断开连接。", @@ -403,6 +455,12 @@ "ef64a3770e": "控制 mDNS(多播 DNS)运行模式", "be8226fe0c": "时间同步", "7e06bd28a6": "配置时间同步设置", + "a2323452ba": "HTTP 代理", + "7fedd1ea53": "配置程序 HTTP 代理(可选)", + "a30d487cba": "HTTPS 代理", + "3f4c7e23cb": "配置程序 HTTPS 代理(可选)", + "48d941841f": "ALL 代理", + "3a2cd1e4a7": "配置程序 ALL 代理(可选)", "d4dccb8ca2": "保存设置", "8750a898cb": "IPv4 模式", "72c2543791": "配置 IPv4 模式", @@ -444,7 +502,16 @@ "5b404b3c98": "后台更新", "3723a3f846": "系统已更新到最新版本", "a6b8796d51": "您的系统已运行最新版本。当前没有可用的更新。", + "a8d2a89696": "签名文件缺失", + "480f05b41b": "当前固件缺少签名文件,无法完全验证完整性。", + "323ebc9620": "签名验证失败", + "21355c9ecb": "签名文件存在但与固件不匹配,可能存在篡改。", + "0b54a6c322": "未嵌入公钥", + "9beb932d5a": "此版本未嵌入 OTA 公钥,无法进行签名验证。", "7c81553358": "再次检查", + "3b5f9a1f01": "更新签名", + "d25bfbb978": "更新 kvm_app 的签名到最新版本。如果当前版本不是最新版本,签名验证将会失败。", + "1bdd158a19": "签名更新结果", "217b416896": "更新可用", "f00af8c98f": "新的更新可用,以增强系统性能和提高兼容性。我们建议更新以确保一切正常运行。", "8977c3f0b7": "下载代理加速前缀", @@ -453,11 +520,22 @@ "cffae9918d": "更新错误", "49cba7cadf": "更新设备时发生错误。请稍后重试。", "d849d5b330": "错误详情:", - "2b2f7a6d7c": "按下 Ctrl+V 直接将本地剪贴板内容发送到远端主机", + "fcfc8da1a3": "正在验证签名...", + "4e16083ef8": "请等待固件签名验证完成。", + "5367acff78": "签名状态不可用", + "634aac26af": "无法获取签名验证状态。", + "77de342f39": "签名已验证", + "20ac4a17cc": "固件签名已验证且有效。", + "36a41c937c": "无视频信号", + "d34da01de2": "启用粘贴快捷键", "2b1a1676d1": "将文本从您的客户端复制到远程主机", "604c45fbf2": "以下字符将不会被粘贴:", "a7eb9efa0b": "使用键盘布局发送文本:", "2593d0a3d5": "确认粘贴", + "ac7e2a9783": "启用 OCR 快捷键", + "f529c51ee6": "OCR", + "607440855c": "在视频区域打开 OCR 选择模式", + "c3bf447eab": "打开", "a0e3947a02": "宏", "276043dbf0": "创建新的键盘宏", "9605ef9593": "修改键盘宏", @@ -487,6 +565,7 @@ "21d104a54f": "处理中...", "9844086d90": "未检测到 SD 卡", "b56e598918": "SD 卡挂载失败", + "d646589704": "选择 MicroSD 格式化的文件系统", "16eb8ed6c8": "格式化 MicroSD 卡", "a63d5e0260": "管理 KVM MicroSD Card 的共享文件夹", "1d50425f88": "管理 KVM 存储中的共享文件夹", @@ -498,10 +577,15 @@ "8bf8854beb": "共", "53e61336bb": "条结果", "d1f5a81904": "卸载 MicroSD 卡", - "827048afc2": "格式化 SD 卡会清除所有数据。是否继续?", "5874cf46ff": "SD 卡格式化成功", "7dc25305d4": "视频编码类型", "41d263a988": "视频质量", + "d8d7a5377b": "RC 控制", + "a09ff5b350": "调整码率控制 QP 参数,在质量和码率之间取得更好的平衡", + "b1f9d95e72": "StepQp", + "bf1af7559b": "MinQp", + "6e4e284fcc": "MinIQp", + "df1b8f72f2": "DetlpQp", "41f5283941": "NPU 应用", "466990072d": "启用 NPU 以进行物体检测", "a6c2e30b8e": "视频增强", @@ -518,6 +602,8 @@ "85a003df9b": "EDID 文件", "5c7a666766": "设置自定义 EDID", "7dad0ba758": "恢复默认", + "7b2fb72a68": "RC 高级配置", + "827048afc2": "格式化 SD 卡会清除所有数据。是否继续?", "556c7553f1": "从 KVM MicroSD 卡挂载", "b99cf1ecb7": "从 MicroSD 卡中管理和挂载镜像", "0a01e80566": "已从 KVM 存储挂载", @@ -571,23 +657,5 @@ "65f1314580": "确认您的密码", "af21497286": "设置密码", "c3f88872d6": "此密码将用于保护您的设备数据并防止未经授权的访问。", - "06a7b3bf6e": "所有数据保留在您的本地设备上。", - "d6d56d5972": "WebRTC 服务器", - "8bea04c48e": "用于点对点连接的 STUN 和 TURN 服务器", - "4cd48aed7f": "STUN 服务器已保存", - "f6f10b4517": "TURN 服务器已保存", - "d235995f96": "STUN 服务器", - "fc1bd2c935": "用于 NAT 穿透的公共 STUN 服务器", - "289929755b": "恢复默认", - "0268827609": "TURN 服务器", - "b3c14a0273": "当直接点对点连接失败时用作中继", - "bbc48fb751": "未配置 TURN 服务器", - "f6039d44b2": "用户名", - "03bc142e64": "凭据", - "ca3e8baee9": "添加 TURN 服务器", - "0acde1b3e3": "保存 TURN 服务器", - "6f20995c95": "加载 WebRTC 服务器失败", - "6ef7ce5b80": "保存 STUN 失败", - "4b5d050e51": "保存 TURN 失败", - "aee9784c03": "未知错误" -} + "06a7b3bf6e": "所有数据保留在您的本地设备上。" +} \ No newline at end of file