# Deploying FlexHub customisations

These notes cover **updating an already-running** FlexHub. If you're
standing up Forgejo from scratch on a new host, follow the
operator-internal runbook at `_github/infra/forgejo/DEPLOY.md` in the
`ai-lab` repo first, then come back here for the rebrand layer.

All paths below are on `nullstone` (the host running the Forgejo
container). The container is named `forgejo`. Host UID 1000 owns the
data tree.

---

## Layout on nullstone

```
/opt/docker/forgejo/
└── docker-compose.yml          ← image tag, env, traefik labels

/home/docker/forgejo/
├── data/
│   └── gitea/
│       └── custom/             ← Forgejo "override" tree
│           ├── conf/app.ini    ← APP_NAME, DEFAULT_THEME, THEMES
│           ├── templates/
│           │   └── home.tmpl   ← homepage (mirror of this repo)
│           └── public/assets/
│               ├── css/theme-flexhub.css
│               └── img/        ← logos + favicons
└── config/                     ← Forgejo-managed; don't hand-edit
```

`custom/` is the only path you ever edit by hand. Everything in this
repo maps 1:1 into that tree.

---

## Update the homepage template

Edit `templates/home.tmpl` here, then on `nullstone`:

```bash
ssh nullstone bash <<'EOF'
sudo cp /tmp/home.tmpl /home/docker/forgejo/data/gitea/custom/templates/home.tmpl
sudo chown 1000:1000 /home/docker/forgejo/data/gitea/custom/templates/home.tmpl
EOF
```

Forgejo hot-reloads templates — no container restart needed. Hit
<https://flexhub.s8n.ru/> and hard-refresh (Ctrl+Shift+R) to confirm.

If the page renders blank, check the Forgejo log for a template parse
error:

```bash
ssh nullstone 'docker logs --tail 50 forgejo 2>&1 | grep -i template'
```

The wrapping `{{template "base/head" .}}` and `{{template "base/footer" .}}`
calls are mandatory — drop them and you get a half-page.

---

## Update the theme CSS

Edit `public/assets/css/theme-flexhub.css`, copy to nullstone:

```bash
ssh nullstone bash <<'EOF'
sudo cp /tmp/theme-flexhub.css \
   /home/docker/forgejo/data/gitea/custom/public/assets/css/theme-flexhub.css
sudo chown 1000:1000 \
   /home/docker/forgejo/data/gitea/custom/public/assets/css/theme-flexhub.css
EOF
```

CSS is served static — no reload needed. The theme is selected
per-user via `DEFAULT_THEME = flexhub` in `app.ini` (see next section)
and is overridable in user settings.

The brand palette is **orange `#FF9000` / black `#000` / white `#FFF`**.
Don't drift.

---

## Bump APP_NAME or theme list

`app.ini` lives at `/home/docker/forgejo/data/gitea/custom/conf/app.ini`
on nullstone (Forgejo merges custom values over its built-in defaults).
The relevant keys:

```ini
APP_NAME = FlexHub
DEFAULT_THEME = flexhub
THEMES = forgejo-auto,forgejo-light,forgejo-dark,flexhub,veilor
```

After editing `app.ini` you **must restart the container** — env-style
INI is read at boot only:

```bash
ssh nullstone 'cd /opt/docker/forgejo && docker compose restart forgejo'
```

Watch the log come back clean:

```bash
ssh nullstone 'docker logs -f --tail 30 forgejo'
```

You're good when you see `Listen: http://0.0.0.0:3000`. The web UI
should now show the new title bar / theme list.

---

## Add a new template override

Forgejo template hierarchy:
<https://forgejo.org/docs/latest/admin/customization/#changing-the-look-of-forgejo>.
The general flow:

1. Find the upstream template in the Forgejo source tree
   (`templates/...`).
2. Copy its full content into `custom/templates/<same path>.tmpl` on
   nullstone, owned `1000:1000`.
3. Edit your override.
4. Mirror the file into this repo under `templates/<same path>.tmpl`
   so the repo stays the source of truth.

Hot-reload applies — no container restart.

If the override stops rendering after a Forgejo version bump, diff
your override against the new upstream template — base layout slots
sometimes get renamed across major versions.

---

## Add a new image asset

```bash
scp my-asset.png nullstone:/tmp/
ssh nullstone bash <<'EOF'
sudo cp /tmp/my-asset.png \
   /home/docker/forgejo/data/gitea/custom/public/assets/img/my-asset.png
sudo chown 1000:1000 \
   /home/docker/forgejo/data/gitea/custom/public/assets/img/my-asset.png
EOF
```

Reachable at `https://flexhub.s8n.ru/assets/img/my-asset.png`. No
container restart.

When replacing favicons specifically, also clear the browser cache —
favicons are aggressively cached.

---

## Traefik routing for `flexhub.s8n.ru`

The FlexHub-branded hostname is wired in via a **file-provider** router
(`/opt/docker/traefik/config/dynamic.yml` on nullstone), not via the
docker-provider labels on the Forgejo compose file. This is intentional:

- `git.s8n.ru` — docker-provider router, `no-guest@file` middleware.
- `flexhub.s8n.ru` — file-provider router, public, rate-limit only.

The file-provider router defines its **own** service
(`flexhub-svc`) pointing at `http://forgejo:3000` directly, rather than
referencing `forgejo@docker`. This works around a startup race where
file-provider routers can resolve before docker-provider services are
populated, leaving the router permanently broken with
`the service "forgejo@docker" does not exist`.

If you change the Forgejo container name or move it off the `proxy`
network, update `flexhub-svc.loadBalancer.servers[0].url` in
`dynamic.yml` to match.

---

## Rollback

The customisations are pure overlay files. To revert FlexHub branding:

```bash
ssh nullstone bash <<'EOF'
sudo mv /home/docker/forgejo/data/gitea/custom /home/docker/forgejo/data/gitea/custom.disabled
EOF
ssh nullstone 'cd /opt/docker/forgejo && docker compose restart forgejo'
```

Forgejo falls back to vanilla theme + homepage. Nothing in the data
store changes. Restore by renaming `custom.disabled` back to `custom`.
