Skip to content

Repository files navigation

HumbleShare

Self-hosted spare Steam key / gift-link share for a small group of friends. Replaces the Humble Bundle spreadsheet.

Stack: Go (Fiber) + Postgres 16 + SvelteKit 2 (Svelte 5) + Tailwind 4.

Local setup

cp .env.example .env
# set SUPERADMIN_EMAIL and AUTH_JWT_SECRET
docker compose -f docker-compose.dev.yml up --build

Dev compose puts Traefik in front (same host / path split idea as production):

*.localhost resolves to 127.0.0.1 on current browsers / macOS. Traefik routes /api/v1, /health, and /webhooks to the Go API; everything else (including the SvelteKit BFF under /api/...) goes to the web container.

On first boot the API creates a superadmin for SUPERADMIN_EMAIL. If SUPERADMIN_PASSWORD is set (Infisical /api/auth key superadmin_password), the account is created immediately. Otherwise an invite is emailed (Mailpit in dev).

Or run pieces on the host (no Traefik; hit Vite/API ports directly):

docker compose -f docker-compose.dev.yml up -d postgres mailpit
cd api && cp ../.env.example .env
make dev
cd web && npm i && npm run dev

Environment variables

Config precedence: env > CLI > optional TOML > Infisical > defaults.

When Infisical Machine Identity bootstrap env is set, secrets are loaded on startup (API: /api recursively; web: /ui recursively). Failures abort startup. Process env still overrides Infisical for operational tweaks.

Infisical paths (folder → key → env)

PathKeyEnv
/api/dburlDATABASE_URL
/api/dbca_certPEM; written to a temp file and PGSSLROOTCERT is set if unset
/api/authjwt_secretAUTH_JWT_SECRET
/api/authsuperadmin_emailSUPERADMIN_EMAIL
/api/authsuperadmin_passwordSUPERADMIN_PASSWORD
/api/auth/superadmin_passwordpasswordSUPERADMIN_PASSWORD
/api/authcookie_secureCOOKIE_SECURE
/api/authsession_max_age_secondsSESSION_MAX_AGE_SECONDS
/api/authpublic_web_base_urlPUBLIC_WEB_BASE_URL
/api/mailgunapi_key, domain, from, api_baseMAILGUN_*
/api/auth/googleclient_id, client_secretGOOGLE_OAUTH_*
/api/auth/discordclient_id, client_secretDISCORD_OAUTH_*
/api/auth/steamapi_keySTEAM_API_KEY
/api/auth/turnstilesite_key, secret_keyTURNSTILE_SITE_KEY / TURNSTILE_SECRET_KEY
/api/r2account_id, access_key_id, secret_access_key, bucket, public_base_url, user_assets_bucket, user_assets_public_base_urlR2_* (static + user-assets buckets)
/api/steamclaim_rate_limit_per_minute / metadata_cache_ttl_seconds / rate_per_secondclaim / Steam knobs
/api/sentrydsn, environment, release, traces_sample_rateSENTRY_* (API)
/ui/apiinternal_urlAPI_INTERNAL_URL
/ui/authcookie_secureCOOKIE_SECURE
/ui/sentrydsn, server_dsn, environment, release, traces_sample_ratePUBLIC_SENTRY_DSN / SENTRY_* (web)

Operational knobs such as HTTP_ADDR and CORS_ORIGINS stay on env/TOML.

Bootstrap + common env

VariableServiceDescription
DATABASE_URLAPIPostgres connection string
AUTH_JWT_SECRETAPISigns 2FA pending / invite approve / OAuth state JWTs
SUPERADMIN_EMAILAPIFirst superadmin email
SUPERADMIN_PASSWORDAPIOptional; creates that account on boot (skips invite)
MAILGUN_API_KEY / MAILGUN_DOMAINAPIProduction email (ignored when SMTP_HOST is set)
SMTP_HOST / SMTP_PORT / SMTP_FROMAPILocal SMTP (Mailpit in docker-compose.dev.yml)
PUBLIC_WEB_BASE_URLAPIBrowser origin for invite/OAuth links
API_INTERNAL_URLWebGo API base URL for the BFF
SENTRY_DSNAPI (+ optional web server)Sentry DSN; empty disables the SDK
PUBLIC_SENTRY_DSNWebBrowser Sentry DSN (same project DSN is fine)
SENTRY_ENVIRONMENT / SENTRY_RELEASEAPI + WebEnvironment and release tags
COOKIE_SECUREAPI + Webtrue in production HTTPS
SESSION_MAX_AGE_SECONDSAPISession cookie max-age (default 30d)
R2_ACCOUNT_ID / R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEYAPIShared R2 credentials
R2_BUCKET / R2_PUBLIC_BASE_URLAPIStatic assets bucket + public CDN (https://assets.humbleshare.com)
R2_USER_ASSETS_BUCKET / R2_USER_ASSETS_PUBLIC_BASE_URLAPIUser uploads (avatars, banners) + public CDN (https://user-assets.humbleshare.com)

Auth

  1. Visitors submit Request access with an email.
  2. Admins (and SUPERADMIN_EMAIL) get a Mailgun notification; approve in Admin or via the email link.
  3. The requester gets an invite email, sets a password, and signs in.
  4. Optional: enable TOTP 2FA and link Google / Discord / Steam from Settings.
  5. Superadmin can promote other users to admin in the portal.

Sessions use an opaque humbleshare_session cookie (hashed at rest). The SvelteKit BFF proxies /api/* so the browser never talks to Go with secrets in client JS.

Humble session cookie

For Admin → Import:

  1. Log into humblebundle.com in a browser.
  2. DevTools → Application/Storage → Cookies → copy _simpleauth_sess.
  3. Paste into the import form: Humble gift links (gift?key=…) and/or order gamekeys (downloads?key=…). Steam CD keys go in Bulk paste.
  4. The cookie is sent only for that request and is never written to the database or logs.

Production

Root docker-compose.yml is the Traefik/Infisical production stack (GHCR images on an external shared-network). Build and push humbleshare-api / humbleshare-web via CI; prefer Infisical for secrets (process env remains a valid override).

Dev Traefik mirrors production router names (humbleshare-api / humbleshare-web) and priorities. Local uses plain HTTP on :80 and PathPrefix(/api/v1) so OAuth callbacks hit Go while the BFF keeps /api/*. Production labels still use the broader PathPrefix(/api) (TLS + Let's Encrypt on the external Traefik).

Design notes

  • Claiming is a single conditional UPDATE … WHERE status = 'available' (see ClaimOldestAvailable). Concurrent losers get 409.
  • Public game list never includes code_value. The key appears only after a successful claim (and on admin inventory).
  • Game identity comes from Humble titles or manual entry; Steam metadata is resolved from the title, never from the opaque key.
  • Claims store claimed_by_user_id plus a denormalized display name.

About

Svelte/Go web app for sharing Steam Gift Codes obtained via Humble Bundle Choice Membership

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages