Files
harbor-net/docs/product/application-state.md
T
dokril aa9c959368
Build and Deploy Gateway / build-and-push (push) Successful in 20s
Build and Deploy Gateway / deploy (push) Successful in 13s
Refactor VPN proxy components and update related behavior
2026-08-11 01:27:46 +03:00

5.1 KiB

Harbor application state v1

GET /api/state is the canonical Harbor domain snapshot. Successful mutations return the same snapshot as state. The persisted owner is state.json schema v5; React keeps only drafts, disclosure, focus, animation and transport freshness.

An abbreviated snapshot:

{
  "apiVersion": 1,
  "revision": 42,
  "mode": "client",
  "profiles": [
    {
      "id": "profile_primary",
      "label": "Личный",
      "subscription": {
        "status": "ready",
        "host": "provider.example/…",
        "fetchedAt": "2026-08-11T12:00:00.000Z",
        "userInfo": {},
        "lastRefreshAttemptAt": null,
        "errorCode": null
      },
      "desiredServerId": "srv_amsterdam",
      "servers": [
        {
          "id": "srv_amsterdam",
          "label": "Amsterdam",
          "host": "nl.example.net",
          "port": 443,
          "protocol": "vless"
        }
      ]
    }
  ],
  "selection": {
    "desiredProfileId": "profile_primary",
    "desiredServerId": "srv_amsterdam",
    "appliedProfileId": "profile_primary",
    "appliedServerId": "srv_amsterdam",
    "appliedServerSnapshot": {
      "id": "srv_amsterdam",
      "label": "Amsterdam",
      "host": "nl.example.net",
      "port": 443,
      "protocol": "vless"
    }
  },
  "operation": {
    "kind": null,
    "status": "idle",
    "startedAt": null,
    "error": null,
    "profileId": null,
    "serverId": null
  }
}

Every profile owns one private provider URL/config, public metadata, server list and desired server. The URL/config never enters the public snapshot. subscription and top-level servers remain a one-release projection of the desired profile for older clients; they are not a second owner.

Revision rules

revision increases for every visible domain transition, including operation start, completion and failure. Commands carry expectedRevision; stale commands fail with STATE_CONFLICT. Duplicate profile labels are rejected by preflight without a provider request or revision change.

The frontend accepts only newer snapshots. Equal revisions preserve object identity, and older polling responses cannot overwrite mutation results. After a failed mutation the browser immediately synchronizes the authoritative snapshot before allowing another command or retry.

Browser boot/offline/stale state remains a transport envelope beside the domain snapshot. A transport failure retains the last accepted domain state.

Desired and applied identity

desiredProfileId and each profile's desiredServerId record the next local choice. appliedProfileId, appliedServerId and appliedServerSnapshot describe the runtime that actually owns traffic. There is no third activeProfileId.

While stopped, selecting or activating a profile only updates desired state. While running, changing the applied profile/server builds a candidate config, starts it, then publishes desired and applied identity in one final state commit. Until that commit the old applied pair remains authoritative. A failure restores the previous config, runtime and state.

If refresh removes the applied server, the running process is not silently switched. The provider list and desired selection are cleared as needed, while appliedServerSnapshot retains the last applied label until explicit stop or a successful switch. Stop clears applied identity and keeps the desired pair.

Profile operations

The canonical API is scoped by profile:

  • POST /api/profiles adds a profile after one explicit provider fetch;
  • PATCH /api/profiles/:id renames it locally;
  • PUT /api/profiles/:id/server selects one of its servers;
  • POST /api/profiles/:id/activate activates/switches it;
  • POST /api/profiles/:id/refresh refreshes only that provider;
  • DELETE /api/profiles/:id deletes it, with explicit stop-and-delete for a running applied profile;
  • POST /api/profiles/:id/servers/ping performs bounded transient health checks.

Provider failure retains the last successful list and metadata, marks only the target profile stale and records the last successful timestamp. Refreshing, pinging or deleting an inactive profile does not mutate the applied config/runtime. Background refresh iterates profiles independently every 15 minutes.

Compatibility and migration

Schema v5 migrates the legacy singleton and subscription-cache.json into one profile named Основной. Stable endpoint identity preserves unambiguous desired/applied selection, including transport variants whose normalized IDs differ from old labels. An explicitly stopped legacy state does not resurrect an old applied target.

Migration atomically backs up the previous state.json. After the embedded profile is committed, Harbor also backs up and removes the legacy subscription cache so there is one persisted owner. Invalid legacy cache/config returns to a truthful stopped first-run state instead of starting stale generated config.

The old HTTP projection remains bounded for one release, but schema v5 persistence is not downgrade-compatible. To roll back to a pre-v5 build, stop Harbor and restore both pre-migration backups rather than deploying old code over v5 data.