From 43e05501eec5c5dede2578624ce18a602c73a8f1 Mon Sep 17 00:00:00 2001 From: Rogee Date: Wed, 26 Aug 2026 18:29:12 +0800 Subject: [PATCH] HH-701: document operations and local acceptance (#10) --- Makefile | 2 +- README.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 859721b..5c1ccac 100644 --- a/Makefile +++ b/Makefile @@ -16,4 +16,4 @@ down: docker compose down logs: - docker compose logs -f ssclash + docker compose logs -f mihomo diff --git a/README.md b/README.md index 032b700..6cc510f 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ docker compose up -d --build `subscription.url` must contain exactly one absolute HTTP(S) URL without URL userinfo. Compose mounts it read-only at `/run/secrets/subscription`; the URL is never passed in the environment or written to the image, volume, generated configuration, or logs. Keep this file out of Git. +The host file should remain mode `0600`; Compose exposes it inside the container as a read-only secret. Do not put the URL in `.env`, command-line arguments, Compose YAML, or support logs. The checked-in `.gitignore` and `.dockerignore` exclude the default secret filename, but operators remain responsible for protecting custom secret paths. + Open `http://127.0.0.1:9090/ui/` for the packaged MetaCubeXD interface. It uses Mihomo's controller and proxy-group APIs to inspect status and switch nodes. Proxy clients use: ```text @@ -23,13 +25,30 @@ Controller: http://127.0.0.1:9090 Both ports bind to host loopback by default. `PROXY_BIND`, `PROXY_PORT`, `CONTROLLER_BIND`, and `CONTROLLER_PORT` are optional overrides. Expose port 9090 only to a trusted network or authenticated reverse proxy; this minimal deployment intentionally does not add a second authentication layer. +In MetaCubeXD, open the `🚀 节点选择` group and choose a node or policy. The choice applies to the current Mihomo process only; `profile.store-selected` is disabled, so a restart returns to the configured default. + +## Data and restart recovery + +Compose mounts the `mihomo-data` named volume at `/data`. It contains generated configuration and normalized subscription data, including node credentials, with private container-side permissions. Treat the volume as sensitive: do not copy it into images, source control, unencrypted backups, or support bundles. + +`/data/last-good` is a managed relative symlink to `/data/generations/a` or `/data/generations/b`. Keep the same Compose project and named volume across upgrades and restarts. Do not use `docker compose down --volumes` unless intentionally deleting the cached configuration; removing the volume makes the next start a cold start that requires the subscription endpoint to be reachable. + +Normal recovery uses the existing volume: + +```sh +docker compose restart mihomo +docker compose ps +``` + ## Lifecycle and updates -On a fresh volume, bootstrap downloads, normalizes, generates, and validates a candidate with the packaged Mihomo binary before starting Mihomo. A failure exits nonzero without starting an empty configuration. +On a fresh volume, bootstrap downloads, normalizes, generates, and validates a candidate with the packaged Mihomo binary before starting Mihomo. This is the cold-start update; a failure exits nonzero without starting an empty configuration. -`/data/last-good` atomically points to one of two generation slots. On restart, a valid cached slot starts first and bootstrap immediately attempts an update. Download, HTTP, YAML, generation, Mihomo validation, publication, or reload failures leave the previous slot active. Successful updates use Mihomo's native `PUT /configs` API without replacing the foreground process. +On restart, bootstrap validates and starts the cached `last-good` slot, waits for the controller, and then immediately attempts an update. Later updates run hourly from container start. Each candidate is written to the inactive generation, validated, and atomically selected before Mihomo reloads it through the native `PUT /configs` API. -Updates run hourly from container start. Trigger the same update path immediately for tests or operations: +Download, HTTP, YAML, generation, or Mihomo validation failures reject the candidate and keep the running `last-good`. A reload failure restores the prior pointer and reloads the prior configuration. A storage failure, or failure to persist/reload that rollback, stops Mihomo instead of claiming an unsafe recovery; Compose's restart policy then retries startup from whatever valid `last-good` remains. + +Trigger the same update path immediately for tests or operations: ```sh docker kill --signal HUP mohomo-docker @@ -37,13 +56,41 @@ docker kill --signal HUP mohomo-docker The `bootstrap candidate` subcommand remains available for an isolated one-shot candidate pipeline check; a running service should use `SIGHUP` so the result is hot-reloaded. +## Troubleshooting + +Start with service state and bounded logs: + +```sh +docker compose ps +docker compose logs --tail=200 mihomo +curl --fail http://127.0.0.1:9090/version +docker compose exec mihomo readlink /data/last-good +docker compose exec mihomo /usr/local/bin/mihomo -t -d /data/last-good -f /data/last-good/config.yaml +``` + +Do not print `/run/secrets/subscription` or `/data/last-good/subscription.yaml` while collecting diagnostics. + +- `cold-start candidate failed`: there is no valid cache and the secret, endpoint, response, or generated configuration was rejected. Confirm the secret file contains one reachable absolute HTTP(S) URL and that the response is a single Mihomo/Clash YAML document with a non-empty `proxies` list. +- `update rejected; keeping last-good`: the service remains available on the old configuration. Fix the subscription response or connectivity, then send `SIGHUP` to retry. +- `reload rejected; restored and reloaded last-good`: the new candidate did not load, and bootstrap restored the previous configuration. Inspect the preceding error without exposing the subscription. +- `fatal update stopped Mihomo`: persistence or rollback could not be guaranteed. Check free space, ownership, and write access for the `/data` volume before relying on automatic restart. +- Controller works but the UI does not: use the trailing-slash URL `/ui/` and confirm the 9090 mapping with `docker compose port mihomo 9090`. A remote browser cannot use the default loopback binding; change `CONTROLLER_BIND` only after adding an appropriate network boundary. + ## Runtime assets The image pins and SHA-256 verifies Mihomo `v1.19.30`, MetaCubeXD `v1.273.0`, and ACL4SSR commit `6e27259b8625e360699c014f98f978ee7408c644`. Rules and UI files are local to the image; runtime does not call an online converter or rule provider. The container runs as an unprivileged user with all capabilities dropped, a read-only root filesystem, and only `/data` writable. Do not publish a derivative image without respecting the upstream Mihomo, MetaCubeXD, and ACL4SSR licenses. -## Verification +## Local acceptance + +```sh +./tests/container-smoke.sh +``` + +This single command builds the image and uses an isolated local provider with sanitized fake nodes and a fake query token. It covers cold-start failure, warm recovery, immediate and `SIGHUP` updates, invalid-candidate retention, restart recovery, the 7890/9090 boundary, ExternalUI loading, proxy-group reads, and one node switch. It never uses a real subscription or an online rule conversion service. The image build may still need network access to download the pinned official artifacts whose SHA-256 values are verified. + +For the complete developer check set, run: ```sh ./scripts/test.sh @@ -53,4 +100,4 @@ go mod verify git diff --check ``` -Tests use only local fake subscription URLs and fake node data. +All test fixtures use only sanitized fake values.