2026-07-11 16:09:28 +02:00
# Website Template
2026-08-20 01:10:37 +02:00
A **template** for the platform's websites, shipping as a complete, buildable
reference site under the placeholder identity ** `Site` ** / ** `site` **. Generating
a real site takes two steps: create a fresh repository, then run the bootstrap
script to stamp in the display name, slug, and canonical URL.
2026-07-11 16:09:28 +02:00
## What's in here
| Path | Role |
| --- | --- |
2026-08-20 01:10:37 +02:00
| [`Services/Website` ](Services/Website/README.md ) | The Hummingbird website service — executable, pages, static assets, Docker/Compose deployment. Its README is the operational reference. |
| [`Packages/Infrastructure` ](Packages/Infrastructure/README.md ) | Shared Hummingbird toolkit: declarative routing, the hardened middlewares, the `Page` /`Asset` scaffolding, the head-metadata types (`SocialCard` , `StructuredData` , `Analytics` ), and the pre-rendered localized responses. |
2026-08-02 03:04:50 +02:00
| [`Packages/Localization` ](Packages/Localization/README.md ) | String Catalog lookups, `Accept-Language` negotiation, and the catalog-derived language list. |
2026-08-20 01:10:37 +02:00
| [`Packages/Persistence` ](Packages/Persistence/README.md ) | The Fluent data layer: driver selection (in-memory SQLite or PostgreSQL), migration registration, and the readiness probe. |
2026-08-02 03:04:50 +02:00
| [`Packages/Utility` ](Packages/Utility/README.md ) | Dependency-free helpers shared across the services. |
2026-07-11 16:09:28 +02:00
| `Site.xcodeproj` | The umbrella Xcode project (renamed to your site by bootstrap). |
| `Scripts/bootstrap` | The generator. Rewrites placeholders, then deletes itself. |
2026-08-02 03:04:50 +02:00
| `Makefile` | Bootstrap convenience only (`make bootstrap` ); removed by the script. |
2026-07-11 16:09:28 +02:00
2026-08-02 03:04:50 +02:00
All four packages are **vendored** , not fetched: the service depends on them by
2026-08-20 01:10:37 +02:00
relative path, so every generated repository owns its copy and can diverge
freely. The trade-off is that engine fixes reach existing sites only by hand.
2026-08-02 03:04:50 +02:00
## Requirements
- Swift 6.3 toolchain (every manifest is `swift-tools-version:6.3` ).
2026-08-20 01:10:37 +02:00
- Docker — optional: the containerized run/deploy workflow, the local PostgreSQL
instance (`make db-mount` ), and the asset-optimization preview.
- Hummingbird CLI (`hb` ) — optional, only for `make site-run` .
2026-08-02 03:04:50 +02:00
2026-08-20 01:10:37 +02:00
Configuration keys, persistence, testing, and deployment are documented in
[the service README ](Services/Website/README.md ).
2026-07-11 16:09:28 +02:00
## Generating a new site
### 1. Create a repository from this template
2026-08-20 01:10:37 +02:00
On Gitea, mark this repository as a template once (**Settings → Template →
“Template”**); new sites then come from the ** “Use this template”** button, each
with its own history.
2026-07-11 16:09:28 +02:00
2026-08-20 01:10:37 +02:00
> No Gitea button? Clone this repo, `rm -rf .git`, and continue — bootstrap
> offers to re-initialise git.
2026-07-11 16:09:28 +02:00
### 2. Bootstrap
2026-08-20 01:10:37 +02:00
From the new repository's root:
2026-07-11 16:09:28 +02:00
```sh
make bootstrap # or: ./Scripts/bootstrap
```
| Prompt | Example | Rewrites |
| --- | --- | --- |
| **Site display name** (PascalCase) | `Loud` | Server name (`LoudWebsite` ), `Loud.xcodeproj` , README title. |
| **Project slug** (lowercase) | `loud-ams` | Container owner, database name/user, Compose project name. |
2026-08-02 02:57:43 +02:00
| **Canonical site URL** (scheme + host) | `https://loud.amsterdam` | The `Sitemap:` reference in `robots.txt` and the `<loc>` entry in `sitemap.xml` . |
2026-08-20 01:10:37 +02:00
- The URL defaults to the reserved `https://site.example.com` ; leaving it is
allowed (the script warns) and can be corrected later in the two crawler files.
- Keeping the default display name `Site` is allowed too: the Xcode rename is
skipped and the placeholders stay in place.
- The script refuses to run outside the repository root and validates the URL
before writing, so a rejected value leaves the tree untouched.
- It rewrites in place, renames `Site.xcodeproj` , optionally starts a fresh git
history, then deletes itself, this file, and the root `Makefile` .
2026-07-11 16:09:28 +02:00
### 3. Fill in the content
1. **Copy & localization** — `Services/Website/Sources/Library/Catalogs/Localizable.xcstrings`
2026-08-02 03:04:50 +02:00
(English only out of the box) and the landing / 404 pages under
`Services/Website/Sources/Library` .
2026-08-02 02:57:43 +02:00
2. **Static assets** — `Services/Website/Resources/Static` (CSS, JS, favicon, icons).
The manifest's `name` / `short_name` are empty and are not rewritten by bootstrap.
2026-08-02 03:04:50 +02:00
3. **Head metadata** — the pages ship no `summary` , `canonicalURL` , `socialCard` , or
2026-08-20 01:10:37 +02:00
`structuredData` : no meta description, no link preview, no JSON-LD until you
supply them. See [Page metadata ](Services/Website/README.md#page-metadata ).
4. **Analytics** — the cookieless [Umami ](https://umami.is ) tracker ships **off**
(`analytics.websiteID` is empty, so no third-party script is requested).
Enabling it, in order: point `String.Analytics.origin` at the instance you
report to, allow that origin in `security.contentSecurityPolicy` (`.env.local`
already does; `docker-compose.yml` falls back to a `'self'` -only policy), then
set `ANALYTICS_WEBSITE_ID` . `ANALYTICS_DOMAINS` limits which hosts report;
`ANALYTICS_RECORDER` opts into the session recorder. See
[Analytics ](Services/Website/README.md#analytics ).
5. **Secrets** — set a real `DATABASE_PASSWORD` in a git-ignored
`Services/Website/.env` ; the committed `.env.local` is an example and defaults
the password to the slug.
6. **Domain models** — replace the `Persistence` package's sample `ExampleRecord`
/ `ExampleRepository` . The default driver is in-memory SQLite, migrated on
every boot; a real site sets `DATABASE_DRIVER=postgres` and migrates out of
band (`swift run Website --database-migrate` ). See
[Persistence ](Services/Website/README.md#persistence ).
2026-07-11 16:09:28 +02:00
### 4. Build & run
```sh
cd Services/Website
make site-run # watch + rebuild
# or
make site-mount # docker compose up --build
```
## What bootstrap does NOT change
- Organisation (`Röck+Cöde VoF` ) and Xcode `DEVELOPMENT_TEAM` — shared across all sites.
2026-08-20 01:10:37 +02:00
- The package architecture, middlewares, security headers, and persistence layer
(PostgreSQL, behind the in-memory SQLite default).
- The analytics origin: `String.Analytics.origin` and the `Content-Security-Policy`
in `.env.local` that allows it both name the platform's shared Umami instance.
Tracking stays off until a site sets `ANALYTICS_WEBSITE_ID` .
- The generic `website` image name and the `Makefile` target names (`site-run` ,
`site-mount` , …).
- The four `Packages/*/README.md` files and the package sources — no rewrite line
covers them, so whatever site name they carry ships as-is. `Packages/Persistence` 's
PostgreSQL integration test defaults its `POSTGRES_TEST_*` credentials to `site` .
2026-07-11 16:09:28 +02:00
## Maintaining the template itself
2026-08-20 01:10:37 +02:00
Edit and test the reference site directly — it builds and its tests pass with the
`Site` / `site` placeholders in place:
2026-08-02 03:04:50 +02:00
```sh
( cd Services/Website && make pkg-test) # the service's own two test targets
( cd Packages/Infrastructure && swift test ) # and likewise for each vendored package
```
2026-08-20 01:10:37 +02:00
No external infrastructure is needed: `Packages/Persistence` 's PostgreSQL
integration test skips itself unless `POSTGRES_TEST_HOST` is set. To run it,
start the database (`cd Services/Website && make db-mount` ), then
`POSTGRES_TEST_HOST=127.0.0.1 swift test` .
Keep the placeholders intact so the bootstrap rewrites keep matching:
2026-08-02 03:04:50 +02:00
| Placeholder | Appears in |
| --- | --- |
| `Site` | Xcode project name and `project.pbxproj` , the service README title |
| `SiteWebsite` | `String+Constants.swift` , `.env.local` , `docker-compose.yml` , the service README |
2026-08-20 01:10:37 +02:00
| `site` | `String+Constants.swift` defaults, `HOST_OWNER` / `DATABASE_*` in `.env.local` and both Compose files, the `site-platform` Compose project, the `Makefile` `db-shell` fallbacks |
2026-08-02 03:04:50 +02:00
| `https://site.example.com` | `Resources/Static/robots.txt` , `Resources/Static/sitemap.xml` |
2026-08-20 01:10:37 +02:00
A new site-specific value needs a placeholder from this set and a matching
rewrite line in `Scripts/bootstrap` ; anything else ships verbatim into every
generated site.