Files
porthole/README.md
OpenCode Test e1a64aa092 Initial commit
2025-12-24 10:50:10 -08:00

222 lines
6.5 KiB
Markdown

# porthole
Porthole: timeline media library (Next.js web + worker), backed by Postgres/Redis/MinIO.
## How to try it
- Create a values file (example minimal):
- set `secrets.postgres.password`
- set `secrets.minio.accessKeyId` + `secrets.minio.secretAccessKey`
- set `images.web.repository/tag` and `images.worker.repository/tag`
- set `global.tailscale.tailnetFQDN` (recommended), or set `app.minio.publicEndpointTs` (must be `https://minio.<tailnet-fqdn>`)
- Render locally: `helm template porthole helm/tline -f your-values.yaml --namespace porthole`
- Install (to `porthole` namespace): `helm upgrade --install porthole helm/tline -f your-values.yaml --namespace porthole`
## ArgoCD
A ready-to-apply ArgoCD `Application` manifest is included at `argocd/tline-application.yaml` (it deploys the Helm release name `porthole`).
Reference example (deploys into the `porthole` namespace; the Helm chart itself does not hardcode a namespace):
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: porthole
namespace: argocd
spec:
project: default
source:
repoURL: git@gitea-gitea-ssh.taildb3494.ts.net:will/porthole.git
targetRevision: main
path: helm/tline
helm:
releaseName: porthole
valueFiles:
- values.yaml
# - values-porthole.yaml
# Alternative to valueFiles: set a few values inline.
# parameters:
# - name: global.tailscale.tailnetFQDN
# value: tailxyz.ts.net
# - name: images.web.repository
# value: registry.lan:5000/tline-web
# - name: images.web.tag
# value: dev
destination:
server: https://kubernetes.default.svc
namespace: porthole
# Optional: if you use image pull secrets, you can set them via values files
# or inline Helm parameters.
# source:
# helm:
# parameters:
# - name: imagePullSecrets[0]
# value: my-registry-secret
# - name: registrySecret.create
# value: "true"
# - name: registrySecret.server
# value: registry.lan:5000
# - name: registrySecret.username
# value: your-user
# - name: registrySecret.password
# value: your-pass
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=false
- ApplyOutOfSyncOnly=true
```
## Notes
- MinIO bucket creation: the app does not auto-create buckets. You can either create the bucket yourself, or enable the Helm hook job:
- `jobs.ensureBucket.enabled=true`
- Staging cleanup: disabled by default; enable with:
- `cronjobs.cleanupStaging.enabled=true`
## Build + push images (multi-arch)
This repo is a Bun monorepo, but container builds use Docker Buildx.
- Assumptions:
- You have an **in-cluster registry** reachable over **insecure HTTP** (example: `registry.lan:5000`).
- Your Docker daemon is configured to allow that registry as an insecure registry.
- Create/use a buildx builder (one-time):
- `docker buildx create --name tline --use`
- Build + push **web** (Next standalone):
- `REGISTRY=registry.lan:5000 TAG=dev`
- `docker buildx build --platform linux/amd64,linux/arm64 -f apps/web/Dockerfile -t "$REGISTRY/tline-web:$TAG" --push .`
- Notes:
- The Dockerfile uses `bun install --frozen-lockfile` and copies all workspace `package.json` files first to keep Bun from mutating `bun.lock`.
- Runtime entrypoint comes from Next standalone output (the image runs `node app/apps/web/server.js`).
- Build + push **worker** (includes `ffmpeg` + `exiftool`):
- `REGISTRY=registry.lan:5000 TAG=dev`
- `docker buildx build --platform linux/amd64,linux/arm64 -f apps/worker/Dockerfile -t "$REGISTRY/tline-worker:$TAG" --push .`
- Notes:
- The Dockerfile uses `bun install --frozen-lockfile --production` and also copies all workspace `package.json` files first for stable `workspace:*` resolution.
- Then set Helm values:
- `images.web.repository: registry.lan:5000/tline-web`
- `images.web.tag: dev`
- `images.worker.repository: registry.lan:5000/tline-worker`
- `images.worker.tag: dev`
### Private registry auth (optional)
If your registry requires auth, you can either:
- reference an existing Secret via `imagePullSecrets`, or
- have the chart create a `kubernetes.io/dockerconfigjson` Secret via `registrySecret`.
Example values:
```yaml
# Option A: reference an existing secret
imagePullSecrets:
- my-registry-secret
# Option B: create a secret from values (stores creds in values)
registrySecret:
create: true
server: "registry.lan:5000"
username: "your-user"
password: "your-pass"
email: "you@example.com"
```
## MinIO exposure (Tailscale)
MinIO S3 URLs must be signed against `https://minio.<tailnet-fqdn>`.
You can expose MinIO over tailnet either via:
- **Tailscale Ingress** (default), or
- **Tailscale LoadBalancer Service** (often more reliable for streaming/Range)
Example values (LoadBalancer for S3 + console):
```yaml
global:
tailscale:
tailnetFQDN: "tailxyz.ts.net"
minio:
tailscaleServiceS3:
enabled: true
hostnameLabel: minio
tailscaleServiceConsole:
enabled: true
hostnameLabel: minio-console
# Optional: if you prefer explicit override instead of deriving from tailnetFQDN
# app:
# minio:
# publicEndpointTs: "https://minio.tailxyz.ts.net"
```
## Example values (Pi cluster)
This chart assumes you label nodes like:
- Pi 5 nodes: `node-class=compute`
- Pi 3 node: `node-class=tiny`
The default scheduling in `helm/tline/values.yaml` pins heavy pods to `node-class=compute`.
Example `values.yaml` you can start from:
```yaml
secrets:
postgres:
password: "change-me"
minio:
accessKeyId: "minioadmin"
secretAccessKey: "minioadmin"
images:
web:
repository: registry.lan:5000/tline-web
tag: dev
worker:
repository: registry.lan:5000/tline-worker
tag: dev
global:
tailscale:
tailnetFQDN: "tailxyz.ts.net"
# Optional, but common for Pi clusters (Longhorn default shown as example)
# global:
# storageClass: longhorn
minio:
# Prefer LB Services for streaming/Range reliability
tailscaleServiceS3:
enabled: true
hostnameLabel: minio
tailscaleServiceConsole:
enabled: true
hostnameLabel: minio-console
jobs:
ensureBucket:
enabled: true
# Optional staging cleanup (never touches originals/**)
# cronjobs:
# cleanupStaging:
# enabled: true
# olderThanDays: 7
```
## Quick checks
- Range support through ingress (expect `206`):
- `curl -sS -D- -H 'Range: bytes=0-1023' "$(curl -sS https://app.<tailnet-fqdn>/api/assets/<assetId>/url?variant=original | jq -r .url)" -o /dev/null`