Files
image/README.md
T
2026-08-06 14:34:27 +00:00

6.1 KiB

Shared image repository

icons/ is served as /image/icons/. General portraits follow these rules:

  • icons/장수/<장수명>.<확장자> is the canonical shared path.
  • Scenario-specific collections keep a separate directory such as icons/걸그룹/ or icons/롤시나리오/.
  • Root-level numeric files such as icons/1047.jpg are compatibility aliases for deployed legacy versions. Do not delete or replace them while those versions are in service.
  • A general name must map to one canonical file. If two records intentionally need different portraits, disambiguate the scenario name or use an explicit scenario-specific path.

The core2026 repository owns resources/general-icons.json and tools/manage-general-icons.mjs. Use that tool with this repository as --image-root to synchronize aliases, scenario paths, and verify that every catalog source exists and has identical bytes.

Public URLs

The same tracked files are exposed through both URL contracts:

  • https://sam.hided.net/image/game/... and /image/icons/...
  • https://sam-image.hided.net/game/... and /icons/...
  • https://sam-image.hided.net/image/... is a compatibility alias.

Do not redirect the old /image/* URLs to the dedicated domain. Existing PHP and core2026 clients use same-origin relative paths and can move independently. The image server never exposes the repository root, .git, PHP sources, or directory listings.

Node webhook deployment service

compose.yaml runs a read-only Nginx static edge and an internal Node service. Only the edge publishes port 8191. The Node service verifies the raw Gitea X-Gitea-Signature, accepts push events for devsam/image, fetches the exact remote branch tip, rejects dirty or non-fast-forward updates, and serializes all Git changes. The initial and only allowed branch is master unless IMAGE_ALLOWED_BRANCHES is explicitly expanded.

The service is reverse-proxy agnostic. Caddy, Nginx, HAProxy, or another proxy can use the same port and path contract.

For a native reverse proxy running on the same host as Docker, publish port 8191 on loopback only and proxy to 127.0.0.1:8191:

IMAGE_BIND_ADDRESS=127.0.0.1
IMAGE_PORT=8191
TRUSTED_PROXY_CIDRS=172.24.0.1/32

These two addresses intentionally describe different hops. The native proxy connects to the host loopback address, while the Nginx process inside image-web normally observes the connection as coming from that Compose network's Docker bridge gateway (172.24.0.1 in the example). Use the source shown in the image-web access/error log, then recreate image-web whenever TRUSTED_PROXY_CIDRS changes. Do not replace it with a Cloudflare CIDR: the direct peer of image-web remains the host-native reverse proxy path.

If the reverse proxy is on another host, bind an explicit private interface instead of loopback and apply the host DOCKER-USER (or equivalent) firewall rule. Keep every source CIDR observed by image-web in the untracked TRUSTED_PROXY_CIDRS value, separated by commas or spaces. Avoid 0.0.0.0 unless the firewall has already been verified.

Prepare

cp .env.example .env
./deploy/scripts/init-secrets.sh
docker compose config --quiet
docker compose build

Only for a proxy on another host, apply and inspect the dedicated Docker ingress chain with root privileges:

sudo env TRUSTED_PROXY_CIDRS=192.0.2.10/32 IMAGE_PORT=8191 \
  ./deploy/scripts/firewall-8191.sh apply
sudo ./deploy/scripts/firewall-8191.sh check

The script only owns the SAM_IMAGE_INGRESS chain and its port-8191 jump from DOCKER-USER; it does not flush shared firewall chains.

For the same-host native-proxy case, point the proxy upstream at loopback. A minimal Caddy site is:

sam-image.hided.net {
    reverse_proxy 127.0.0.1:8191
}

The equivalent Nginx/HAProxy configuration should use the same upstream. The application does not depend on Caddy-specific request behavior.

Secret values are generated under ignored secrets/ files with mode 0600 and are never printed. Put the contents of secrets/gitea_webhook_secret into the Gitea webhook configuration. Configure a JSON push webhook targeting:

https://sam-image.hided.net/v1/hooks/gitea

Use branch filter master. Disable the old PHP webhook before enabling the new writer. The legacy PHP files remain in hook/ for an explicit rollback, but PHP and Node must never mutate the checkout concurrently.

Legacy HTTP mutation is disabled by default. An emergency PHP rollback must first stop image-hook, then create the ignored hook/legacy-enabled sentinel in the legacy checkout before restoring its Caddy/Gitea route. Remove the sentinel before Node is started again. CLI execution of hook/git_pull.php from the hook/ directory remains available for local recovery without exposing the HTTP endpoint.

Start and verify

docker compose up -d --build
docker compose ps
curl -fsS https://sam-image.hided.net/healthz
curl -fsS https://sam-image.hided.net/v1/status
curl -fsS https://sam-image.hided.net/game/back.jpg -o /dev/null
curl -fsS https://sam-image.hided.net/image/icons/default.jpg -o /dev/null

Nginx serves only game/, icons/, hook/list.json, and hook/inventory.v2.json. The API inventory additionally reports the deployed branch, full commit, generation time, asset list, and both public base URLs.

Explicit branch deployment

Automatic pushes only update the active branch. Add a branch to IMAGE_ALLOWED_BRANCHES, recreate image-hook, and switch it with the internal signed administration command:

./deploy/scripts/admin-deploy.sh <branch> [expected-commit]

The administration route is not exposed through the public reverse proxy.

Tests and rollback

docker build -t sam-image-hook:test node-hook
docker run --rm sam-image-hook:test npm test
docker compose exec -T image-web nginx -t -c /tmp/nginx.conf

Stopping image-hook does not remove the last checked-out files from the static service. For a full rollback, stop Node mutation first, restore the old Caddy/PHP webhook route, and only then enable the legacy writer. Preserve both checkouts until public file hashes and representative browser requests have been verified.