update README for current stack state
Reflects domain change (homelab.fhirworx.io), network segmentation, centralized CSS theming, Go template routing, and favicon injection.
This commit is contained in:
495
README.md
495
README.md
@@ -1,88 +1,117 @@
|
||||
# Homelab Stack
|
||||
|
||||
Self-hosted infrastructure stack with GPU support, S3-compatible storage, Git server with container registry, CI/CD, data lakehouse, observability, and research tools.
|
||||
Self-hosted infrastructure stack with GPU support, S3-compatible storage, Git server with container registry, CI/CD, data lakehouse, observability, and research tools. All services share a unified retro "throwback" theme injected via Traefik middleware.
|
||||
|
||||
## Services
|
||||
|
||||
All services are accessible via Traefik reverse proxy on port 80.
|
||||
All services are accessible via Traefik reverse proxy at `*.homelab.fhirworx.io`.
|
||||
|
||||
| Service | URL | Description |
|
||||
|---------|-----|-------------|
|
||||
| Dashboard | http://home.fhirworx.io | Homelab command center |
|
||||
| Traefik | http://traefik.home.fhirworx.io | Reverse proxy dashboard |
|
||||
| Gitea | http://gitea.home.fhirworx.io | Git server with LFS + container registry |
|
||||
| Woodpecker CI | http://ci.home.fhirworx.io | CI/CD pipelines |
|
||||
| RustFS Console | http://minio.home.fhirworx.io | S3 storage management UI |
|
||||
| RustFS S3 API | http://s3.home.fhirworx.io | S3-compatible object storage |
|
||||
| Notebooks | http://notebooks.home.fhirworx.io | GPU-accelerated Marimo notebooks |
|
||||
| Zotero | http://zotero.home.fhirworx.io | Reference manager with VNC |
|
||||
| Nessie | http://nessie.home.fhirworx.io | Git-like data catalog for Iceberg |
|
||||
| Polaris | http://polaris.home.fhirworx.io | Apache Iceberg catalog with governance |
|
||||
| Trino | http://trino.home.fhirworx.io | Distributed SQL query engine |
|
||||
| Grafana | http://grafana.home.fhirworx.io | Observability dashboards |
|
||||
| Prometheus | http://prometheus.home.fhirworx.io | Metrics collection |
|
||||
| Jaeger | http://jaeger.home.fhirworx.io | Distributed tracing |
|
||||
| Loki | http://loki.home.fhirworx.io | Log aggregation |
|
||||
| PostgreSQL | localhost:5432 | Database (Bitnami hardened) |
|
||||
| Dashboard | `homelab.fhirworx.io` | Homelab command center |
|
||||
| Traefik | `traefik.homelab.fhirworx.io` | Reverse proxy dashboard |
|
||||
| Gitea | `gitea.homelab.fhirworx.io` | Git server with LFS + container registry |
|
||||
| Woodpecker CI | `ci.homelab.fhirworx.io` | CI/CD pipelines |
|
||||
| RustFS Console | `s3console.homelab.fhirworx.io` | S3 storage management UI |
|
||||
| RustFS S3 API | `s3.homelab.fhirworx.io` | S3-compatible object storage |
|
||||
| Notebooks | `notebooks.homelab.fhirworx.io` | GPU-accelerated Marimo notebooks |
|
||||
| Zotero | `zotero.homelab.fhirworx.io` | Reference manager (KasmVNC) |
|
||||
| Nessie | `nessie.homelab.fhirworx.io` | Git-like data catalog for Iceberg |
|
||||
| Polaris | `polaris.homelab.fhirworx.io` | Apache Iceberg catalog with governance |
|
||||
| Trino | `trino.homelab.fhirworx.io` | Distributed SQL query engine |
|
||||
| Grafana | `grafana.homelab.fhirworx.io` | Observability dashboards |
|
||||
| Prometheus | `prometheus.homelab.fhirworx.io` | Metrics collection |
|
||||
| Jaeger | `jaeger.homelab.fhirworx.io` | Distributed tracing |
|
||||
| Loki | `loki.homelab.fhirworx.io` | Log aggregation |
|
||||
|
||||
## Architecture
|
||||
|
||||
### Network Segmentation
|
||||
|
||||
The stack uses five isolated Docker networks. Services only join the networks they need.
|
||||
|
||||
| Network | Type | Purpose |
|
||||
|---------|------|---------|
|
||||
| `gateway` | external | Traefik + public-facing services |
|
||||
| `storage` | internal | PostgreSQL + RustFS (S3) |
|
||||
| `data` | internal | Lakehouse (Nessie, Trino, Polaris) |
|
||||
| `observability` | internal | Grafana, Prometheus, Jaeger, Loki |
|
||||
| `ci` | external | Woodpecker + Gitea + RustFS |
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ intrastack network │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Traefik :80/:443 │ │
|
||||
│ │ Reverse Proxy + Load Balancer + OTEL Tracing │ │
|
||||
│ └───────────────────────────────┬─────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────────┼─────────────────────────────┐ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Gitea │ │Woodpecker│ │ Notebooks│ │ Zotero │ │ Dashboard│ │
|
||||
│ │ :3000 │ │ :8000 │ │ :2718 │ │ :8080 │ │ :80 │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────────┘ └──────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └─────────────┼─────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ PostgreSQL :5432 │ │
|
||||
│ │ (gitea, woodpecker, nessie) │ │
|
||||
│ └──────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Data Lakehouse │ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────────────────┐ │ │
|
||||
│ │ │ Trino │────▶│ Nessie │────▶│ RustFS │ │ │
|
||||
│ │ │ :8080 │ │ :19120 │ │ S3 :9000 | Console :9001 │ │ │
|
||||
│ │ └──────────┘ └──────────┘ │ Buckets: gitea, lakehouse │ │ │
|
||||
│ │ │ └──────────────────────────────┘ │ │
|
||||
│ │ │ ┌──────────┐ ▲ │ │
|
||||
│ │ └──────────▶│ Polaris │──────────────┘ │ │
|
||||
│ │ │ :8181 │ Governance + RBAC │ │
|
||||
│ │ └──────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Observability Stack │ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||
│ │ │ Grafana │────▶│Prometheus│ │ Jaeger │◀────│ Loki │ │ │
|
||||
│ │ │ :3000 │ │ :9090 │ │ :16686 │ │ :3100 │ │ │
|
||||
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
|
||||
│ │ │ ▲ ▲ │ │
|
||||
│ │ └──────── Dashboards ───────────────┴───────────────┘ │ │
|
||||
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
┌──────────────────────────┐
|
||||
│ Traefik :80/:443 │
|
||||
│ Reverse Proxy + Tracing │
|
||||
└────────────┬─────────────┘
|
||||
gateway network │
|
||||
┌───────────┬──────────┬────────┼────────┬───────────┐
|
||||
▼ ▼ ▼ ▼ ▼ ▼
|
||||
┌────────┐ ┌─────────┐ ┌────────┐ ┌──────┐ ┌──────┐ ┌────────┐
|
||||
│ Gitea │ │Woodpeck.│ │Notebook│ │Zotero│ │Dash- │ │Grafana │
|
||||
│ :3000 │ │ :8000 │ │ :2718 │ │:8080 │ │board │ │ :3000 │
|
||||
└───┬────┘ └────┬────┘ └────────┘ └──────┘ └──────┘ └───┬────┘
|
||||
│ │ │
|
||||
│ ci network observability network
|
||||
│ │ ┌───────┬───────┬───┘
|
||||
│ │ ▼ ▼ ▼
|
||||
│ │ ┌──────┐ ┌───────┐ ┌──────┐
|
||||
│ │ │Jaeger│ │Promet.│ │ Loki │
|
||||
│ │ │:16686│ │ :9090 │ │:3100 │
|
||||
│ │ └──────┘ └───────┘ └──────┘
|
||||
│ │
|
||||
└─────┬─────┘ data network
|
||||
│ ┌────────┬──────────┐
|
||||
storage network ▼ ▼ ▼
|
||||
┌─────┼────┐ ┌──────┐ ┌──────┐ ┌───────┐
|
||||
▼ ▼ │ │Nessie│ │Trino │ │Polaris│
|
||||
┌────────┐ ┌────┴──┐ │:19120│ │:8080 │ │ :8181 │
|
||||
│Postgres│ │RustFS │ └──────┘ └──────┘ └───────┘
|
||||
│ :5432 │ │:9000/1│
|
||||
└────────┘ └───────┘
|
||||
```
|
||||
|
||||
### Traefik Routing
|
||||
|
||||
Routes are defined in `traefik/dynamic/services.yml` using Go templates with Sprig functions. The `$reef` dict is the single source of truth — each entry generates a router, service, and middleware chain:
|
||||
|
||||
```
|
||||
"gitea" (dict "port" "3000" "theme" true "mw" "secure-headers")
|
||||
```
|
||||
|
||||
- **`theme: true`** applies the `inject-throwback` middleware (injects favicon + CSS via HTML rewrite)
|
||||
- **`theme: false`** skips injection (API-only services like Nessie, Polaris, Loki)
|
||||
- **`mw`** is a comma-separated list of additional middlewares (`secure-headers`, `local-only`, `infra-headers`)
|
||||
- **`subdomain`** overrides the FQDN prefix (e.g. `woodpecker-server` → `ci.homelab.fhirworx.io`)
|
||||
|
||||
To add a new service, add one line to the `$reef` dict and a service block to `compose.yml`.
|
||||
|
||||
### Throwback Theme
|
||||
|
||||
All HTML-serving services get a unified retro theme via the `inject-throwback` Traefik middleware, which uses the [rewrite-body](https://github.com/packruler/rewrite-body) plugin to inject `<link>` tags before `</head>`:
|
||||
|
||||
- **`favicon.svg`** — Pixel-art "H" favicon served from the dashboard container
|
||||
- **`throwback.css`** — Global CSS overrides (colors, fonts, borders)
|
||||
- **`inject.css`** — Served as `throwback.css`, hides service-specific logos and applies the palette
|
||||
|
||||
Per-service CSS files handle service-specific overrides:
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `css/inject.css` | Global throwback palette + logo hiding (served as `throwback.css`) |
|
||||
| `css/dashboard.css` | Dashboard tile grid layout |
|
||||
| `css/gitea.css` | Gitea theme (registered as `throwback` in Gitea UI settings) |
|
||||
| `css/woodpecker.css` | Woodpecker custom CSS (via `WOODPECKER_CUSTOM_CSS_FILE`) |
|
||||
| `css/marimo.css` | Marimo notebook theme |
|
||||
| `css/favicon.svg` | Canonical SVG favicon |
|
||||
| `css/logo.svg` | Navbar logo for Gitea |
|
||||
|
||||
Grafana favicons are replaced via volume mounts over the default Grafana icons.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker with rootless mode
|
||||
- NVIDIA GPU with container toolkit
|
||||
- iptables for port 80 redirect (rootless Docker can't bind privileged ports)
|
||||
- Domain pointing to your server (or `/etc/hosts` entries)
|
||||
|
||||
Fix for rootless NVIDIA containers:
|
||||
```bash
|
||||
@@ -93,36 +122,23 @@ sudo sed -i 's/#no-cgroups = false/no-cgroups = true/' /etc/nvidia-container-run
|
||||
|
||||
Add to `/etc/hosts` (replace IP with your server's LAN IP):
|
||||
|
||||
```bash
|
||||
# Homelab Stack
|
||||
192.168.1.192 home.fhirworx.io
|
||||
192.168.1.192 dashboard.home.fhirworx.io
|
||||
192.168.1.192 traefik.home.fhirworx.io
|
||||
192.168.1.192 gitea.home.fhirworx.io
|
||||
192.168.1.192 ci.home.fhirworx.io
|
||||
192.168.1.192 notebooks.home.fhirworx.io
|
||||
192.168.1.192 zotero.home.fhirworx.io
|
||||
192.168.1.192 nessie.home.fhirworx.io
|
||||
192.168.1.192 trino.home.fhirworx.io
|
||||
192.168.1.192 minio.home.fhirworx.io
|
||||
192.168.1.192 s3.home.fhirworx.io
|
||||
192.168.1.192 grafana.home.fhirworx.io
|
||||
192.168.1.192 prometheus.home.fhirworx.io
|
||||
192.168.1.192 jaeger.home.fhirworx.io
|
||||
192.168.1.192 loki.home.fhirworx.io
|
||||
192.168.1.192 polaris.home.fhirworx.io
|
||||
```
|
||||
|
||||
## Port 80 Redirect
|
||||
|
||||
Rootless Docker cannot bind to privileged ports. Use iptables to redirect port 80 to Traefik's port 8880:
|
||||
|
||||
```bash
|
||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8880
|
||||
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -o lo -j REDIRECT --to-port 8880
|
||||
|
||||
# Persist across reboots
|
||||
sudo sh -c 'iptables-save > /etc/iptables/rules.v4'
|
||||
192.168.1.192 homelab.fhirworx.io
|
||||
192.168.1.192 dashboard.homelab.fhirworx.io
|
||||
192.168.1.192 traefik.homelab.fhirworx.io
|
||||
192.168.1.192 gitea.homelab.fhirworx.io
|
||||
192.168.1.192 ci.homelab.fhirworx.io
|
||||
192.168.1.192 notebooks.homelab.fhirworx.io
|
||||
192.168.1.192 zotero.homelab.fhirworx.io
|
||||
192.168.1.192 nessie.homelab.fhirworx.io
|
||||
192.168.1.192 trino.homelab.fhirworx.io
|
||||
192.168.1.192 s3console.homelab.fhirworx.io
|
||||
192.168.1.192 s3.homelab.fhirworx.io
|
||||
192.168.1.192 grafana.homelab.fhirworx.io
|
||||
192.168.1.192 prometheus.homelab.fhirworx.io
|
||||
192.168.1.192 jaeger.homelab.fhirworx.io
|
||||
192.168.1.192 loki.homelab.fhirworx.io
|
||||
192.168.1.192 polaris.homelab.fhirworx.io
|
||||
```
|
||||
|
||||
## Setup
|
||||
@@ -132,6 +148,9 @@ sudo sh -c 'iptables-save > /etc/iptables/rules.v4'
|
||||
Create `.env` file:
|
||||
|
||||
```bash
|
||||
DOMAIN=homelab.fhirworx.io
|
||||
HOST_IP=192.168.1.192
|
||||
|
||||
POSTGRES_PASSWORD=<your_password>
|
||||
RUSTFS_ACCESS_KEY=<your_access_key>
|
||||
RUSTFS_SECRET_KEY=<your_secret_key>
|
||||
@@ -140,9 +159,11 @@ GITEA_S3_SECRET_KEY=<gitea_s3_password>
|
||||
GITEA_DB_PASSWORD=<gitea_db_password>
|
||||
GITEA_TOKEN=<gitea_api_token>
|
||||
WOODPECKER_DB_PASSWORD=<woodpecker_db_password>
|
||||
WOODPECKER_ADMIN=<admin_username>
|
||||
WOODPECKER_AGENT_SECRET=<generated_secret>
|
||||
WOODPECKER_GITEA_CLIENT=<oauth_client_id>
|
||||
WOODPECKER_GITEA_SECRET=<oauth_client_secret>
|
||||
GF_ADMIN_PASSWORD=<grafana_password>
|
||||
|
||||
# Nessie Data Lake
|
||||
NESSIE_DB_PASSWORD=<nessie_db_password>
|
||||
@@ -169,10 +190,10 @@ docker compose up -d postgres rustfs traefik
|
||||
|
||||
### 3. Configure RustFS
|
||||
|
||||
1. Access console at http://minio.home.fhirworx.io
|
||||
2. Create user `git` with S3 credentials matching `GITEA_S3_ACCESS_KEY` and `GITEA_S3_SECRET_KEY`
|
||||
1. Access console at `s3console.homelab.fhirworx.io`
|
||||
2. Create user `git` with S3 credentials matching `.env`
|
||||
3. Create buckets: `gitea`, `gitea-lfs`, `gitea-packages`
|
||||
4. Create user `nessie` with S3 credentials matching `NESSIE_S3_ACCESS_KEY` and `NESSIE_S3_SECRET_KEY`
|
||||
4. Create user `nessie` with S3 credentials matching `.env`
|
||||
5. Create bucket: `lakehouse`
|
||||
6. Apply IAM policies to users (see below)
|
||||
|
||||
@@ -260,27 +281,10 @@ docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREA
|
||||
# Nessie database
|
||||
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE USER nessie WITH PASSWORD '<nessie_db_password>';"
|
||||
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE DATABASE nessie OWNER nessie;"
|
||||
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE nessie TO nessie;"
|
||||
|
||||
# Polaris database
|
||||
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE USER polaris WITH PASSWORD '<polaris_db_password>';"
|
||||
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "CREATE DATABASE polaris OWNER polaris;"
|
||||
docker exec -e PGPASSWORD=<postgres_password> postgres psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE polaris TO polaris;"
|
||||
```
|
||||
|
||||
### Bootstrap Polaris
|
||||
|
||||
After starting Polaris, bootstrap the realm:
|
||||
|
||||
```bash
|
||||
docker run --rm --network stack_intrastack \
|
||||
-e POLARIS_PERSISTENCE_TYPE=relational-jdbc \
|
||||
-e QUARKUS_DATASOURCE_DB_KIND=postgresql \
|
||||
-e QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://postgres:5432/polaris \
|
||||
-e QUARKUS_DATASOURCE_USERNAME=polaris \
|
||||
-e QUARKUS_DATASOURCE_PASSWORD=<polaris_db_password> \
|
||||
apache/polaris-admin-tool:latest \
|
||||
bootstrap -r POLARIS -c POLARIS,root,<polaris_root_secret> -p
|
||||
```
|
||||
|
||||
### 5. Start All Services
|
||||
@@ -291,20 +295,20 @@ docker compose up -d
|
||||
|
||||
### 6. Configure Gitea
|
||||
|
||||
1. Complete initial setup at http://gitea.home.fhirworx.io
|
||||
1. Complete initial setup at `gitea.homelab.fhirworx.io`
|
||||
2. Create organization `homelab`
|
||||
3. Create repository `stack`
|
||||
4. Generate API token for container registry access
|
||||
5. Create OAuth2 application for Woodpecker:
|
||||
- Name: `Woodpecker CI`
|
||||
- Redirect URI: `http://ci.home.fhirworx.io/authorize`
|
||||
- Redirect URI: `http://ci.homelab.fhirworx.io/authorize`
|
||||
- Copy Client ID and Secret to `.env`
|
||||
|
||||
### 7. Configure Woodpecker Secrets
|
||||
|
||||
Add secrets in Woodpecker UI (http://ci.home.fhirworx.io):
|
||||
- `registry_user` - Gitea username
|
||||
- `registry_pass` - Gitea token
|
||||
Add secrets in Woodpecker UI (`ci.homelab.fhirworx.io`):
|
||||
- `registry_user` — Gitea username
|
||||
- `registry_pass` — Gitea token
|
||||
|
||||
### 8. Verify Services
|
||||
|
||||
@@ -313,35 +317,29 @@ Add secrets in Woodpecker UI (http://ci.home.fhirworx.io):
|
||||
curl -s http://localhost:8081/api/http/routers | jq -r '.[].name'
|
||||
|
||||
# Check Nessie
|
||||
curl http://nessie.home.fhirworx.io/api/v2/config
|
||||
curl http://nessie.homelab.fhirworx.io/api/v2/config
|
||||
|
||||
# Check Trino
|
||||
docker exec -it trino trino --execute "SHOW CATALOGS"
|
||||
|
||||
# Check Prometheus targets
|
||||
curl -s http://prometheus.home.fhirworx.io/api/v1/targets | jq '.data.activeTargets[].labels.job'
|
||||
curl -s http://prometheus.homelab.fhirworx.io/api/v1/targets | jq '.data.activeTargets[].labels.job'
|
||||
```
|
||||
|
||||
## Observability
|
||||
|
||||
The stack includes a complete observability solution:
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| **Prometheus** | Metrics collection and storage |
|
||||
| **Grafana** | Visualization and dashboards |
|
||||
| **Jaeger** | Distributed tracing (OTLP collector) |
|
||||
| **Loki** | Log aggregation |
|
||||
| **Promtail** | Log shipper (Docker container logs) |
|
||||
|
||||
### Components
|
||||
Services instrumented with OpenTelemetry tracing:
|
||||
|
||||
| Component | Purpose | Metrics |
|
||||
|-----------|---------|---------|
|
||||
| **Prometheus** | Metrics collection and storage | Scrapes all services |
|
||||
| **Grafana** | Visualization and dashboards | Homelab Overview, Traefik |
|
||||
| **Jaeger** | Distributed tracing | OTLP collector |
|
||||
| **Loki** | Log aggregation | Container logs via Promtail |
|
||||
| **Promtail** | Log shipper | Scrapes Docker container logs |
|
||||
|
||||
### Tracing
|
||||
|
||||
Services instrumented with OpenTelemetry:
|
||||
|
||||
| Service | Tracing |
|
||||
|---------|---------|
|
||||
| Service | Method |
|
||||
|---------|--------|
|
||||
| Traefik | Native OTLP export |
|
||||
| Nessie | Quarkus OTEL |
|
||||
| Polaris | Quarkus OTEL |
|
||||
@@ -349,12 +347,6 @@ Services instrumented with OpenTelemetry:
|
||||
|
||||
All traces flow to Jaeger via OTLP gRPC (port 4317).
|
||||
|
||||
### Dashboards
|
||||
|
||||
Pre-provisioned Grafana dashboards:
|
||||
- **Homelab Overview** - Service health, logs, traces
|
||||
- **Traefik** - Request rates, latencies, errors by router/service
|
||||
|
||||
### Prometheus Targets
|
||||
|
||||
| Job | Endpoint |
|
||||
@@ -369,81 +361,40 @@ Pre-provisioned Grafana dashboards:
|
||||
|
||||
## Data Lakehouse
|
||||
|
||||
The stack includes a complete data lakehouse built on Apache Iceberg with two catalog options:
|
||||
|
||||
### Components
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| **Nessie** | Git-like version control for data (branches, merges, time travel) |
|
||||
| **Polaris** | Apache Iceberg catalog with governance, RBAC, and multi-tenant support |
|
||||
| **Apache Iceberg** | Open table format with ACID transactions, schema evolution |
|
||||
| **Trino** | Distributed SQL query engine with JDBC/ODBC support |
|
||||
| **RustFS** | S3 storage backend (lakehouse, polaris buckets) |
|
||||
| **PyIceberg** | Python client for Iceberg tables in notebooks |
|
||||
| **RustFS** | S3 storage backend (lakehouse bucket) |
|
||||
|
||||
### Nessie vs Polaris
|
||||
|
||||
| Feature | Nessie | Polaris |
|
||||
|---------|--------|---------|
|
||||
| Git-like branching | ✓ | ✗ |
|
||||
| Time travel | ✓ | ✓ |
|
||||
| Git-like branching | Yes | No |
|
||||
| Time travel | Yes | Yes |
|
||||
| RBAC/governance | Basic | Full |
|
||||
| Multi-catalog management | ✗ | ✓ |
|
||||
| Iceberg REST API | ✓ | ✓ |
|
||||
| Unity Catalog-like | ✗ | ✓ |
|
||||
|
||||
**Use Nessie** for data versioning, experimentation branches, and development workflows.
|
||||
**Use Polaris** for production governance, access control, and multi-tenant catalog management.
|
||||
|
||||
### Polaris API
|
||||
|
||||
```bash
|
||||
# Get access token
|
||||
TOKEN=$(curl -s -X POST http://polaris.home.fhirworx.io/api/catalog/v1/oauth/tokens \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "grant_type=client_credentials&client_id=root&client_secret=<polaris_root_secret>" | jq -r '.access_token')
|
||||
|
||||
# List catalogs
|
||||
curl -s http://polaris.home.fhirworx.io/api/management/v1/catalogs \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# Create a catalog
|
||||
curl -s -X POST http://polaris.home.fhirworx.io/api/management/v1/catalogs \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "analytics",
|
||||
"type": "INTERNAL",
|
||||
"properties": {"default-base-location": "s3://polaris/analytics/"},
|
||||
"storageConfigInfo": {
|
||||
"storageType": "S3",
|
||||
"allowedLocations": ["s3://polaris/"],
|
||||
"s3": {"region": "us-east-1", "endpoint": "http://rustfs:9000", "pathStyleAccess": true}
|
||||
}
|
||||
}'
|
||||
```
|
||||
| Multi-catalog management | No | Yes |
|
||||
| Iceberg REST API | Yes | Yes |
|
||||
|
||||
### Query Access
|
||||
|
||||
| Method | Use Case | Connection |
|
||||
|--------|----------|------------|
|
||||
| **Trino CLI** | Ad-hoc SQL queries | `docker exec -it trino trino` |
|
||||
| **Trino JDBC** | External tools (DBeaver, etc.) | `jdbc:trino://trino.home.fhirworx.io/iceberg` |
|
||||
| **Trino Web UI** | Query monitoring | http://trino.home.fhirworx.io |
|
||||
| **PyIceberg** | Python notebooks | Direct to Nessie REST API |
|
||||
| **DuckDB** | Fast local queries | Read Iceberg via PyArrow |
|
||||
| Method | Connection |
|
||||
|--------|------------|
|
||||
| **Trino CLI** | `docker exec -it trino trino` |
|
||||
| **Trino JDBC** | `jdbc:trino://trino.homelab.fhirworx.io/iceberg` |
|
||||
| **Trino Web UI** | `trino.homelab.fhirworx.io` |
|
||||
| **PyIceberg** | Direct to Nessie REST API |
|
||||
| **DuckDB** | Read Iceberg via PyArrow |
|
||||
|
||||
### Example: Create a Table
|
||||
|
||||
```sql
|
||||
-- Connect to Trino
|
||||
docker exec -it trino trino
|
||||
|
||||
-- Create a schema
|
||||
CREATE SCHEMA iceberg.analytics;
|
||||
|
||||
-- Create a partitioned table
|
||||
CREATE TABLE iceberg.analytics.events (
|
||||
event_id VARCHAR,
|
||||
event_time TIMESTAMP(6) WITH TIME ZONE,
|
||||
@@ -456,101 +407,62 @@ WITH (
|
||||
partitioning = ARRAY['day(event_time)']
|
||||
);
|
||||
|
||||
-- Insert data
|
||||
INSERT INTO iceberg.analytics.events VALUES
|
||||
('evt-001', CURRENT_TIMESTAMP, 'user-1', 'page_view', MAP(ARRAY['page'], ARRAY['/home']));
|
||||
|
||||
-- Query data
|
||||
SELECT * FROM iceberg.analytics.events;
|
||||
```
|
||||
|
||||
## Traefik Reverse Proxy
|
||||
|
||||
All HTTP services are routed through Traefik using file-based configuration (not Docker labels).
|
||||
|
||||
### Configuration
|
||||
|
||||
Routes are defined in `traefik/dynamic/services.yml`. To add a new service:
|
||||
|
||||
```yaml
|
||||
http:
|
||||
routers:
|
||||
myservice:
|
||||
rule: "Host(`myservice.home.fhirworx.io`)"
|
||||
service: myservice
|
||||
entryPoints:
|
||||
- web
|
||||
|
||||
services:
|
||||
myservice:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://myservice:8080"
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- **OTEL Tracing** - All requests traced to Jaeger
|
||||
- **Prometheus Metrics** - Request rates, latencies by router/service
|
||||
- **File Provider** - Routes defined in YAML, hot-reloaded
|
||||
- **Dashboard** - http://traefik.home.fhirworx.io
|
||||
|
||||
### TCP/UDP Routing
|
||||
|
||||
Traefik can also route TCP and UDP traffic. See `traefik/dynamic/` for examples.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
stack/
|
||||
├── compose.yml # Docker Compose configuration
|
||||
├── .env # Environment variables (not in git)
|
||||
├── .woodpecker.yml # CI/CD pipeline
|
||||
├── README.md # This file
|
||||
├── data/ # Shared data directory
|
||||
├── compose.yml # Docker Compose (all services)
|
||||
├── .env # Environment variables (not in git)
|
||||
├── README.md
|
||||
├── css/ # Centralized theme assets
|
||||
│ ├── inject.css # Global throwback CSS (served as throwback.css)
|
||||
│ ├── dashboard.css # Dashboard tile grid
|
||||
│ ├── gitea.css # Gitea throwback theme
|
||||
│ ├── woodpecker.css # Woodpecker custom CSS
|
||||
│ ├── marimo.css # Marimo notebook theme
|
||||
│ ├── favicon.svg # Pixel-art "H" favicon (canonical)
|
||||
│ ├── favicon.png # 64x64 PNG favicon
|
||||
│ ├── fav32.png # 32x32 PNG favicon
|
||||
│ ├── fav16.png # 16x16 PNG favicon
|
||||
│ ├── apple-touch-icon.png # 180x180 Apple touch icon
|
||||
│ ├── logo.svg # Navbar logo (Gitea)
|
||||
│ └── logo.png # PNG logo (256x256)
|
||||
├── nginx/
|
||||
│ ├── nginx.conf # Dashboard nginx config (CORS for theme assets)
|
||||
│ └── index.html # Dashboard HTML
|
||||
├── traefik/
|
||||
│ ├── traefik.yml # Static config (entrypoints, plugins, OTEL)
|
||||
│ ├── dynamic/
|
||||
│ │ └── services.yml # All routes, middlewares, services (Go template)
|
||||
│ └── plugins/ # Local rewrite-body plugin
|
||||
├── gitea/
|
||||
│ └── custom/
|
||||
│ └── public/assets/css/
|
||||
│ └── theme-throwback.css
|
||||
│ └── custom/ # Gitea custom branding
|
||||
├── grafana/
|
||||
│ ├── provisioning/
|
||||
│ │ ├── dashboards/
|
||||
│ │ │ └── dashboards.yml
|
||||
│ │ └── datasources/
|
||||
│ │ └── datasources.yml
|
||||
│ └── dashboards/
|
||||
│ ├── homelab-overview.json
|
||||
│ └── traefik.json
|
||||
│ └── provisioning/ # Datasources + dashboard config
|
||||
├── loki/
|
||||
│ ├── loki-config.yml
|
||||
│ └── promtail-config.yml
|
||||
├── nginx/
|
||||
│ ├── nginx.conf # Dashboard nginx config
|
||||
│ └── index.html # Dashboard HTML
|
||||
├── notebooks/
|
||||
│ ├── Dockerfile
|
||||
│ ├── pyproject.toml
|
||||
│ ├── .marimo.toml
|
||||
│ └── retro-arcade.css
|
||||
├── prometheus/
|
||||
│ └── prometheus.yml
|
||||
├── traefik/
|
||||
│ ├── traefik.yml # Static configuration
|
||||
│ └── dynamic/
|
||||
│ ├── middlewares.yml # Rate limiting, security headers
|
||||
│ └── services.yml # All HTTP routes
|
||||
│ ├── prometheus.yml
|
||||
│ └── targets/services.yml
|
||||
├── trino/
|
||||
│ └── etc/
|
||||
│ ├── config.properties
|
||||
│ ├── jvm.config
|
||||
│ ├── node.properties
|
||||
│ └── catalog/
|
||||
│ └── iceberg.properties
|
||||
├── woodpecker/
|
||||
│ └── custom.css
|
||||
└── zotero/
|
||||
├── Dockerfile
|
||||
└── data/
|
||||
│ └── etc/ # Trino config + catalog properties
|
||||
├── notebooks/ # Marimo notebook files
|
||||
├── zotero/ # Zotero data + profiles
|
||||
├── src/ # Python source (narwhals expressions, pipes)
|
||||
├── tests/ # Test suite
|
||||
├── .woodpecker/ # CI/CD pipeline definitions
|
||||
│ ├── ci.yml
|
||||
│ ├── deploy.yml
|
||||
│ ├── infra-ci.yml
|
||||
│ └── rebuild-all.yml
|
||||
└── data/ # Shared data directory
|
||||
```
|
||||
|
||||
## SSH Access
|
||||
@@ -559,7 +471,7 @@ Configure Git SSH access in `~/.ssh/config`:
|
||||
|
||||
```
|
||||
Host gitea
|
||||
HostName gitea.home.fhirworx.io
|
||||
HostName homelab.fhirworx.io
|
||||
Port 2222
|
||||
User git
|
||||
IdentityFile ~/.ssh/gitea_ed25519
|
||||
@@ -569,33 +481,28 @@ Host gitea
|
||||
Generate and add SSH key:
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -C "user@homelab" -f ~/.ssh/gitea_ed25519 -N ""
|
||||
ssh-keyscan -p 2222 gitea.home.fhirworx.io >> ~/.ssh/known_hosts
|
||||
ssh-keyscan -p 2222 homelab.fhirworx.io >> ~/.ssh/known_hosts
|
||||
```
|
||||
|
||||
Add public key to Gitea: http://gitea.home.fhirworx.io/user/settings/keys
|
||||
Add public key to Gitea: `gitea.homelab.fhirworx.io/user/settings/keys`
|
||||
|
||||
## Container Registry
|
||||
|
||||
Login:
|
||||
```bash
|
||||
echo "<GITEA_TOKEN>" | docker login gitea.home.fhirworx.io -u <username> --password-stdin
|
||||
```
|
||||
# Login
|
||||
echo "<GITEA_TOKEN>" | docker login gitea.homelab.fhirworx.io -u <username> --password-stdin
|
||||
|
||||
Tag and push:
|
||||
```bash
|
||||
docker tag <image>:latest gitea.home.fhirworx.io/homelab/<image>:latest
|
||||
docker push gitea.home.fhirworx.io/homelab/<image>:latest
|
||||
# Tag and push
|
||||
docker tag <image>:latest gitea.homelab.fhirworx.io/homelab/<image>:latest
|
||||
docker push gitea.homelab.fhirworx.io/homelab/<image>:latest
|
||||
```
|
||||
|
||||
## Ports Reference
|
||||
|
||||
| Port | Service | Notes |
|
||||
|------|---------|-------|
|
||||
| 80 | Traefik HTTP | iptables redirect to 8880 |
|
||||
| 443 | Traefik HTTPS | iptables redirect to 8443 |
|
||||
| 2222 | Gitea SSH | Direct access |
|
||||
| 4317 | Jaeger OTLP gRPC | Direct access for external traces |
|
||||
| 4318 | Jaeger OTLP HTTP | Direct access for external traces |
|
||||
| 5432 | PostgreSQL | Direct access |
|
||||
| 9000 | RustFS S3 API | Also via s3.home.fhirworx.io |
|
||||
| 9001 | RustFS Console | Also via minio.home.fhirworx.io |
|
||||
| 80 | Traefik HTTP | All subdomain routing |
|
||||
| 443 | Traefik HTTPS | TLS termination |
|
||||
| 8081 | Traefik API | Dashboard + API (localhost only) |
|
||||
| 2222 | Gitea SSH | Git over SSH |
|
||||
| 3478 | Zotero TURN | WebRTC for KasmVNC |
|
||||
|
||||
Reference in New Issue
Block a user