# QYCW Go Server

Go backend for QYCW. Python backend and Python plugins are no longer part of the Go plugin path.

Migration differences and temporary gaps are tracked in `../docs/go-migration-differences.md`.

## Principles

- Keep `/api/v1/...` compatibility.
- Keep the response envelope: `{ "code": 200, "message": "success", "data": ... }`.
- Read `.env` during the early migration stage.
- Move business settings to database-backed admin configuration later.

## Run

```bash
go run ./cmd/api
```

Optional bootstrap flags:

```bash
go run ./cmd/api -http.addr=:8080 -db.enabled=false -redis.enabled=false
```

The server reads `.env` from `go-server/.env`. The current Python backend `.env` keys are supported:

```text
APP_NAME
APP_VERSION
DB_HOST
DB_PORT
DB_USER
DB_PASSWORD
DB_NAME
REDIS_HOST
REDIS_PORT
REDIS_PASSWORD
REDIS_DB
JWT_SECRET_KEY
JWT_ALGORITHM
ACCESS_TOKEN_EXPIRE_MINUTES
REFRESH_TOKEN_EXPIRE_DAYS
GEETEST_CAPTCHA_ID
GEETEST_CAPTCHA_KEY
GEETEST_FAIL_OPEN
SMTP_HOST
SMTP_PORT
SMTP_USER
SMTP_PASSWORD
SMTP_FROM_EMAIL
SMTP_FROM_NAME
GO_LICENSE_SERVER_URL
GO_LICENSE_TIMEOUT
GO_LICENSE_HEARTBEAT_INTERVAL
GO_LICENSE_KEY_FILE
GO_LICENSE_REQUIRE_PINNED_KEY
```

Development authorization service defaults to `http://127.0.0.1:8090`.
Encrypted L2 credentials are stored in `license_credentials`. The AES-256-GCM
machine key defaults to `data/license/master.key`; keep this file restricted to
the service account and include it in the node's protected disaster-recovery
backup. Losing the key requires license reactivation.

Production builds require an Ed25519 public key and matching key ID embedded at
link time. The service fails closed when they are absent:

```bash
go build -ldflags "-X qycw/go-server/internal/license.EmbeddedPublicKeyBase64=BASE64URL_PUBLIC_KEY -X qycw/go-server/internal/license.EmbeddedPublicKeyID=KEY_ID" ./cmd/api
go build -ldflags "-X qycw/go-server/internal/license.EmbeddedPublicKeyBase64=BASE64URL_PUBLIC_KEY -X qycw/go-server/internal/license.EmbeddedPublicKeyID=KEY_ID" ./cmd/worker
```

Only local development may set `GO_LICENSE_REQUIRE_PINNED_KEY=false` to fetch a
temporary public key from the development authorization service. Non-loopback
license endpoints must use HTTPS, and redirects are never followed.

Signed lease `sequence` values are restricted to `1..9223372036854775807` so
the Go protocol and the signed MySQL `BIGINT` column use the same range. Query
and heartbeat must share one transactionally increasing per-instance sequence.
Heartbeat scheduling adds per-process jitter and backs off after consecutive
failures to avoid synchronized retry spikes. A failed heartbeat still closes
the in-memory gate immediately.

Query and Heartbeat both require `Authorization: Bearer <instanceToken>` and an
exactly matching `instanceId`. Heartbeat also includes a fresh nonce and the
reported client version. The Reporter never sends the activation code and never
calls Register automatically. An expired or rejected instance token keeps all
commercial routes blocked; the API starts only its login/activation surface so
an administrator can complete the explicit activation flow. Suspended leases
retain the token so a later signed `active` heartbeat can restore service;
revoked leases remain force-blocked.

L1 anchors time to the signed lease `issuedAt` plus Go's local monotonic elapsed
time. Lease checks use the later of that estimate and the wall clock, so moving
the host clock backwards cannot extend `validUntil`. A drift greater than five
minutes fails closed until a fresh signed Query or Heartbeat recalibrates L1.

Database and Redis are enabled by default so the migrated routes can reuse the existing MySQL and Redis data. Disable them explicitly for local skeleton-only checks:

```bash
go run ./cmd/api ^
  -db.enabled=false ^
  -redis.enabled=false
```

## Hot Reload

Install Air once:

```bash
go install github.com/air-verse/air@latest
```

Then start the dev server manually from `go-server`:

```bash
air
```

Air uses `.air.toml`, rebuilds `./cmd/api`, and writes temporary files to `tmp/`.

## Worker

The worker is started manually when needed:

```bash
go run ./cmd/worker
```

Current worker scope:

- scans `instances(status=provisioning)`;
- respects `products.auto_provision=false` and leaves the instance for manual provisioning;
- supports the built-in-compatible `generic_blank` provision plugin;
- supports Go process plugins through the common plugin layer;
- records `provision_log` for providers that are missing, not configured, or fail.

Optional worker settings:

```text
GO_PROVISION_WORKER_INTERVAL=30s
GO_PROVISION_WORKER_BATCH_SIZE=20
GO_PROVISION_MAX_ATTEMPTS=3
GO_PLUGIN_DIRS=data/plugins,plugins
GO_PLUGIN_TIMEOUT=30s
```

See `../docs/go-plugin-system.md` for plugin package format, JSON-RPC methods, and the example plugin.

Process plugins run outside the Go address space. The runtime rejects entries
outside the installed plugin directory, passes only a small environment
allowlist, enforces a one MiB stdout/stderr limit, and terminates calls at the
configured timeout. This is process hardening, not a native-code security
sandbox. Untrusted plugins must run under a separate service account or an OS
sandbox/container that cannot read the database credentials, license machine
key, application binaries, or host process memory. Only signed, trusted plugin
packages should be enabled in production.

## Current Endpoints

- `GET /health`
- `GET /api/v1/health`
- `GET /api/v1/system/bootstrap`
- `/api/v1/auth/*` login, register, code login, SMS code login, refresh, logout
- `/api/v1/admin/auth/*` admin login, refresh, logout, current user
- `/api/v1/users/me*` current user, profile update, password change, email/phone binding
- `/api/v1/store/categories`
- `/api/v1/store/categories/{cat_slug}/groups`
- `/api/v1/store/categories/{cat_slug}/groups/{group_slug}/products`
- `/api/v1/cart` list, add, clear
- `/api/v1/cart/{item_id}` update, delete
- `GET /api/v1/orders/coupons/available`
- `POST /api/v1/orders/coupons/preview`
- `POST /api/v1/orders/preview` Go-side alias for checkout amount preview
- `POST /api/v1/orders/checkout` balance checkout, order write, stock decrement, coupon/points ledger, instance records
- `GET /api/v1/orders`
- `GET /api/v1/orders/{order_id}`
- `/api/v1/recharge/*` balance recharge, payment options, status, callback
- `/api/v1/admin/plugins/*` plugin upload, list, enable, disable

SMS, email, verification, provision, and payment plugins are executed by the Go plugin runtime.
