* fix: increase updateTunnel timeout to 120s Editing tunnel entry nodes triggers forward sync to all entry nodes. If nodes are offline or many forwards exist, the sync can exceed the default 30s timeout. Match the timeout used by other heavy operations like batchDeleteTunnels. * docs: add commercial white-label design spec * docs: add commercial white-label implementation plan * feat: add license activation endpoint and authorization check for commercial config keys * feat: add frontend api and update site config state for license * feat: conditionally hide flvx footer brand * feat: ui settings for commercial white-label and license activation * fix: add missing licenseActivateRequest and fix GetConfig in handler.go * docs: add keygen.sh license integration design spec * docs: add keygen.sh integration implementation plan * feat: add machine fingerprint generation * feat: add keygen.sh api client * feat: integrate keygen into license activation endpoint * feat: add periodic license validation job * fix: remove accidentally leaked dash kernel test codes that caused compilation failures * fix: correct keygen validation scope and binding logic * feat: hardcode Keygen.sh account ID * fix: relax strict validation matching after successful machine activation
7.1 KiB
Commercial White-Label Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Allow users with a valid license key to activate commercial white-label features, enabling them to remove FLVX branding and use their own app name, logos, and footer.
Architecture: Backend API handles license validation and stores state (is_commercial). Both frontend and backend check this state to conditionally render or allow modifications to brand config.
Tech Stack: Go (Backend API), React + Vite (Frontend UI).
Task 1: Backend License Activation Endpoint
Files:
-
Modify:
go-backend/internal/http/handler/handler.go -
Step 1: Add license request struct Add the
licenseActivateRequeststruct inhandler.go.
type licenseActivateRequest struct {
LicenseKey string `json:"license_key"`
}
- Step 2: Add
licenseActivatehandler method Add the method to validate the key inhandler.go.
func (h *Handler) licenseActivate(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
response.WriteJSON(w, response.ErrDefault("请求失败"))
return
}
var req licenseActivateRequest
if err := decodeJSON(r.Body, &req); err != nil {
response.WriteJSON(w, response.ErrDefault("授权码不能为空"))
return
}
key := strings.TrimSpace(req.LicenseKey)
if !strings.HasPrefix(key, "FLVX-") {
response.WriteJSON(w, response.ErrDefault("无效的商业授权码"))
return
}
now := time.Now().UnixMilli()
if err := h.repo.UpsertConfig("license_key", key, now); err != nil {
response.WriteJSON(w, response.Err(-2, err.Error()))
return
}
if err := h.repo.UpsertConfig("is_commercial", "true", now); err != nil {
response.WriteJSON(w, response.Err(-2, err.Error()))
return
}
response.WriteJSON(w, response.OKEmpty())
}
- Step 3: Register the route
In
handler.goinsideRegister(mux *http.ServeMux), add the route.
mux.HandleFunc("/api/v1/license/activate", h.licenseActivate)
- Step 4: Commit
git add go-backend/internal/http/handler/handler.go
git commit -m "feat: add license activation endpoint"
Task 2: Backend Config Update Validation
Files:
-
Modify:
go-backend/internal/http/handler/handler.go -
Step 1: Add permission check in
updateConfigsInupdateConfigs, fetchisCommercial := h.repo.GetConfig("is_commercial"). Inside the loop, check if the user is trying to update protected keys.
isCommercial, _ := h.repo.GetConfig("is_commercial")
protectedKeys := map[string]bool{
"app_name": true,
"app_logo": true,
"app_favicon": true,
"hide_footer_brand": true,
}
Inside for k, v := range payload:
if protectedKeys[key] && isCommercial.Value != "true" {
response.WriteJSON(w, response.ErrDefault("需要商业版授权"))
return
}
- Step 2: Add permission check in
updateSingleConfigInupdateSingleConfig, do the same check before callingnormalizeAndValidateConfigValue.
isCommercial, _ := h.repo.GetConfig("is_commercial")
if (name == "app_name" || name == "app_logo" || name == "app_favicon" || name == "hide_footer_brand") && isCommercial.Value != "true" {
response.WriteJSON(w, response.ErrDefault("需要商业版授权"))
return
}
- Step 3: Commit
git add go-backend/internal/http/handler/handler.go
git commit -m "feat: add authorization check for commercial config keys"
Task 3: Frontend API & Site Config Update
Files:
-
Modify:
vite-frontend/src/api/index.ts -
Modify:
vite-frontend/src/config/site.ts -
Step 1: Add
activateLicenseAPI Invite-frontend/src/api/index.ts:
export const activateLicense = (licenseKey: string) =>
Network.post("/license/activate", { license_key: licenseKey });
- Step 2: Update
siteConfigdefaults Invite-frontend/src/config/site.ts, insidegetInitialConfig(), add properties.
app_logo: cachedAppLogo,
app_favicon: cachedAppFavicon,
is_commercial: configCache.get("is_commercial") === "true",
hide_footer_brand: configCache.get("hide_footer_brand") === "true",
- Step 3: Update
updateSiteConfigInupdateSiteConfiginsidesite.ts, extract and updateis_commercialandhide_footer_brand.
const isCommercial = resolvedConfigMap.is_commercial === "true";
const hideFooterBrand = resolvedConfigMap.hide_footer_brand === "true";
siteConfig.is_commercial = isCommercial;
siteConfig.hide_footer_brand = hideFooterBrand;
- Step 4: Commit
git add vite-frontend/src/api/index.ts vite-frontend/src/config/site.ts
git commit -m "feat: add frontend api and update site config state for license"
Task 4: Frontend Footer Component Update
Files:
-
Modify:
vite-frontend/src/components/version-footer.tsx -
Step 1: Conditionally hide "Powered by FLVX" In the render block, wrap the
Powered by FLVXtext.
{siteConfig.hide_footer_brand !== true && (
<p className={poweredClassName}>
Powered by{" "}
<a
className="text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
href={siteConfig.github_repo}
rel="noopener noreferrer"
target="_blank"
>
FLVX
</a>
</p>
)}
- Step 2: Commit
git add vite-frontend/src/components/version-footer.tsx
git commit -m "feat: conditionally hide flvx footer brand"
Task 5: Frontend Settings Page UI Update
Files:
-
Modify:
vite-frontend/src/pages/config.tsx -
Step 1: Add config keys to initialization In
getInitialConfigs, add"is_commercial"and"hide_footer_brand"toconfigKeys. -
Step 2: Add
hide_footer_brandswitch field Add it to theCONFIG_ITEMSarray.
{
key: "hide_footer_brand",
label: "隐藏页面底部 FLVX 版权信息",
description: "需商业版授权才能生效",
type: "switch",
},
-
Step 3: Add license activation UI Above the System Config Card (near
value="configs"), add a newCardfor "商业版授权". You will need a local statelicenseKeyand anhandleActivateLicensefunction that callsactivateLicense(licenseKey)and refetches configs on success. -
Step 4: Disable brand settings when not commercial In
renderConfigItem, computeisDisabledand pass it to the<Input>,<Switch>, andBrandUploadingUI. Update the logic to disable modifications and add a lock icon or a tooltip explaining that a commercial license is required.
const isCommercialDisabled = ["app_name", "app_logo", "app_favicon", "hide_footer_brand"].includes(item.key) && configs.is_commercial !== "true";
- Step 5: Commit
git add vite-frontend/src/pages/config.tsx
git commit -m "feat: ui settings for commercial white-label and license activation"