5.2 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/걸그룹/oricons/롤시나리오/. - Root-level numeric files such as
icons/1047.jpgare 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. Its current reverse proxy happens to
reach this server from 172.30.1.75, but Caddy, Nginx, HAProxy, or another
proxy can be used. Keep every observed proxy source CIDR in the untracked
TRUSTED_PROXY_CIDRS value, separated by commas or spaces, and re-check the
value whenever proxy networking changes. The published port binds all server
interfaces, so production also needs a host DOCKER-USER (or equivalent)
firewall rule allowing those source CIDRs to TCP 8191 and rejecting other
sources. The static Nginx edge applies the same source allowlist.
Prepare
cp .env.example .env
./deploy/scripts/init-secrets.sh
docker compose config --quiet
docker compose build
Apply and inspect the dedicated Docker ingress chain with root privileges:
sudo env TRUSTED_PROXY_CIDRS=172.30.1.75/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.
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.