# Website Template 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. ## What's in here | Path | Role | | --- | --- | | [`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. | | [`Packages/Localization`](Packages/Localization/README.md) | String Catalog lookups, `Accept-Language` negotiation, and the catalog-derived language list. | | [`Packages/Persistence`](Packages/Persistence/README.md) | The Fluent data layer: driver selection (in-memory SQLite or PostgreSQL), migration registration, and the readiness probe. | | [`Packages/Utility`](Packages/Utility/README.md) | Dependency-free helpers shared across the services. | | `Site.xcodeproj` | The umbrella Xcode project (renamed to your site by bootstrap). | | `Scripts/bootstrap` | The generator. Rewrites placeholders, then deletes itself. | | `Makefile` | Bootstrap convenience only (`make bootstrap`); removed by the script. | All four packages are **vendored**, not fetched: the service depends on them by 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. ## Requirements - Swift 6.3 toolchain (every manifest is `swift-tools-version:6.3`). - 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`. Configuration keys, persistence, testing, and deployment are documented in [the service README](Services/Website/README.md). ## Generating a new site ### 1. Create a repository from this template 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. > No Gitea button? Clone this repo, `rm -rf .git`, and continue — bootstrap > offers to re-initialise git. ### 2. Bootstrap From the new repository's root: ```sh make bootstrap # or: ./Scripts/bootstrap ``` | Prompt | Example | Rewrites | | --- | --- | --- | | **Site display name** (PascalCase) | `Acme` | Server name (`AcmeWebsite`), `Acme.xcodeproj`, README title. | | **Project slug** (lowercase) | `acme-web` | Container owner, database name/user, Compose project name. | | **Canonical site URL** (scheme + host) | `https://acme.example.com` | The `Sitemap:` reference in `robots.txt`, the `` entry in `sitemap.xml`, and `String.Site.origin`. | - 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`. ### 3. Fill in the content 1. **Copy & localization** — `Services/Website/Sources/Library/Catalogs/Localizable.xcstrings` (English only out of the box) and the landing / 404 pages under `Services/Website/Sources/Library`. 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. 3. **Head metadata** — the pages ship no `summary`, `canonicalURL`, `socialCard`, or `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. **Site origin** — bootstrap writes the canonical URL into `String.Site.origin` (override with `SITE_ORIGIN`), leaving it empty for the placeholder. An empty or non-HTTPS origin disables the HTTPS redirect, which `HTTPS_TRUST_FORWARDED_PROTO` must enable besides. 6. **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. 7. **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). ### 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. - 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`. ## Maintaining the template itself Edit and test the reference site directly — it builds and its tests pass with the `Site` / `site` placeholders in place: ```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 ``` 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: | 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 | | `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 | | `https://site.example.com` | `Resources/Static/robots.txt`, `Resources/Static/sitemap.xml` | 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.