86 Commits
Author SHA1 Message Date
Hide_D dc0e1a6da9 feat: store editor images in upload bind 2026-08-06 15:52:54 +00:00
Hide_D 9046a131f2 feat: add signed bind-backed user icon uploads 2026-08-06 15:40:55 +00:00
Hide_D a0e1271c88 Merge branch 'master' into feature/image-sync-trigger-20260806 2026-08-06 14:58:46 +00:00
Hide_D 2fb618aa5c feat: add scoped image sync endpoint 2026-08-06 14:58:41 +00:00
Hide_D 69f0fedc20 change local 2026-08-06 14:34:27 +00:00
Hide_D 5078f6f3b1 Generalize reverse proxy source allowlist 2026-08-06 11:32:40 +00:00
Hide_D 87cc2977d5 Disable legacy HTTP image hooks by default 2026-08-06 11:25:39 +00:00
Hide_D f04b81c606 Add secure Node image webhook service 2026-08-06 11:21:54 +00:00
Hide_D 24073326b3 Add canonical name-based general icons 2026-07-26 05:21:13 +00:00
Hide_D 785937addc 강서유서월드 추가 이미지 2025-06-27 03:38:03 +09:00
Hide_D 7a5e1950ec 다병종 이벤트 깃수 병종 이미지 2025-01-23 20:39:11 +09:00
Hide_D 5447189129 병종명 변경경 2024-12-28 20:48:03 +09:00
Hide_D 58c50c6655 버퍼 추가 2024-12-28 20:44:35 +09:00
Hide_D 5d1bf46ccd 병종 이미지 수정
- 이후 리뉴얼 때 큰 카드로 사용
2024-12-28 20:01:54 +09:00
Hide_D 2bed18136f 롤시나리오 2024-12-10 02:51:08 +09:00
Hide_D e0030cc390 강서유서월드 이미지 업데이트 2024-12-07 15:11:42 +09:00
Hide_D 885ab4502e 강서유서월드 전콘 2024-12-07 00:14:29 +09:00
sars 8c630f2b63 롤 시나리오 아이콘 1.0 업데이트 2024-12-04 22:50:13 -08:00
sars d2be68899d 롤시나리오 아이콘 추가 2024-12-02 19:16:40 -08:00
Hide_D 90b7fb8679 cr 지도 인식 편의를 위해 배 이미지 추가 2023-03-20 00:53:36 +09:00
Hide_D 972aebc0be cr 이미지 추가 2023-03-15 03:19:16 +09:00
Hide_D 3ee857c50a 삼모시네마틱유니버스 시나리오 2022-06-07 01:46:06 +09:00
Hide_D 0d60a63308 feat: 걸그룹 대전 이미지 업데이트 2022-04-20 21:28:32 +09:00
Hide_D e1ee13d2d9 백이병 추가.
- 임시 이미지로 구 근위병 이미지 사용용
2021-12-09 01:22:11 +09:00
Hide_D 711e951ddd 쿠키런킹덤 2021-09-19 02:43:50 +09:00
Hide_D 3a50184bb8 병종이미지형태 변경
weap숫자 -> crewtype숫자
신엔진용
2020-04-30 06:30:33 +09:00
Hide_D b341ed376c .htaccess 버림 2019-10-27 20:10:21 +09:00
Hide_D 2d106cf5fe 포켓몬스터 이미지셋 업로드 2019-10-03 15:35:36 +09:00
Hide_D b7f62fd70a Hash Key Installer 추가 2019-09-23 00:01:06 +09:00
Hide_D 4bfa257b02 git_pull이 CLI에서도 동작하게 변경 2019-09-22 23:15:29 +09:00
Hide_D 0b4ef0e333 결과값 반환 2019-09-22 22:48:57 +09:00
Hide_D 0a8b35d528 버그 수정 2019-09-22 22:46:04 +09:00
Hide_D 09052ec21d git_pull request 추가 2019-09-22 22:45:06 +09:00
Hide_D 100a0cb9be 스1 김가을 추가 2019-06-14 23:52:58 +09:00
Hide_D fd07bae0bc chess판 추가 2019-05-01 22:52:12 +09:00
Hide_D a501219eee 스타1프로게이머 이미지 등록 2019-04-08 00:03:13 +09:00
Hide_D fbb0cb0149 걸그룹 추가 이미지 반영 2018-12-09 20:13:41 +09:00
Hide_D 9240d2e328 걸그룹 이미지 추가 반영 2018-12-09 18:02:54 +09:00
Hide_D 1b54cc1c43 걸그룹 전콘 초안 반영 2018-12-08 20:49:49 +09:00
Hide_D b42e2ad627 htaccess 반영 2018-10-27 21:48:17 +09:00
Hide_DandGogs 1c11f0c2df Merge branch 'master' of cappu/image into master 2018-08-28 13:30:34 +09:00
cappu 78636596f5 투신 이미지 누락 2018-08-28 13:26:13 +09:00
Hide_DandGogs 0694dd0304 Merge branch 'master' of cappu/image into master 2018-08-26 22:46:57 +09:00
cappu 74d1eb10d4 파일명 수정 자동으로 매칭되게끔 2018-08-26 22:38:09 +09:00
Hide_D cb8f1cdd49 루드라사움 지도 개선 2018-08-10 02:19:27 +09:00
Hide_D fd6fce5c0d 루드라사움 도로 수정 2018-08-08 03:32:31 +09:00
Hide_D 064194b8c2 루드라사움 병종 이미지 변경 2018-08-08 03:28:29 +09:00
Hide_D 5c83344288 루드라사움 지도 업데이트 2018-08-08 02:52:14 +09:00
Hide_D 79366da01e 루드라사움 더미 이미지 2018-07-30 00:33:42 +09:00
Hide_D c53dbe201c 루드라사움 지도 추가 2018-07-29 23:39:15 +09:00
Hide_D 9a005714a6 miniche_road.png 변경 2018-07-18 22:19:46 +09:00
Hide_D 4ca3f3a524 봄 지도 2018-07-18 04:02:41 +09:00
Hide_D 59c07d82d6 지도 파일 경로 이동. 미니 지도 추가 2018-07-17 23:46:24 +09:00
Hide_D 9c6981c7ec Merge branch 'master' of https://storage.hided.net/gogs/devsam/image 2018-07-16 23:48:45 +09:00
Hide_D 7f2c953984 기본 지도 추가 2018-07-16 23:48:31 +09:00
cappu c3e6e155c5 Merge https://storage.hided.net/gogs/devsam/image 2018-06-21 20:44:21 +09:00
cappuandGogs 9b258265cd 일부 엑박뜨는 전콘 파일명수정 2018-06-21 20:37:38 +09:00
cappuandGogs 06aa7c5c82 환상향 전콘 리폼 2018-06-21 20:37:38 +09:00
Hide_DandGogs baf5da783e 삼국지6 테스트 헌제 2018-06-21 20:37:06 +09:00
cappu 9b3743c7be 일부 엑박뜨는 전콘 파일명수정 2018-06-21 20:33:07 +09:00
cappu 63a97c82ab 환상향 전콘 리폼 2018-06-21 20:23:29 +09:00
cappu 0afef7bfbc 환상향 전콘 추가 2018-06-21 20:01:23 +09:00
Hide_D 72d4013d48 걸그룹 전콘 추가 2018-06-16 11:02:43 +09:00
Hide_D 3fead3dc40 버그 수정 완료 2018-06-15 03:20:03 +09:00
Hide_D 2b466afc88 locale 위치 변경 2018-06-15 03:19:40 +09:00
Hide_D a264842bd9 setlocale 재설정 2018-06-15 03:19:26 +09:00
Hide_D 505226fc1c 테스트 4 2018-06-15 03:16:48 +09:00
Hide_D d98767cc70 테스트 3 2018-06-15 03:12:23 +09:00
Hide_D d68a4cf285 버그테스트2 2018-06-15 03:10:33 +09:00
Hide_D 5579aaec8e 버그 테스트1 2018-06-15 03:10:22 +09:00
Hide_D 9f3e02fe04 unicode escape 하도록 변경 2018-06-15 03:08:00 +09:00
Hide_D 1e9f8a583e 루드라사움 추가 2018-06-15 02:30:57 +09:00
Hide_D 2b7f71f1cc 2018-06-15 00:39:00 +09:00
Hide_D fa91fc11e1 파일 경로 변경 2018-06-15 00:38:05 +09:00
Hide_D cf32fb402d 소소한 수정 2018-06-15 00:30:19 +09:00
Hide_D 55608cae4f list.json 생성 코드 추가 2018-06-15 00:28:51 +09:00
Hide_D 2eab567919 Merge branch 'master' of ssh://storage.hided.net:2525/devsam/image 2018-06-14 23:06:44 +09:00
Hide_D da8905eb67 환상향 전콘 등록 2018-06-14 23:06:25 +09:00
Hide_D 4bb944b9fe hook 코드 추가 2018-06-14 23:06:00 +09:00
Hide_D a9274d2341 테스트3 2018-06-14 23:05:41 +09:00
Hide_D 8af6ff3201 테스트2 2018-06-14 22:57:32 +09:00
Hide_D 4ef8dd6ca7 테스트 커밋 2018-06-14 22:51:54 +09:00
Hide_D f01bc5a119 gogs_key.orig 준비 2018-06-14 22:47:39 +09:00
Hide_D 9283906dde gitignore 추가 2018-06-14 22:46:27 +09:00
Hide_D 787d24e287 새 병종 이미지 추가 2018-04-14 03:09:22 +09:00
Hide_D e5c3385b7f 디렉토리명을 용도에 맞게 변경.
새 병종 이미지를 덮어씌움
2018-04-08 15:00:59 +09:00
5665 changed files with 2102 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# Same-host native reverse proxies should use the loopback-only published port.
IMAGE_BIND_ADDRESS=127.0.0.1
IMAGE_PORT=8191
IMAGE_REPOSITORY_PATH=/home/letrhee/sam_rebuild/image
IMAGE_UID=1000
IMAGE_GID=1000
# Source CIDRs observed inside image-web. A host-native proxy normally appears
# as this Compose network's Docker bridge gateway, not as 127.0.0.1.
# Confirm after startup with: docker compose logs image-web
TRUSTED_PROXY_CIDRS=172.24.0.1/32
IMAGE_REMOTE_URL=https://gitea.hided.net/devsam/image.git
IMAGE_REPOSITORY_FULL_NAME=devsam/image
IMAGE_DEFAULT_BRANCH=master
IMAGE_ALLOWED_BRANCHES=master
IMAGE_PUBLIC_BASES=https://sam.hided.net/image,https://sam-image.hided.net
GITEA_WEBHOOK_SECRET_FILE=./secrets/gitea_webhook_secret
IMAGE_ADMIN_SECRET_FILE=./secrets/image_admin_secret
IMAGE_SYNC_CORE_SECRET_FILE=./secrets/image_sync_core_secret
IMAGE_SYNC_CORE2026_SECRET_FILE=./secrets/image_sync_core2026_secret
IMAGE_UPLOAD_CORE_SECRET_FILE=./secrets/image_upload_core_secret
IMAGE_UPLOAD_CORE2026_SECRET_FILE=./secrets/image_upload_core2026_secret
+10
View File
@@ -0,0 +1,10 @@
/hook/gogs_key.php
/hook/logs.txt
/hook/list.json
/hook/HashKey.php
/hook/legacy-enabled
/hook/inventory.v2.json
/.env
/secrets/*
!/secrets/.gitkeep
/runtime-data/
+212
View File
@@ -0,0 +1,212 @@
# 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`:
```dotenv
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
```sh
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:
```sh
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:
```caddyfile
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:
```text
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.
### Fallback sync callers
If Gitea webhook delivery is missed, Core and Core2026 can request a restricted
reconciliation through `POST /v1/sync`. This endpoint cannot select or change a
branch: it only fetches the current active branch and applies the same clean
worktree and fast-forward checks as a webhook deployment.
Each caller has a separate secret:
- `secrets/image_sync_core_secret` for legacy Core
- `secrets/image_sync_core2026_secret` for Core2026
The caller sends its name, a timestamp, a unique request ID, and an HMAC-SHA256
signature over `timestamp.request-id.<exact JSON body>`. Requests expire after
five minutes and successful request IDs are persisted for replay protection.
The body is either `{}` or `{ "commit": "<full-image-commit-sha>" }`; all other
fields are rejected. The optional commit asserts the expected remote tip and
does not grant checkout selection.
Distribute only the matching caller secret through an ignored secret file.
Never give either caller `image_admin_secret`, which also authorizes explicit
branch changes. This fallback handles webhook delivery outages; if the image
service itself is stopped, restore it and run the caller command again.
### Short-lived user-icon uploads
Core and Core2026 can store validated account icons and editor attachments
through this service with
`PUT /v1/uploads/<user-icons|content>/<client>/<random-32-hex>.<extension>`. Each game
server validates the authenticated user and image first, then sends the raw
image body with `X-Image-Client`, `X-Image-Expires`, `X-Image-Request-Id`, and
`X-Image-Signature` headers.
The signature is HMAC-SHA256 over
`expires.requestId.pathname.contentType.sha256(body)`. Expiry may be at most
five minutes in the future, so a grant cannot be reused for another path,
content type, body, or later upload. The service also checks the image magic,
caller scope, and request replay before it writes one immutable file below the
host bind directory `runtime-data/uploads`. User uploads are deliberately not
added to Git; Nginx exposes that bind read-only at `/icons/users/` for account
icons and `/uploads/` for editor content. User icons retain the 50KB limit;
editor content retains the existing 1MB limit.
Create separate upload secrets with `deploy/scripts/init-secrets.sh`. Mount only
the matching `image_upload_core_secret` or `image_upload_core2026_secret` on the
game server. The shared secrets stay server-side in Docker secrets; they are
not returned to browsers or forwarded to Cloudflare.
Run `deploy/scripts/init-secrets.sh` before the first Compose start so
`runtime-data/uploads` exists with permissions that allow the hook container to
write and the Nginx container to read. Back up this directory independently of
the Git repository when moving servers.
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
```sh
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:
```sh
./deploy/scripts/admin-deploy.sh <branch> [expected-commit]
```
The administration route is not exposed through the public reverse proxy.
### Tests and rollback
```sh
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.
+112
View File
@@ -0,0 +1,112 @@
name: sam-image
services:
image-hook:
build:
context: ./node-hook
image: sam-image-hook:1.2.0
restart: unless-stopped
user: "${IMAGE_UID:-1000}:${IMAGE_GID:-1000}"
read_only: true
init: true
environment:
PORT: "8081"
IMAGE_REPOSITORY_PATH: /data/image
IMAGE_REMOTE_URL: ${IMAGE_REMOTE_URL:-https://gitea.hided.net/devsam/image.git}
IMAGE_REPOSITORY_FULL_NAME: ${IMAGE_REPOSITORY_FULL_NAME:-devsam/image}
IMAGE_DEFAULT_BRANCH: ${IMAGE_DEFAULT_BRANCH:-master}
IMAGE_ALLOWED_BRANCHES: ${IMAGE_ALLOWED_BRANCHES:-master}
IMAGE_PUBLIC_BASES: ${IMAGE_PUBLIC_BASES:-https://sam.hided.net/image,https://sam-image.hided.net}
IMAGE_STATE_PATH: /var/lib/image-hook/state.json
IMAGE_UPLOAD_ROOT: /var/lib/image-hook/uploads
IMAGE_UPLOAD_STATE_PATH: /var/lib/image-hook/upload-state.json
GITEA_WEBHOOK_SECRET_FILE: /run/secrets/gitea_webhook_secret
IMAGE_ADMIN_SECRET_FILE: /run/secrets/image_admin_secret
IMAGE_SYNC_CLIENT_SECRET_FILES: core=/run/secrets/image_sync_core_secret,core2026=/run/secrets/image_sync_core2026_secret
IMAGE_UPLOAD_CLIENT_SECRET_FILES: core=/run/secrets/image_upload_core_secret,core2026=/run/secrets/image_upload_core2026_secret
MAX_UPLOAD_BYTES: "51200"
MAX_CONTENT_UPLOAD_BYTES: "1048576"
volumes:
- type: bind
source: ${IMAGE_REPOSITORY_PATH:-.}
target: /data/image
- ./runtime-data:/var/lib/image-hook
secrets:
- gitea_webhook_secret
- image_admin_secret
- image_sync_core_secret
- image_sync_core2026_secret
- image_upload_core_secret
- image_upload_core2026_secret
tmpfs:
- /tmp:size=16m,mode=1777
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
pids_limit: 64
mem_limit: 256m
cpus: 1.0
healthcheck:
test: [CMD, node, -e, "fetch('http://127.0.0.1:8081/healthz').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
interval: 10s
timeout: 3s
retries: 6
start_period: 10s
networks: [image-internal, image-egress]
image-web:
build:
context: ./deploy/nginx
image: sam-image-web:1.2.0
restart: unless-stopped
depends_on:
image-hook:
condition: service_healthy
read_only: true
environment:
TRUSTED_PROXY_CIDRS: ${TRUSTED_PROXY_CIDRS:?Set TRUSTED_PROXY_CIDRS to the direct reverse-proxy source CIDR list}
ports:
- "${IMAGE_BIND_ADDRESS:-0.0.0.0}:${IMAGE_PORT:-8191}:8080"
volumes:
- type: bind
source: ${IMAGE_REPOSITORY_PATH:-.}
target: /srv/image
read_only: true
- type: bind
source: ./runtime-data/uploads
target: /srv/uploads
read_only: true
tmpfs:
- /tmp:size=8m,mode=1777
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
pids_limit: 32
mem_limit: 64m
cpus: 0.5
healthcheck:
test: [CMD, wget, -q, -O, /dev/null, http://127.0.0.1:8080/healthz]
interval: 10s
timeout: 3s
retries: 6
networks: [image-internal, image-edge]
networks:
image-internal:
internal: true
image-egress:
image-edge:
secrets:
gitea_webhook_secret:
file: ${GITEA_WEBHOOK_SECRET_FILE:-./secrets/gitea_webhook_secret}
image_admin_secret:
file: ${IMAGE_ADMIN_SECRET_FILE:-./secrets/image_admin_secret}
image_sync_core_secret:
file: ${IMAGE_SYNC_CORE_SECRET_FILE:-./secrets/image_sync_core_secret}
image_sync_core2026_secret:
file: ${IMAGE_SYNC_CORE2026_SECRET_FILE:-./secrets/image_sync_core2026_secret}
image_upload_core_secret:
file: ${IMAGE_UPLOAD_CORE_SECRET_FILE:-./secrets/image_upload_core_secret}
image_upload_core2026_secret:
file: ${IMAGE_UPLOAD_CORE2026_SECRET_FILE:-./secrets/image_upload_core2026_secret}
+9
View File
@@ -0,0 +1,9 @@
FROM nginx:1.29.5-alpine@sha256:1eff5a5f3fcf8431a0abb7eddf5471fec24e5e1905a2581aeacdb07a4479b92b
COPY templates/default.conf.template /etc/image/default.conf.template
COPY entrypoint.sh /usr/local/bin/image-web-entrypoint
RUN chmod 0555 /usr/local/bin/image-web-entrypoint
USER nginx
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/image-web-entrypoint"]
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
set -eu
trusted_proxy_cidrs=${TRUSTED_PROXY_CIDRS:-${CADDY_SOURCE_CIDR:-}}
if [ -z "$trusted_proxy_cidrs" ]; then
echo "TRUSTED_PROXY_CIDRS is required" >&2
exit 1
fi
: > /tmp/trusted-proxy-allow.conf
for trusted_proxy_cidr in $(printf '%s' "$trusted_proxy_cidrs" | tr ',' ' '); do
case "$trusted_proxy_cidr" in
*[!0-9A-Fa-f:./]*)
echo "Invalid trusted proxy CIDR: $trusted_proxy_cidr" >&2
exit 2
;;
esac
printf 'allow %s;\n' "$trusted_proxy_cidr" >> /tmp/trusted-proxy-allow.conf
done
if [ ! -s /tmp/trusted-proxy-allow.conf ]; then
echo "TRUSTED_PROXY_CIDRS must contain at least one CIDR" >&2
exit 2
fi
cp /etc/image/default.conf.template /tmp/nginx.conf
exec nginx -c /tmp/nginx.conf -g 'daemon off;'
@@ -0,0 +1,137 @@
worker_processes auto;
pid /tmp/nginx.pid;
error_log /dev/stderr notice;
events {
worker_connections 256;
}
http {
include /etc/nginx/mime.types;
access_log /dev/stdout combined;
client_body_temp_path /tmp/client_body;
proxy_temp_path /tmp/proxy;
fastcgi_temp_path /tmp/fastcgi;
uwsgi_temp_path /tmp/uwsgi;
scgi_temp_path /tmp/scgi;
server {
listen 8080;
server_name _;
server_tokens off;
root /srv/image;
disable_symlinks on;
allow 127.0.0.1;
allow ::1;
include /tmp/trusted-proxy-allow.conf;
deny all;
location = /healthz {
proxy_pass http://image-hook:8081/healthz;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location = /v1/status {
proxy_pass http://image-hook:8081/v1/status;
proxy_set_header Host $host;
add_header Access-Control-Allow-Origin "*" always;
}
location = /v1/inventory {
proxy_pass http://image-hook:8081/v1/inventory;
proxy_set_header Host $host;
proxy_buffering off;
add_header Access-Control-Allow-Origin "*" always;
}
location = /v1/hooks/gitea {
limit_except POST { deny all; }
client_max_body_size 1m;
proxy_pass http://image-hook:8081/v1/hooks/gitea;
proxy_set_header Host $host;
proxy_set_header X-Gitea-Signature $http_x_gitea_signature;
proxy_set_header X-Gitea-Event $http_x_gitea_event;
proxy_set_header X-Gitea-Delivery $http_x_gitea_delivery;
proxy_request_buffering on;
}
location = /v1/sync {
limit_except POST { deny all; }
client_max_body_size 4k;
proxy_pass http://image-hook:8081/v1/sync;
proxy_set_header Host $host;
proxy_set_header X-Image-Client $http_x_image_client;
proxy_set_header X-Image-Timestamp $http_x_image_timestamp;
proxy_set_header X-Image-Request-Id $http_x_image_request_id;
proxy_set_header X-Image-Signature $http_x_image_signature;
proxy_request_buffering on;
}
location ^~ /v1/uploads/ {
limit_except PUT { deny all; }
client_max_body_size 1m;
proxy_pass http://image-hook:8081;
proxy_set_header Host $host;
proxy_set_header X-Image-Client $http_x_image_client;
proxy_set_header X-Image-Expires $http_x_image_expires;
proxy_set_header X-Image-Request-Id $http_x_image_request_id;
proxy_set_header X-Image-Signature $http_x_image_signature;
proxy_request_buffering on;
}
location ^~ /v1/admin/ { return 404; }
location = /image { return 404; }
location = /image/ { return 404; }
location ^~ /image/ { rewrite ^/image/(.*)$ /$1 last; }
location ^~ /game/ {
try_files $uri =404;
add_header Access-Control-Allow-Origin "*" always;
add_header X-Content-Type-Options nosniff always;
}
location ~ "^/icons/users/([a-z0-9][a-z0-9_-]{1,31})/([a-f0-9]{32}\.(?:avif|webp|jpe?g|png|gif))$" {
alias /srv/uploads/user-icons/$1/$2;
etag on;
expires 1y;
add_header Cache-Control "public, immutable" always;
add_header Access-Control-Allow-Origin "*" always;
add_header X-Content-Type-Options nosniff always;
}
location /icons/users/ { return 404; }
location ~ "^/uploads/([a-z0-9][a-z0-9_-]{1,31})/([a-f0-9]{32}\.(?:avif|webp|jpe?g|png|gif))$" {
alias /srv/uploads/content/$1/$2;
etag on;
expires 1y;
add_header Cache-Control "public, immutable" always;
add_header Access-Control-Allow-Origin "*" always;
add_header X-Content-Type-Options nosniff always;
}
location /uploads/ { return 404; }
location /icons/ {
try_files $uri =404;
add_header Access-Control-Allow-Origin "*" always;
add_header X-Content-Type-Options nosniff always;
}
location = /hook/list.json {
try_files $uri =404;
add_header Access-Control-Allow-Origin "*" always;
}
location = /hook/inventory.v2.json {
try_files $uri =404;
add_header Access-Control-Allow-Origin "*" always;
}
location ~ (^|/)\. { return 404; }
location ~ \.php$ { return 404; }
location / { return 404; }
}
}
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
set -eu
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
echo "Usage: $0 <branch> [commit]" >&2
exit 2
fi
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repository_dir=$(CDPATH= cd -- "$script_dir/../.." && pwd)
branch=$1
commit=${2:-}
set -- deploy --branch "$branch"
if [ -n "$commit" ]; then
set -- "$@" --commit "$commit"
fi
exec docker compose --project-directory "$repository_dir" exec -T image-hook node src/admin-cli.mjs "$@"
+69
View File
@@ -0,0 +1,69 @@
#!/bin/sh
set -eu
action=${1:-check}
trusted_proxy_cidrs=${TRUSTED_PROXY_CIDRS:-${CADDY_SOURCE_CIDR:-}}
image_port=${IMAGE_PORT:-8191}
chain=SAM_IMAGE_INGRESS
case "$image_port" in
''|*[!0-9]*) echo "IMAGE_PORT must be numeric" >&2; exit 2 ;;
esac
require_root() {
if [ "$(id -u)" -ne 0 ]; then
echo "Run this action as root." >&2
exit 1
fi
}
case "$action" in
check)
iptables -S DOCKER-USER 2>/dev/null | grep -F "$chain" || true
iptables -S "$chain" 2>/dev/null || true
;;
apply)
require_root
if [ -z "$trusted_proxy_cidrs" ]; then
echo "TRUSTED_PROXY_CIDRS is required" >&2
exit 2
fi
rule_count=0
for trusted_proxy_cidr in $(printf '%s' "$trusted_proxy_cidrs" | tr ',' ' '); do
case "$trusted_proxy_cidr" in
*[!0-9A-Fa-f:./]*)
echo "Invalid trusted proxy CIDR: $trusted_proxy_cidr" >&2
exit 2
;;
esac
rule_count=$((rule_count + 1))
done
if [ "$rule_count" -eq 0 ]; then
echo "TRUSTED_PROXY_CIDRS must contain at least one CIDR" >&2
exit 2
fi
iptables -n -L DOCKER-USER >/dev/null
iptables -n -L "$chain" >/dev/null 2>&1 || iptables -N "$chain"
iptables -F "$chain"
iptables -A "$chain" -j DROP
for trusted_proxy_cidr in $(printf '%s' "$trusted_proxy_cidrs" | tr ',' ' '); do
iptables -I "$chain" 1 -s "$trusted_proxy_cidr" -j ACCEPT
done
iptables -C DOCKER-USER -p tcp -m conntrack --ctorigdstport "$image_port" -j "$chain" 2>/dev/null \
|| iptables -I DOCKER-USER 1 -p tcp -m conntrack --ctorigdstport "$image_port" -j "$chain"
;;
remove)
require_root
while iptables -C DOCKER-USER -p tcp -m conntrack --ctorigdstport "$image_port" -j "$chain" 2>/dev/null; do
iptables -D DOCKER-USER -p tcp -m conntrack --ctorigdstport "$image_port" -j "$chain"
done
if iptables -n -L "$chain" >/dev/null 2>&1; then
iptables -F "$chain"
iptables -X "$chain"
fi
;;
*)
echo "Usage: $0 [check|apply|remove]" >&2
exit 2
;;
esac
+23
View File
@@ -0,0 +1,23 @@
#!/bin/sh
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repository_dir=$(CDPATH= cd -- "$script_dir/../.." && pwd)
secret_dir="$repository_dir/secrets"
state_dir="$repository_dir/runtime-data"
umask 077
mkdir -p "$secret_dir"
mkdir -p "$state_dir"
mkdir -p "$state_dir/uploads"
for name in gitea_webhook_secret image_admin_secret image_sync_core_secret image_sync_core2026_secret image_upload_core_secret image_upload_core2026_secret; do
path="$secret_dir/$name"
if [ ! -e "$path" ]; then
openssl rand -hex 32 > "$path"
fi
chmod 600 "$path"
done
chmod 700 "$state_dir"
chmod 755 "$state_dir/uploads"
echo "Secret files are ready in $secret_dir (values not printed)."
View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 266 KiB

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 1010 B

After

Width:  |  Height:  |  Size: 1010 B

Before

Width:  |  Height:  |  Size: 375 B

After

Width:  |  Height:  |  Size: 375 B

Before

Width:  |  Height:  |  Size: 1013 B

After

Width:  |  Height:  |  Size: 1013 B

Before

Width:  |  Height:  |  Size: 649 B

After

Width:  |  Height:  |  Size: 649 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 582 B

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Some files were not shown because too many files have changed in this diff Show More