#!/usr/bin/env sh # # bootstrap — turn this template into a concrete website. # # The template ships as a fully buildable reference site whose identity is the # neutral placeholder "Site" / "site". This script rewrites those placeholders, # in place, to the values you provide — then removes itself. # # Run once, from the repository root: # # ./Scripts/bootstrap # or: make bootstrap # # It is idempotent only in the sense that it self-destructs: once run, the # placeholders are gone and the script (and its scaffolding) are deleted. set -eu # --- Preconditions ------------------------------------------------------------ if [ ! -d "Services/Website" ]; then echo "error: run this from the repository root (Services/Website not found)." >&2 exit 1 fi XCODEPROJ="./Site.xcodeproj" # The placeholder origin the crawler files ship with. It is an RFC 2606 reserved # domain, so an un-bootstrapped copy can never point a crawler at a real site. PLACEHOLDER_URL="https://site.example.com" PLACEHOLDER_URL_PATTERN='https://site\.example\.com' # --- Prompts ------------------------------------------------------------------ printf 'Site display name (PascalCase, e.g. Berlin) [Site]: ' read -r NAME NAME="${NAME:-Site}" # The name is interpolated into sed replacements, a Swift string literal, the # Xcode project's directory name and the pbxproj. In a replacement `&` stands for # the whole match and `\` escapes, so either would corrupt the rewrites silently # rather than fail them — reject anything but letters and digits. case "$NAME" in *[!A-Za-z0-9]*) echo "error: the display name must be letters and digits only (got \"$NAME\")." >&2 exit 1 ;; esac case "$NAME" in [A-Za-z]*) ;; *) echo "error: the display name must start with a letter (got \"$NAME\")." >&2 exit 1 ;; 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" read -r SLUG SLUG="${SLUG:-$DEFAULT_SLUG}" # The slug carries the same sed hazard as the name, and additionally becomes the # Compose project name and the PostgreSQL database name and role, which admit # lowercase alphanumerics, hyphens and underscores alone. case "$SLUG" in *[!a-z0-9_-]*) echo "error: the project slug must be lowercase letters, digits, hyphens or underscores (got \"$SLUG\")." >&2 exit 1 ;; esac case "$SLUG" in [a-z0-9]*) ;; *) echo "error: the project slug must start with a lowercase letter or digit (got \"$SLUG\")." >&2 exit 1 ;; esac printf 'Canonical site URL (scheme + host, e.g. https://berlin.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 # The URL is interpolated into a sed replacement (with `|` as the delimiter) and # into the crawler files, so reject anything that is not a plain scheme + host. case "$URL" in http://* | https://*) ;; *) echo "error: the canonical site URL must start with http:// or https:// (got \"$URL\")." >&2 exit 1 ;; esac case "$URL" in *[!-A-Za-z0-9:/._~]*) echo "error: the canonical site URL contains unexpected characters (got \"$URL\")." >&2 exit 1 ;; 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 printf 'Apply these values? [y/N]: ' read -r CONFIRM case "$CONFIRM" in y | Y | yes | YES) ;; *) echo "Aborted. Nothing was changed." exit 1 ;; esac # --- Helpers ------------------------------------------------------------------ # Portable in-place sed (works on both BSD/macOS and GNU/Linux). rewrite() { file="$1" shift [ -f "$file" ] || return 0 tmp="${file}.bootstrap.tmp" if sed "$@" "$file" >"$tmp"; then mv "$tmp" "$file" else rm -f "$tmp" return 1 fi } W="Services/Website" # --- Rewrites ----------------------------------------------------------------- # Swift constants: server name + database name/username. rewrite "$W/Sources/Library/Public/Extensions/String+Constants.swift" \ -e "s/SiteWebsite/${NAME}Website/g" \ -e "s/= \"site\"/= \"${SLUG}\"/g" # Local env example. rewrite "$W/.env.local" \ -e "s/SiteWebsite/${NAME}Website/g" \ -e "s/^HOST_OWNER=site\$/HOST_OWNER=${SLUG}/" \ -e "s/^DATABASE_NAME=site\$/DATABASE_NAME=${SLUG}/" \ -e "s/^DATABASE_USERNAME=site\$/DATABASE_USERNAME=${SLUG}/" \ -e "s/^DATABASE_PASSWORD=site\$/DATABASE_PASSWORD=${SLUG}/" # Production compose. rewrite "$W/docker-compose.yml" \ -e "s/SiteWebsite/${NAME}Website/g" \ -e "s/^name: site-platform\$/name: ${SLUG}-platform/" \ -e "s/DATABASE_NAME:-site}/DATABASE_NAME:-${SLUG}}/" \ -e "s/DATABASE_USERNAME:-site}/DATABASE_USERNAME:-${SLUG}}/" # Local override compose. rewrite "$W/docker-compose.override.yml" \ -e "s/HOST_OWNER:-site}/HOST_OWNER:-${SLUG}}/" \ -e "s/DATABASE_NAME:-site}/DATABASE_NAME:-${SLUG}}/" \ -e "s/DATABASE_USERNAME:-site}/DATABASE_USERNAME:-${SLUG}}/" \ -e "s/DATABASE_PASSWORD:-site}/DATABASE_PASSWORD:-${SLUG}}/" # Makefile db-shell fallbacks. rewrite "$W/Makefile" \ -e "s/),site)/),${SLUG})/g" # 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" \ -e "s|${PLACEHOLDER_URL_PATTERN}|${URL}|g" rewrite "$W/Resources/Static/sitemap.xml" \ -e "s|${PLACEHOLDER_URL_PATTERN}|${URL}|g" # Service README. 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" # Xcode project: PBXProject name + directory. if [ -d "$XCODEPROJ" ]; then rewrite "$XCODEPROJ/project.pbxproj" -e "s/\"Site\"/\"${NAME}\"/g" # Skip the rename when the display name was left as the placeholder: the # source and destination are then the same path, and `mv dir dir` fails # (it moves the directory into itself), which would abort the run here — # after the rewrites, but before the self-cleanup. XCODEPROJ_RENAMED="./${NAME}.xcodeproj" if [ "$XCODEPROJ" != "$XCODEPROJ_RENAMED" ]; then if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then git mv "$XCODEPROJ" "$XCODEPROJ_RENAMED" 2>/dev/null || mv "$XCODEPROJ" "$XCODEPROJ_RENAMED" else mv "$XCODEPROJ" "$XCODEPROJ_RENAMED" fi fi fi # --- Optional: fresh git history --------------------------------------------- echo printf 'Start a fresh git history (drops the template history)? [y/N]: ' read -r FRESH case "$FRESH" in y | Y | yes | YES) if command -v git >/dev/null 2>&1; then rm -rf .git git init -q echo "Reinitialised git. Remember to add your remote and commit." fi ;; *) ;; esac # --- Self-cleanup ------------------------------------------------------------- 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 --------------------------------------------------------------------- cat < # or 'git remote add origin ...' 5. Build and run: cd $W && make site-run The Persistence package still ships an ExampleRecord / ExampleRepository sample model — replace it with your real domain models when you add persistence. EOF if [ "$URL" = "$PLACEHOLDER_URL" ]; then cat <