{"id":47620184,"url":"https://github.com/jxsl13/teeworlds-asset-service","last_synced_at":"2026-04-05T12:06:43.288Z","repository":{"id":346029451,"uuid":"1188074566","full_name":"jxsl13/teeworlds-asset-service","owner":"jxsl13","description":"Host your own Teeworlds Asset Database with skins, gameskins, maps, HUD and more","archived":false,"fork":false,"pushed_at":"2026-03-22T00:31:40.000Z","size":6503,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-22T11:16:56.694Z","etag":null,"topics":["ddnet","ddnet-client","emoticon","gameskin","map","maps","skins","teeworlds","theme"],"latest_commit_sha":null,"homepage":"https://db.zcat.ch","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jxsl13.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-21T15:29:31.000Z","updated_at":"2026-03-22T09:35:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jxsl13/teeworlds-asset-service","commit_stats":null,"previous_names":["jxsl13/teeworlds-asset-service"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/jxsl13/teeworlds-asset-service","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxsl13%2Fteeworlds-asset-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxsl13%2Fteeworlds-asset-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxsl13%2Fteeworlds-asset-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxsl13%2Fteeworlds-asset-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jxsl13","download_url":"https://codeload.github.com/jxsl13/teeworlds-asset-service/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxsl13%2Fteeworlds-asset-service/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31292631,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ddnet","ddnet-client","emoticon","gameskin","map","maps","skins","teeworlds","theme"],"created_at":"2026-04-01T21:59:55.100Z","updated_at":"2026-04-05T12:06:43.272Z","avatar_url":"https://github.com/jxsl13.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Teeworlds Asset Database\n\nCommunity database for Teeworlds assets — skins, maps, gameskins \u0026 more. Go service with layered architecture and OpenAPI-first code generation.\n\n## Configuration\n\nAll configuration is via environment variables. See [`docker/dev.env`](docker/dev.env) for a working example.\n\n### Database\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `DB_HOST` | **yes** | — | PostgreSQL host |\n| `DB_PORT` | no | `5432` | PostgreSQL port |\n| `DB_USER` | **yes** | — | PostgreSQL user |\n| `DB_PASSWORD` | **yes** | — | PostgreSQL password |\n| `DB_NAME` | **yes** | — | PostgreSQL database name |\n| `DB_SSLMODE` | no | `disable` | SSL mode (`disable`, `require`, `verify-full`, …) |\n\n### Server\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `ADDR` | no | `:8080` | TCP address the HTTP server listens on |\n| `INSECURE` | no | `false` | Set to `true` to allow non-HTTPS cookies (local dev) |\n| `ADMIN_ONLY_UPLOAD` | no | `false` | Restrict uploads to admin users only |\n| `ITEMS_PER_PAGE` | no | `100` | Default number of items per page (1–1000) |\n\n### Branding\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `BRANDING_TITLE` | no | `Teeworlds Asset Database` | Page title and header heading |\n| `BRANDING_SUBTITLE` | no | `Community database for skins, maps, gameskins \u0026 more` | Tagline below the header heading |\n| `BRANDING_HEADER_IMAGE_PATH` | no | — | Local file path for a logo/image displayed in the header (served at `/branding/header-image`) |\n| `BRANDING_FAVICON_PATH` | no | — | Local file path for the browser tab icon (served at `/branding/favicon`) |\n| `DEFAULT_ASSET_TYPE` | no | `map` | Asset type tab selected on initial page load (e.g. `skin`, `map`, `emoticon`) |\n\n### Storage\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `STORAGE_PATH` | **yes** | — | Base directory for all stored asset files |\n| `TEMP_UPLOAD_PATH` | no | OS temp dir | Directory for temporary upload files |\n| `MAX_STORAGE_SIZE` | no | `1GiB` | Maximum total size for all stored items (human-readable, e.g. `10GiB`, `500MB`) |\n\n### Per-Asset-Type Overrides\n\nEach variable below can be suffixed with the asset type in uppercase:\n`MAP`, `GAMESKIN`, `HUD`, `SKIN`, `ENTITY`, `THEME`, `TEMPLATE`, `EMOTICON`.\n\n| Variable Pattern | Default | Description |\n|---|---|---|\n| `ALLOWED_RESOLUTIONS_{TYPE}` | built-in per type | Comma-separated `WxH` pairs (e.g. `256x128,512x256`) |\n| `MAX_UPLOAD_SIZE_{TYPE}` | derived from largest resolution | Maximum upload size per item (e.g. `10MiB`) |\n| `THUMBNAIL_SIZE_{TYPE}` | smallest resolution (map: `1920x1080`, skin: `64x64`) | Thumbnail bounding box as `WxH` |\n\n### OIDC / Pocket-ID\n\nWhen all four variables below are set, OIDC authentication and admin functionality are enabled. When omitted, the service runs in anonymous-only mode with no login link, no admin controls, and no admin API routes.\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `EXTERNAL_URL` | when OIDC is set | — | Publicly reachable base URL of this service (e.g. `https://assets.example.com`); OIDC callback URLs (`/auth/callback`, `/auth/post-logout`) are derived from this |\n| `OIDC_ISSUER_URL` | no | — | Pocket-ID base URL (e.g. `https://id.example.com`) |\n| `OIDC_CLIENT_ID` | no | — | OIDC client ID (from `cmd/provision-pocketid`) |\n| `OIDC_CLIENT_SECRET` | no | — | OIDC client secret (from `cmd/provision-pocketid`) |\n\n\u003e **Note:** Setting only some of the three `OIDC_*` variables is a configuration error — set all three or none.\n\n### Rate Limiting\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `RATE_LIMIT_MAX_GROUPS` | no | `10` | Max new asset groups per IP within the window (0 = disabled) |\n| `RATE_LIMIT_WINDOW` | no | `24h` | Sliding window for per-IP group creation rate limit |\n| `HTTP_RATE_LIMIT_RATE` | no | `20` | Requests per second per IP (0 = disabled) |\n| `HTTP_RATE_LIMIT_BURST` | no | `40` | Max burst size for per-IP token bucket |\n| `HTTP_RATE_LIMIT_CLEANUP` | no | `10m` | How long idle IP entries are kept before eviction |\n\n### Pocket-ID Provisioning (one-time setup)\n\nRun `cmd/provision-pocketid` once to create the OIDC client, admin group, admin user, and obtain the client credentials. Add the output `OIDC_CLIENT_ID` and `OIDC_CLIENT_SECRET` to your environment before starting the service.\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `POCKET_ID_STATIC_API_KEY` | **yes** | — | Pocket-ID admin API key |\n| `POCKET_ID_ADMIN_EMAIL` | no | `admin@example.com` | Email for the initial admin user in Pocket-ID |\n| `POCKET_ID_CLIENT_NAME` | no | `Teeworlds Asset Database` | Display name for the OIDC client in Pocket-ID |\n\n\u003e **Note:** `cmd/provision-pocketid` also reads `OIDC_ISSUER_URL` and `EXTERNAL_URL` from the server configuration above.\n\n## CLI Commands\n\n### Server\n\nStart the server (all configuration via environment variables):\n\n```bash\n# Source dev config and run\nset -a \u0026\u0026 . docker/dev.env \u0026\u0026 set +a \u0026\u0026 go run .\n\n# Or build and run the binary\ngo build -o teeworlds-asset-db .\n./teeworlds-asset-db\n```\n\n### Pocket-ID Provisioning (`cmd/provision-pocketid`)\n\nOne-time setup: creates the OIDC client, admin group, and admin user on a Pocket-ID instance.\nOutputs `OIDC_CLIENT_ID` and `OIDC_CLIENT_SECRET`.\n\n```bash\n# Provision and print credentials to stdout\nset -a \u0026\u0026 . docker/dev.env \u0026\u0026 set +a \u0026\u0026 go run ./cmd/provision-pocketid\n\n# Provision and auto-update docker/dev.env with the credentials\nset -a \u0026\u0026 . docker/dev.env \u0026\u0026 set +a \u0026\u0026 go run ./cmd/provision-pocketid -env-file docker/dev.env\n\n# Shorthand via Make\nmake pocketid-provision\n```\n\n| Flag | Default | Description |\n|---|---|---|\n| `-env-file` | — | Path to `.env` file to update with OIDC credentials (omit to print to stdout) |\n\n### DDNet Skin Seeder (`cmd/seed-ddnet-skins`)\n\nFetches the DDNet skin database and uploads all skins (including UHD variants) to a running server.\n\n```bash\n# Upload all skins to localhost:8080\ngo run ./cmd/seed-ddnet-skins\n\n# Target a different server\ngo run ./cmd/seed-ddnet-skins -addr http://localhost:9090\n\n# Only community skins\ngo run ./cmd/seed-ddnet-skins -type community\n\n# Only normal (non-community) skins\ngo run ./cmd/seed-ddnet-skins -type normal\n\n# Limit parallel downloads to 4\ngo run ./cmd/seed-ddnet-skins -concurrency 4\n\n# Shorthand via Make\nmake seed-ddnet-skins\n```\n\n| Flag | Default | Description |\n|---|---|---|\n| `-addr` | `http://localhost:8080` | Base URL of the running server |\n| `-type` | _(all)_ | Filter by skin type: `normal`, `community`, or empty for all |\n| `-concurrency` | `8` | Number of parallel download/upload workers |\n\n### Make Targets\n\n```bash\nmake build               # Compile the binary\nmake syntax              # Run go vet + go build\nmake generate            # Regenerate code (oapi-codegen + sqlc)\nmake test                # Run tests (sources docker/dev.env)\nmake db-up               # Start PostgreSQL container\nmake db-down             # Stop PostgreSQL and remove volumes\nmake db-reset            # Full DB reset: stop, wipe data, restart\nmake pocketid-provision  # Provision OIDC client (one-time setup)\nmake seed-ddnet-skins    # Import DDNet skins into running service\n```\n\n### Example Production Config\n\n```env\n# Database\nDB_HOST=\u003cyour-postgres-host\u003e\nDB_PORT=5432\nDB_USER=\u003cyour-db-user\u003e\nDB_PASSWORD=\u003cyour-db-password\u003e\nDB_NAME=\u003cyour-db-name\u003e\nDB_SSLMODE=require\n\n# Server\nADDR=:8080\n\n# Storage\nSTORAGE_PATH=/var/lib/asset-service/data\nTEMP_UPLOAD_PATH=/var/lib/asset-service/tmp\nMAX_STORAGE_SIZE=50GiB\n\n# OIDC (Pocket-ID) — credentials from cmd/provision-pocketid\nOIDC_ISSUER_URL=https://\u003cyour-pocket-id-domain\u003e\nOIDC_CLIENT_ID=\u003cfrom provision output\u003e\nOIDC_CLIENT_SECRET=\u003cfrom provision output\u003e\nEXTERNAL_URL=https://\u003cyour-asset-service-domain\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjxsl13%2Fteeworlds-asset-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjxsl13%2Fteeworlds-asset-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjxsl13%2Fteeworlds-asset-service/lists"}