Improved the bootstrap script to write the canonical site origin and also, fixed some nasty bugs.

This commit is contained in:
2026-08-30 09:53:39 +02:00
parent a3d0bc75fd
commit 6c75d5e743
6 changed files with 43 additions and 19 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
**/.build
**/.swiftpm
# Test sources
# Local database data directory.
**/Tests/DB
# Xcode project (not used by the Linux build)
+1 -1
View File
@@ -1,5 +1,5 @@
# Template root Makefile — bootstrap convenience only.
# This file is removed by `Tools/bootstrap` once the template is generated.
# This file is removed by `Scripts/bootstrap` once the template is generated.
.DEFAULT_GOAL := help
+9 -5
View File
@@ -53,9 +53,9 @@ 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`. |
| **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 `<loc>` 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.
@@ -84,10 +84,14 @@ make bootstrap # or: ./Scripts/bootstrap
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
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.
6. **Domain models** — replace the `Persistence` package's sample `ExampleRecord`
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
+26 -7
View File
@@ -31,7 +31,7 @@ PLACEHOLDER_URL_PATTERN='https://site\.example\.com'
# --- Prompts ------------------------------------------------------------------
printf 'Site display name (PascalCase, e.g. Berlin) [Site]: '
printf 'Site display name (PascalCase, e.g. Acme) [Site]: '
read -r NAME
NAME="${NAME:-Site}"
@@ -54,7 +54,7 @@ case "$NAME" in
esac
DEFAULT_SLUG="$(printf '%s' "$NAME" | tr '[:upper:]' '[:lower:]')"
printf 'Project slug (lowercase, used for owner/db/compose, e.g. loud-berlin) [%s]: ' "$DEFAULT_SLUG"
printf 'Project slug (lowercase, used for owner/db/compose, e.g. acme-web) [%s]: ' "$DEFAULT_SLUG"
read -r SLUG
SLUG="${SLUG:-$DEFAULT_SLUG}"
@@ -75,7 +75,7 @@ case "$SLUG" in
;;
esac
printf 'Canonical site URL (scheme + host, e.g. https://berlin.example.com) [%s]: ' "$PLACEHOLDER_URL"
printf 'Canonical site URL (scheme + host, e.g. https://acme.example.com) [%s]: ' "$PLACEHOLDER_URL"
read -r URL
URL="${URL:-$PLACEHOLDER_URL}"
URL="${URL%/}" # the rewrites append their own path, so drop a trailing slash
@@ -99,7 +99,7 @@ esac
echo
echo " display name : $NAME (server name \"${NAME}Website\", ${NAME}.xcodeproj)"
echo " slug : $SLUG (container owner, database name/user, compose project)"
echo " canonical URL: $URL (robots.txt sitemap reference, sitemap.xml entry)"
echo " canonical URL: $URL (robots.txt, sitemap.xml, String.Site.origin)"
echo
printf 'Apply these values? [y/N]: '
read -r CONFIRM
@@ -136,6 +136,13 @@ rewrite "$W/Sources/Library/Public/Extensions/String+Constants.swift" \
-e "s/SiteWebsite/${NAME}Website/g" \
-e "s/= \"site\"/= \"${SLUG}\"/g"
# Canonical origin. Left empty for the placeholder, which keeps the HTTPS
# redirect off rather than aimed at a reserved example domain.
if [ "$URL" != "$PLACEHOLDER_URL" ]; then
rewrite "$W/Sources/Library/Public/Extensions/String+Constants.swift" \
-e "s|^ public static let origin = \"\"\$| public static let origin = \"${URL}\"|"
fi
# Local env example.
rewrite "$W/.env.local" \
-e "s/SiteWebsite/${NAME}Website/g" \
@@ -162,6 +169,17 @@ rewrite "$W/docker-compose.override.yml" \
rewrite "$W/Makefile" \
-e "s/),site)/),${SLUG})/g"
# Web app manifest: the identity shown by an install prompt.
rewrite "$W/Resources/Static/site.webmanifest" \
-e "s/^ \"short_name\": \"\"/ \"short_name\": \"${NAME}\"/" \
-e "s/^ \"name\": \"\"/ \"name\": \"${NAME}\"/"
# Persistence integration test: its POSTGRES_TEST_* fallbacks address the local
# container `make db-mount` creates, which is initialised from the slug.
rewrite "Packages/Persistence/Tests/Cases/Public/Methods/ServiceTests.swift" \
-e "s/?? \"site\"/?? \"${SLUG}\"/g" \
-e "s/: \"site\",\$/: \"${SLUG}\",/"
# Crawler files: the absolute origin they must carry. `|` is the sed delimiter,
# since both the placeholder and the replacement contain slashes.
rewrite "$W/Resources/Static/robots.txt" \
@@ -175,7 +193,9 @@ rewrite "$W/README.md" \
-e "s/^# Site Website\$/# ${NAME} Website/" \
-e "s/\*\*Site\*\*/**${NAME}**/" \
-e "s/SiteWebsite/${NAME}Website/g" \
-e "s/\`site\`/\`${SLUG}\`/g"
-e "s/\`site\`/\`${SLUG}\`/g" \
-e "s/\`Site\.xcodeproj\`/\`${NAME}.xcodeproj\`/g" \
-e "s|^\`robots.txt\` and \`sitemap.xml\` need an absolute origin.*\$|\`robots.txt\` and \`sitemap.xml\` carry the absolute origin bootstrap wrote into them: ${URL}. Update both if the canonical origin ever changes.|"
# Xcode project: PBXProject name + directory.
if [ -d "$XCODEPROJ" ]; then
@@ -218,7 +238,6 @@ rm -f Scripts/bootstrap
rmdir Scripts 2>/dev/null || true
rm -f Makefile # root convenience Makefile (bootstrap only)
rm -f README.md # template usage docs
rm -rf .gitea # Gitea template metadata
# --- Done ---------------------------------------------------------------------
@@ -231,7 +250,7 @@ Next steps:
- $W/Sources/Library/Catalogs/Localizable.xcstrings (copy)
- $W/Sources/Library (landing / 404 pages)
- $W/Resources/Static (css, js, favicon, icons)
- $W/Resources/Static/site.webmanifest (name / short_name)
- $W/Resources/Static/site.webmanifest (theme colours)
2. Set a real database password in a git-ignored $W/.env
(the committed .env.local defaults the password to the slug — do NOT ship that).
3. Analytics ships OFF, and stays off until you opt in. To enable it: check
+2 -2
View File
@@ -134,9 +134,9 @@ every `GET`/`HEAD` whose path ends in a slash is answered with a `301` to the fo
### Site
| Config key | Environment variable | Default | Description |
| --- | --- | --- | --- |
| `site.origin` | `SITE_ORIGIN` | _(empty)_ | The public origin the site is served at (scheme and host, no trailing slash). The HTTPS redirect points at it, and absolute links derive from it. |
| `site.origin` | `SITE_ORIGIN` | _(set by bootstrap)_ | The public origin the site is served at (scheme and host, no trailing slash). The HTTPS redirect points at it, and absolute links derive from it. |
Empty by default, which leaves the [HTTPS redirect](#https-redirect) off: a deployment that trusts the forwarded-protocol header without setting this would otherwise redirect at a host the template guessed, and a `301` is cached for a long time. Bootstrap does **not** fill it in — it rewrites the crawler files only.
Bootstrap writes the canonical URL it prompts for here, leaving it empty for the placeholder. An empty or non-HTTPS origin disables the [HTTPS redirect](#https-redirect), which `https.trustForwardedProto` must enable besides — a `301` is cached for a long time, so it is never issued at a host nobody named.
### Logging
| Config key | Environment variable | Default | Description |
@@ -56,10 +56,11 @@ extension String {
}
/// A namespace for the site string constants.
public enum Site {
/// The default public origin the site is served at (scheme and host, no trailing slash): empty, which leaves the HTTPS redirect off.
/// The default public origin the site is served at (scheme and host, no trailing slash).
///
/// A deployment that trusts the forwarded-protocol header without also setting `site.origin` would otherwise redirect at a host the
/// template guessed for it, and a `301` is cached for a long time.
/// Bootstrap writes the canonical URL it prompts for here, leaving it empty for the placeholder. An empty or non-HTTPS origin disables
/// the HTTPS redirect, which `https.trustForwardedProto` must enable besides a `301` is cached for a long time, so it is never issued
/// at a host nobody named.
public static let origin = ""
}
}