Files
ccn/README.md
T

7.6 KiB

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 The Hummingbird website service — executable, pages, static assets, Docker/Compose deployment. Its README is the operational reference.
Packages/Infrastructure 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 String Catalog lookups, Accept-Language negotiation, and the catalog-derived language list.
Packages/Persistence The Fluent data layer: driver selection (in-memory SQLite or PostgreSQL), migration registration, and the readiness probe.
Packages/Utility 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.

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:

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.
Canonical site URL (scheme + host) https://loud.amsterdam The Sitemap: reference in robots.txt and the <loc> entry in sitemap.xml.
  • 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 & localizationServices/Website/Sources/Library/Catalogs/Localizable.xcstrings (English only out of the box) and the landing / 404 pages under Services/Website/Sources/Library.
  2. Static assetsServices/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.
  4. Analytics — the cookieless Umami 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.
  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.

4. Build & run

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:

(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.