# Fossil SCM Hub — /var/fossil
Self-hosted multi-repo Fossil SCM server for craftdesign.group.
## Architecture
```
https://fossil.craftdesign.group
↓ Cloudflare named tunnel (fossil / 45ea54d2-1b49-496b-bc35-c01c45d95297)
Caddy :8090
├── GET / → /var/fossil/www/index.html (dark landing page)
└── /* → proxy to Fossil :8081
Fossil :8081 (localhost only)
└── serves /var/fossil/repos/*.fossil (multi-repo directory mode)
```
## Directory Layout
```
/var/fossil/
├── repos/ # all .fossil repo files (auto-served by fossil)
│ ├── edgesync.fossil
│ └── go-libfossil.fossil
├── skin/ # shared skin — push to repos via script below
│ ├── header.txt # TH1 template
│ ├── footer.txt
│ └── css.txt # dark theme matching landing page
├── www/
│ └── index.html # custom landing page (dark theme)
├── Caddyfile # Caddy reverse proxy config
└── CLAUDE.md # this file
```
## Systemd Services
All three are enabled and start on boot.
| Service | Command |
|---------|---------|
| `fossil.service` | `fossil server /var/fossil/repos --port 8081 --localhost --repolist --https --baseurl https://fossil.craftdesign.group --notfound /` |
| `caddy-fossil.service` | `caddy run --config /var/fossil/Caddyfile` |
| `cloudflared-fossil.service` | `cloudflared tunnel --config ~/.cloudflared/config.yml run` |
```bash
sudo systemctl status fossil caddy-fossil cloudflared-fossil
sudo systemctl restart fossil caddy-fossil cloudflared-fossil
sudo journalctl -u fossil -f
```
## Repositories
| Name | URL | Admin password |
|------|-----|----------------|
| edgesync | https://fossil.craftdesign.group/edgesync/ | set via /edgesync/setup_alist |
| go-libfossil | https://fossil.craftdesign.group/go-libfossil/ | set via /go-libfossil/setup_alist |
Admin user: `dmestas`
## Adding a Repository
```bash
# 1. Init on server
fossil init /var/fossil/repos/<name>.fossil
# 2. Apply the skin
python3 /var/fossil/skin/apply.py
# 3. Add a card to /var/fossil/www/index.html
# From local machine:
fossil clone https://[email protected]/<name>/ <name>.fossil
# or push existing:
fossil remote add hub https://[email protected]/<name>/
fossil push hub
```
## Skin
Dark theme matching the landing page. Design tokens:
- `--bg: #0f1117` / `--surface: #1a1d27` / `--border: #2a2d3e`
- `--accent: #6c8ebf` / `--text: #e2e8f0` / `--muted: #8892a4`
- Fonts: Inter (body), JetBrains Mono (code)
The skin lives in `/var/fossil/skin/` and must be written into each repo's
SQLite config table (keys: `css`, `header`, `footer`). After editing skin files, push with:
```bash
python3 << 'EOF'
import sqlite3, time, os, glob
REPOS = glob.glob("/var/fossil/repos/*.fossil")
SKIN_DIR = "/var/fossil/skin"
parts = {
"css": open(f"{SKIN_DIR}/css.txt").read(),
"header": open(f"{SKIN_DIR}/header.txt").read(),
"footer": open(f"{SKIN_DIR}/footer.txt").read(),
}
mtime = int(time.time())
for repo in REPOS:
con = sqlite3.connect(repo)
for name, value in parts.items():
con.execute("INSERT OR REPLACE INTO config(name,value,mtime) VALUES(?,?,?)",
(name, value, mtime))
con.commit()
con.close()
print(f"skinned {os.path.basename(repo)}")
EOF
```
## Cloudflare Tunnel
- Tunnel name: `fossil`
- Tunnel ID: `45ea54d2-1b49-496b-bc35-c01c45d95297`
- Config: `~/.cloudflared/config.yml` (points to Caddy :8090)
- Credentials: `~/.cloudflared/45ea54d2-….json`
- DNS CNAME managed automatically by cloudflared
## Installed Versions
- Fossil 2.28 — `/usr/local/bin/fossil` (built from source tarball)
- Caddy 2.9.1 — `/usr/local/bin/caddy`
- cloudflared 2026.3.0 — `/usr/local/bin/cloudflared`
## Known Gotchas
- Fossil skin keys are `css`/`header`/`footer` (no `skin:` prefix) — written directly to each repo's SQLite `config` table
- `--https` flag is required on `fossil server` when behind a reverse proxy — without it Fossil generates http:// redirect loops
- Skins are per-repo; adding a new repo requires running the apply script
- Port 8080 is taken by Coolify's Traefik proxy — Fossil runs on 8081
- The repo listing page (`/`) has no skin — it's replaced entirely by Caddy serving the static `index.html`