2026-05-20 11:01:12 +02:00
|
|
|
# HELIX
|
|
|
|
|
|
|
|
|
|
> **EVOLV and every R&D strand, one helix.**
|
|
|
|
|
>
|
|
|
|
|
> The R&D showcase platform of Waterschap Brabantse Delta. EVOLV at its core,
|
|
|
|
|
> every innovation along the strands.
|
|
|
|
|
|
|
|
|
|
HELIX collects projects, innovations, and updates from across the R&D team in
|
|
|
|
|
one place — with deep links to repos, dashboards, and demos. Sign-in is gated
|
|
|
|
|
to the `RnD` Gitea organisation; viewing is open.
|
|
|
|
|
|
|
|
|
|
## Stack
|
|
|
|
|
|
|
|
|
|
- **SvelteKit 2** + **Svelte 5** + TypeScript
|
|
|
|
|
- **Tailwind v4** (CSS-first design tokens)
|
|
|
|
|
- **SQLite** (single file) + **Drizzle ORM**
|
|
|
|
|
- **Gitea OAuth2** for authoring (no passwords stored)
|
|
|
|
|
- Pure **SVG + CSS** helix animation — no WebGL
|
|
|
|
|
|
2026-05-20 11:05:06 +02:00
|
|
|
## Run it locally — two ways
|
|
|
|
|
|
|
|
|
|
### 1. Docker (production-like, recommended for testing)
|
|
|
|
|
|
|
|
|
|
Mirrors how the EVOLV stack is exercised locally. One command, named volume
|
|
|
|
|
for SQLite, healthcheck wired:
|
2026-05-20 11:01:12 +02:00
|
|
|
|
|
|
|
|
```bash
|
2026-05-20 11:05:06 +02:00
|
|
|
cd /path/to/helix
|
|
|
|
|
docker compose up -d --build # build image + start container
|
|
|
|
|
docker compose logs -f helix # tail logs
|
|
|
|
|
# → http://localhost:3000
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If port 3000 is in use (Node-RED, another dev server, …) pick another:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
HELIX_PORT=3030 \
|
|
|
|
|
ORIGIN=http://localhost:3030 \
|
|
|
|
|
GITEA_REDIRECT_URI=http://localhost:3030/auth/gitea/callback \
|
|
|
|
|
docker compose up -d --build
|
|
|
|
|
# → http://localhost:3030
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Useful commands:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
docker compose ps # status + health
|
|
|
|
|
docker inspect helix --format '{{.State.Health.Status}}'
|
|
|
|
|
docker compose exec helix sh # shell into the container
|
|
|
|
|
docker compose exec helix sqlite3 /data/helix.db # inspect the DB
|
|
|
|
|
docker compose down # stop (keeps the volume)
|
|
|
|
|
docker compose down -v # stop + DELETE all data
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The SQLite database lives in the **`helix_helix-data`** named volume at
|
|
|
|
|
`/data/helix.db`. Migrations + the idempotent seed both run on every boot.
|
|
|
|
|
Once you have real content, set `SEED_ON_BOOT=false` in your env to stop
|
|
|
|
|
re-seeding the demo entries.
|
|
|
|
|
|
|
|
|
|
### 2. Native Node (faster dev loop, hot reload)
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
nvm use # .nvmrc → Node 20 (Tailwind v3 also works on 18)
|
|
|
|
|
cp .env.example .env # fill in Gitea OAuth — see below
|
2026-05-20 11:01:12 +02:00
|
|
|
|
|
|
|
|
npm install
|
2026-05-20 11:05:06 +02:00
|
|
|
npm run db:generate # generate drizzle/0000_*.sql from the schema
|
|
|
|
|
npm run db:migrate # apply migrations to ./helix.db
|
|
|
|
|
npm run db:seed # idempotent demo content
|
|
|
|
|
npm run dev # http://localhost:5173
|
2026-05-20 11:01:12 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Gitea OAuth setup
|
|
|
|
|
|
|
|
|
|
HELIX uses your existing Gitea identity. Create an OAuth2 application once:
|
|
|
|
|
|
|
|
|
|
1. Open <https://gitea.wbd-rd.nl/-/user/settings/applications>
|
|
|
|
|
2. Click **Create new OAuth2 application**
|
|
|
|
|
3. Application name: `HELIX (local)` (or `HELIX (production)`)
|
|
|
|
|
4. Redirect URI:
|
|
|
|
|
- local: `http://localhost:3000/auth/gitea/callback`
|
|
|
|
|
- prod: `https://<your-helix-host>/auth/gitea/callback`
|
|
|
|
|
5. Click **Create application**
|
|
|
|
|
6. Copy the **Client ID** and **Client Secret** into your `.env`:
|
|
|
|
|
```
|
|
|
|
|
GITEA_CLIENT_ID=...
|
|
|
|
|
GITEA_CLIENT_SECRET=...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`GITEA_ALLOWED_ORG=RnD` restricts authoring (sign-in) to members of the `RnD`
|
|
|
|
|
organisation. Leave it empty to allow any authenticated Gitea user.
|
|
|
|
|
|
|
|
|
|
## Adding a dashboard embed allowlist host
|
|
|
|
|
|
|
|
|
|
`src/lib/config.ts` → `DASHBOARD_ALLOWED_HOSTS` controls which Grafana / dashboard
|
|
|
|
|
origins can be embedded inline on a project page. Add a hostname (no scheme,
|
|
|
|
|
no path) and redeploy. Hosts not on the list render as a "Open in new tab" card
|
|
|
|
|
instead — never blindly iframed.
|
|
|
|
|
|
2026-05-20 11:05:06 +02:00
|
|
|
## Production deploy
|
|
|
|
|
|
|
|
|
|
Same compose file as local. On the production host:
|
2026-05-20 11:01:12 +02:00
|
|
|
|
|
|
|
|
```bash
|
2026-05-20 11:05:06 +02:00
|
|
|
cp .env.example .env
|
|
|
|
|
# Set: ORIGIN=https://your-host, GITEA_REDIRECT_URI=https://your-host/auth/gitea/callback,
|
|
|
|
|
# GITEA_CLIENT_ID, GITEA_CLIENT_SECRET (from a separate prod OAuth app)
|
2026-05-20 11:01:12 +02:00
|
|
|
docker compose up -d --build
|
|
|
|
|
```
|
|
|
|
|
|
2026-05-20 11:05:06 +02:00
|
|
|
Put a TLS-terminating reverse proxy (nginx/caddy/traefik) in front of port 3000.
|
|
|
|
|
Back up the volume periodically:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
docker compose exec helix sqlite3 /data/helix.db .dump | gzip > helix-backup-$(date +%F).sql.gz
|
|
|
|
|
```
|
2026-05-20 11:01:12 +02:00
|
|
|
|
|
|
|
|
## Project layout
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
helix/
|
|
|
|
|
├── src/
|
|
|
|
|
│ ├── lib/
|
|
|
|
|
│ │ ├── components/ # Helix.svelte, ProjectCard, PostCard, Nav, Footer, DashboardEmbed
|
|
|
|
|
│ │ ├── server/
|
|
|
|
|
│ │ │ ├── db/ # Drizzle schema + client (better-sqlite3)
|
|
|
|
|
│ │ │ ├── auth.ts # session token + cookie helpers
|
|
|
|
|
│ │ │ └── gitea.ts # OAuth2 dance + user fetch
|
|
|
|
|
│ │ ├── config.ts # site name, tagline, dashboard allowlist
|
|
|
|
|
│ │ └── markdown.ts # marked wrapper + slug/id helpers
|
|
|
|
|
│ ├── routes/
|
|
|
|
|
│ │ ├── +page.svelte # landing (helix hero + recent projects/posts)
|
|
|
|
|
│ │ ├── projects/ # /projects, /[slug], /new
|
|
|
|
|
│ │ ├── posts/ # /posts, /[slug], /new
|
|
|
|
|
│ │ ├── login/ # sign-in page (kicks off Gitea OAuth)
|
|
|
|
|
│ │ ├── logout/+server.ts # POST → invalidates session
|
|
|
|
|
│ │ └── auth/gitea/ # OAuth start + callback
|
|
|
|
|
│ ├── app.html
|
|
|
|
|
│ ├── app.css # Tailwind v4 entry + design tokens
|
|
|
|
|
│ ├── app.d.ts # Locals types
|
|
|
|
|
│ └── hooks.server.ts # session hydration
|
|
|
|
|
├── drizzle/ # generated migrations
|
|
|
|
|
├── scripts/
|
|
|
|
|
│ ├── migrate.js # run pending migrations
|
|
|
|
|
│ └── seed.js # idempotent example data
|
|
|
|
|
├── static/favicon.svg
|
|
|
|
|
├── Dockerfile
|
|
|
|
|
├── docker-compose.yml
|
|
|
|
|
├── drizzle.config.ts
|
|
|
|
|
├── svelte.config.js
|
|
|
|
|
├── vite.config.ts
|
|
|
|
|
├── tailwind.config.ts (none — Tailwind v4 uses @theme in app.css)
|
|
|
|
|
├── tsconfig.json
|
|
|
|
|
└── package.json
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## How to post
|
|
|
|
|
|
|
|
|
|
After signing in (top-right → Sign in → Gitea), use:
|
|
|
|
|
|
|
|
|
|
- **+ New** in the nav (or `/projects/new`) — a project: title, summary, markdown body, links
|
|
|
|
|
- **`/posts/new`** — a shorter update / write-up
|
|
|
|
|
|
|
|
|
|
Both are auth-gated. Anyone in `RnD` on Gitea can post.
|
|
|
|
|
|
|
|
|
|
## Contributing
|
|
|
|
|
|
|
|
|
|
This is internal R&D — open a PR against `main` on
|
|
|
|
|
[`gitea.wbd-rd.nl/RnD/helix`](https://gitea.wbd-rd.nl/RnD/helix).
|
|
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
Internal — Waterschap Brabantse Delta R&D.
|