H1K0 a0a987d561
deploy / deploy (push) Failing after 1m35s
ci(project): run the test suite before deploying
Gate the deploy on tests. After pulling master and before building the
image, run the full Go suite (incl. integration tests, against an ephemeral
Postgres) and the frontend type-check + build. Everything runs inside
throwaway toolchain containers, so the host still needs only docker and a
red build never reaches production. Update DEPLOY.md to reflect the gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 10:57:16 +03:00
2026-07-03 10:13:56 +03:00
2026-07-03 10:13:56 +03:00

Tanabata File Manager

A multi-user, tag-based web file manager for images and video. Go + Gin backend (Clean Architecture, pgx, goose migrations), SvelteKit SPA frontend, PostgreSQL, JWT auth — shipped as a single Docker image that serves both the API and the built SPA on one port.

Documentation

Quick start

cp .env.example .env        # then edit the secrets (JWT_SECRET, ADMIN_PASSWORD, …)
docker compose up -d --build

By default this runs the app plus a bundled PostgreSQL container (COMPOSE_PROFILES=with-db). To point at a Postgres already on the host, set COMPOSE_PROFILES= empty and aim DATABASE_URL at host.docker.internal. See .env.example for the full matrix.

The app is published on 127.0.0.1 only and expects a reverse proxy in front (see below). The default port is 42776 — the sum of the Unicode code points of 七夕.

Reverse proxy (nginx)

The container publishes its port on loopback (127.0.0.1:${APP_PORT}:42776 in docker-compose.yml), so a reverse proxy on the host terminates TLS and forwards to it. Three settings matter for this app:

  1. client_max_body_size — uploads go up to MAX_UPLOAD_BYTES (500 MiB by default). nginx caps request bodies at 1 MiB out of the box, so without this every large upload fails with 413.
  2. Forwarded headers — the app trusts X-Forwarded-For only from the hops in TRUSTED_PROXIES (default: loopback + Docker bridge ranges) and keys its login/refresh rate limiter on the resulting client IP. If the proxy doesn't send the header, every request looks like it comes from the proxy and shares one rate-limit bucket.
  3. Streaming for big media — turning request/response buffering off lets large uploads stream straight to the app and lets video range-seeks work without nginx spooling whole files to disk first.
server {
    listen 443 ssl;
    server_name tanabata.example.com;

    # ssl_certificate / ssl_certificate_key ... (e.g. from certbot)

    # Match MAX_UPLOAD_BYTES (500 MiB default); nginx defaults to 1m → 413.
    client_max_body_size 512m;

    location / {
        proxy_pass http://127.0.0.1:42776;   # APP_PORT
        proxy_http_version 1.1;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Stream large uploads/downloads instead of buffering to disk; keeps
        # video range-seek responsive. Scope these to file/preview locations
        # instead if you'd rather keep buffering for small JSON responses.
        proxy_request_buffering off;
        proxy_buffering         off;
        proxy_read_timeout      300s;
        proxy_send_timeout      300s;
    }
}

If you run the app without a proxy and want it reachable on the LAN, drop the 127.0.0.1: prefix from the ports line in docker-compose.yml and adjust TRUSTED_PROXIES accordingly.

Development

# Backend
cd backend
go run ./cmd/server          # dev server
go test ./...                # all tests

# Frontend
cd frontend
npm run dev                  # Vite dev server
npm run build                # production build
npm run generate:types       # regenerate API types from openapi.yaml

License

Licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE for the full text.

S
Description
3.0.0 Latest
2026-07-07 11:17:52 +03:00
Languages
Go 48.9%
Svelte 39.9%
TypeScript 8.7%
PLpgSQL 1.3%
Dockerfile 0.5%
Other 0.7%