commit 5e33f4c530576f6e77755eeb2ae746aa6c82990d Author: ct Date: Sun Mar 15 19:26:37 2026 -0400 game builds proxy diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c185130 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM nginx:alpine +COPY nginx.conf.template /etc/nginx/nginx.conf.template +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh && mkdir -p /var/cache/nginx/builds +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..9409519 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# Game Builds Service + +Nginx reverse proxy to Hetzner Object Storage with local caching. Upload builds with `s3cmd`, testers access them at `https://builds.kill.systems//`. + +## Setup + +### 1. Create a public bucket for builds + +In Hetzner Console → Object Storage, create a new bucket (e.g. `kill-builds`). Set visibility to **public** so Nginx can proxy to it without authentication. + +Generate S3 credentials if you haven't already (Security → S3 Credentials). + +### 2. Configure s3cmd locally + +```bash +# Install +brew install s3cmd # or apt install s3cmd + +# Configure +s3cmd --configure \ + --host=fsn1.your-objectstorage.com \ + --host-bucket='%(bucket)s.fsn1.your-objectstorage.com' +``` + +Enter your Hetzner S3 access key and secret key when prompted. + +### 3. Push this repo to Gitea + +```bash +cd game-builds +git init +git add . +git commit -m "game builds proxy" +git remote add origin https://src.kill.systems//game-builds.git +git push -u origin main +``` + +### 4. Deploy in Dokploy + +1. Create a new project (e.g. "builds") +2. Add a new **Application** service (not Docker Compose) +3. Set source to your Gitea repo +4. In **Environment Variables**, add: + ``` + BUCKET_ORIGIN=https://kill-builds.fsn1.your-objectstorage.com/builds/ + BUCKET_HOST=kill-builds.fsn1.your-objectstorage.com + ``` + Replace `kill-builds` with your actual bucket name. +5. Set the port to `3000` +6. Deploy + +### 5. Add the domain + +In the Domains tab, add `builds.kill.systems` with HTTPS and Let's Encrypt on port 3000. + +DNS at Squarespace: +``` +Host: builds +Type: A +Data: 46.224.133.129 +``` + +## Uploading Builds + +Edit `deploy.sh` and set your bucket name, then: + +```bash +# Upload current build (uses git hash as version) +./deploy.sh ./dist + +# Upload with a specific version name +./deploy.sh ./dist v1.2.3 + +# Upload with a custom label +./deploy.sh ./dist beta-march15 +``` + +Testers visit `https://builds.kill.systems//` to play. + +## How it works + +``` +Tester → builds.kill.systems → Nginx (cache) → Hetzner Object Storage + ↓ + Cached for 30 days + (builds are immutable) +``` + +First request fetches from Object Storage and caches locally. Subsequent requests are served from the Nginx cache. The `X-Cache-Status` response header shows `HIT` or `MISS`. + +Since each build has a unique path (the git hash), cache invalidation is never needed — new builds go to new paths. diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..86124da --- /dev/null +++ b/deploy.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Upload a build to Hetzner Object Storage. +# Usage: ./deploy.sh ./dist [version] +# +# If version is omitted, uses the current git short hash. +# Requires: s3cmd configured for Hetzner Object Storage. +set -euo pipefail + +BUILD_DIR="${1:?Usage: ./deploy.sh [version]}" +VERSION="${2:-$(git rev-parse --short HEAD 2>/dev/null || date +%s)}" + +# Configure these for your setup +BUCKET="s3://your-builds-bucket" +PREFIX="builds" +SITE_URL="https://builds.kill.systems" + +# Hetzner Object Storage endpoint +S3CMD_OPTS="--host=fsn1.your-objectstorage.com --host-bucket=%(bucket)s.fsn1.your-objectstorage.com" + +if [[ ! -d "$BUILD_DIR" ]]; then + echo "ERROR: $BUILD_DIR is not a directory" + exit 1 +fi + +echo "Uploading build $VERSION..." +s3cmd sync \ + $S3CMD_OPTS \ + --acl-public \ + --no-mime-magic \ + --guess-mime-type \ + "$BUILD_DIR/" \ + "${BUCKET}/${PREFIX}/${VERSION}/" + +echo "" +echo "Build uploaded:" +echo " ${SITE_URL}/${VERSION}/" diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..1461704 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +envsubst '$BUCKET_ORIGIN $BUCKET_HOST' \ + < /etc/nginx/nginx.conf.template \ + > /etc/nginx/conf.d/default.conf +exec nginx -g 'daemon off;' diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..5bf72a3 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,27 @@ +proxy_cache_path /var/cache/nginx/builds levels=1:2 + keys_zone=builds:10m max_size=2g + inactive=30d use_temp_path=off; + +server { + listen 3000; + + location / { + proxy_pass $BUCKET_ORIGIN; + proxy_cache builds; + proxy_cache_valid 200 30d; + proxy_cache_valid 404 1m; + proxy_intercept_errors on; + + proxy_hide_header x-amz-request-id; + proxy_hide_header x-amz-id-2; + proxy_hide_header x-amz-meta-s3cmd-attrs; + proxy_hide_header Set-Cookie; + proxy_ignore_headers Set-Cookie; + + proxy_set_header Host $BUCKET_HOST; + proxy_set_header Cookie ""; + proxy_set_header Authorization ""; + + add_header X-Cache-Status $upstream_cache_status; + } +}