127 lines
4.1 KiB
Markdown
127 lines
4.1 KiB
Markdown
# Deployment
|
|
|
|
## Local Development
|
|
|
|
1. Copy the example environment file and edit it:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
2. Start PostgreSQL (e.g. via Docker):
|
|
|
|
```bash
|
|
docker run -d --name postgres \
|
|
-e POSTGRES_USER=user -e POSTGRES_PASSWORD=password -e POSTGRES_DB=forgejo_tickets \
|
|
-p 5432:5432 postgres:16-alpine
|
|
```
|
|
|
|
3. Run the application:
|
|
|
|
```bash
|
|
make run
|
|
```
|
|
|
|
This runs `go run ./cmd/server`, which auto-migrates the database on startup.
|
|
|
|
4. For CSS development, run the Tailwind watcher in a separate terminal:
|
|
|
|
```bash
|
|
make tailwind-watch
|
|
```
|
|
|
|
The public UI is at `http://localhost:8080` and the admin panel at `http://localhost:8081`.
|
|
|
|
## Makefile Targets
|
|
|
|
| Target | Description |
|
|
|--------|-------------|
|
|
| `build` | Compiles Tailwind CSS then builds the Go binary (`forgejo-tickets`) |
|
|
| `run` | Runs the server directly with `go run` (no prior build needed) |
|
|
| `test` | Runs all Go tests (`go test ./...`) |
|
|
| `tailwind` | Compiles Tailwind CSS from `web/static/css/input.css` to `web/static/css/output.css` (minified) |
|
|
| `tailwind-watch` | Runs Tailwind in watch mode for development |
|
|
| `docker` | Builds the Docker image tagged with the git SHA and `latest` |
|
|
| `docker-push` | Builds and pushes the Docker image to the registry |
|
|
| `clean` | Removes the compiled binary and generated CSS |
|
|
|
|
## Docker
|
|
|
|
The Dockerfile uses a 3-stage build:
|
|
|
|
1. **Node/Tailwind stage** (`node:22-alpine`) — Compiles Tailwind CSS.
|
|
2. **Go build stage** (`golang:1.23-alpine`) — Downloads dependencies and compiles the Go binary with `CGO_ENABLED=0`.
|
|
3. **Runtime stage** (`alpine:3.20`) — Minimal image with just the binary, templates, and static assets. Exposes ports 8080 and 8081.
|
|
|
|
Build and run:
|
|
|
|
```bash
|
|
make docker
|
|
docker run --env-file .env -p 8080:8080 -p 8081:8081 registry.ts.mattnite.net/forgejo-tickets:latest
|
|
```
|
|
|
|
## Nomad
|
|
|
|
The production deployment uses Nomad with the job spec at `infra/tickets/tickets.hcl`.
|
|
|
|
### Job Structure
|
|
|
|
- **Job**: `tickets` (service type, datacenter `dc1`)
|
|
- **Group**: `tickets` (count 1)
|
|
- Two ports on the Tailscale host network: `http` (public) and `admin`
|
|
- **Task**: `server` (Docker driver)
|
|
|
|
### Environment Injection
|
|
|
|
Secrets are stored as Nomad variables at the path `nomad/jobs/tickets` and injected via a template block:
|
|
|
|
- `DATABASE_URL`, `SESSION_SECRET`
|
|
- `FORGEJO_URL`, `FORGEJO_API_TOKEN`
|
|
- `POSTMARK_SERVER_TOKEN`
|
|
- `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`
|
|
- `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`
|
|
- `APPLE_CLIENT_ID`, `APPLE_TEAM_ID`, `APPLE_KEY_ID`
|
|
|
|
Non-secret configuration is set directly in the `env` block:
|
|
|
|
- `PUBLIC_ADDR` and `ADMIN_ADDR` use Nomad's dynamic port variables
|
|
- `BASE_URL` is the public-facing URL
|
|
- `POSTMARK_FROM_EMAIL` is the sender address
|
|
- `TAILSCALE_ALLOWED_USERS` controls admin access
|
|
|
|
### Traefik Routing
|
|
|
|
Two Nomad services are registered with Traefik tags:
|
|
|
|
| Service | Host | Purpose |
|
|
|---------|------|---------|
|
|
| `tickets` | `tickets.ts.mattnite.net` | Public UI (internal Traefik, TLS via Let's Encrypt) |
|
|
| `tickets-admin` | `tickets-admin.ts.mattnite.net` | Admin panel (internal Traefik, TLS via Let's Encrypt) |
|
|
|
|
Both use the `websecure` entrypoint with automatic TLS certificate resolution.
|
|
|
|
### Health Checks
|
|
|
|
The public service registers an HTTP health check at `GET /health` with a 10-second interval and 30-second timeout.
|
|
|
|
### Logging
|
|
|
|
Docker logging is configured to ship to Loki at `loki.ts.mattnite.net` with job name and allocation ID as labels.
|
|
|
|
### Resources
|
|
|
|
- CPU: 200 MHz
|
|
- Memory: 256 MB
|
|
|
|
## Database
|
|
|
|
The application uses PostgreSQL with GORM as the ORM. On startup, `database.RunMigrations()` calls `models.AutoMigrate()` which:
|
|
|
|
1. Creates PostgreSQL enum types (`ticket_status`, `token_type`) if they don't exist
|
|
2. Auto-migrates all model tables (User, OAuthAccount, Session, Repo, Ticket, TicketComment, EmailToken)
|
|
3. Creates composite and partial unique indexes
|
|
|
|
No separate migration tool is needed — the schema is managed entirely through GORM auto-migration.
|
|
|
|
See [Configuration](./configuration.md) for all environment variables and [Forgejo Integration](./forgejo-integration.md) for webhook URL setup.
|