diff --git a/deploy/README.md b/deploy/README.md index 0e205a6..ddb0474 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -65,6 +65,10 @@ missing). Everything else runs inside containers. 10. **[R]** Verify: `curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/` and `journalctl --user -u ctao-portal-build -n 20` (shows measured build times). +Editor accounts need WRITE access to `ctao/content` (Gitea → repo → +Collaborators, or a team) — public read alone lets them sign in but every +save fails. + ## CMS sign-in (Sveltia ↔ Gitea OAuth) - Gitea OAuth2 app (PKCE, `confidential_client=false`, no secret) with diff --git a/deploy/build.sh b/deploy/build.sh index 5f579f6..e12992c 100755 --- a/deploy/build.sh +++ b/deploy/build.sh @@ -43,18 +43,22 @@ head_of() { code_sha=$(head_of "$CODE_REPO") content_sha=$(head_of "$CONTENT_REPO") # Gitea down/unreachable is a transient, not a unit failure — exit 0 quietly -# instead of painting the journal red every 10 s. -for sha in "$code_sha" "$content_sha"; do - if [[ ! "$sha" =~ ^[0-9a-f]{40}$ ]]; then - echo "poll failed (gitea unreachable?) — skipping this tick" - exit 0 - fi -done +# instead of painting the journal red every 10 s. Name the repo: a 404 here +# also means "repo/branch missing or renamed", not just "Gitea down". +[[ "$code_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CODE_REPO@$BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; } +[[ "$content_sha" =~ ^[0-9a-f]{40}$ ]] || { echo "poll failed for $CONTENT_REPO@$BRANCH (gitea down, or repo/branch missing) — skipping"; exit 0; } release="${code_sha:0:12}-${content_sha:0:12}" # code+content pin the release # Skip only if this pair is both recorded AND still present in releases/ # (a deleted release dir must trigger a rebuild, not an eternal skip). [[ "$release" == "$(cat "$BASE/state/last-built" 2>/dev/null)" \ && -d "$BASE/releases/$release" ]] && exit 0 +# A release that already failed is not retried until either repo moves — +# otherwise one bad commit (e.g. broken frontmatter) turns into a full +# rebuild every 10 s on a shared VM. The failure is loud once, then quiet. +if [[ "$release" == "$(cat "$BASE/state/last-failed" 2>/dev/null)" ]]; then + echo "skipping $release — build failed before; push a fix to retry" + exit 0 +fi echo "building code=$code_sha content=$content_sha" t0=$(date +%s) @@ -106,7 +110,8 @@ podman run --rm --network="$BUILD_NETNS" --memory=1g \ npm run build rm -rf "../releases/$RELEASE" cp -a dist "../releases/$RELEASE" - ' + ' || { echo "$release" > "$BASE/state/last-failed"; echo "BUILD FAILED for $release (see above) — will not retry until a new commit"; exit 1; } +rm -f "$BASE/state/last-failed" # --- 3. Atomic publish: symlink flip via rename(2) — no half-published moment. # mv -T is GNU (the target host is Rocky); when testing on macOS put a diff --git a/deploy/ctao-portal-build.service b/deploy/ctao-portal-build.service index f0d0065..5f905ab 100644 --- a/deploy/ctao-portal-build.service +++ b/deploy/ctao-portal-build.service @@ -4,7 +4,7 @@ # activate a service that is still running. [Unit] -Description=CTAO portal demo — rebuild if the content repo has new commits +Description=CTAO portal demo — rebuild if the code or content repo has new commits [Service] Type=oneshot diff --git a/deploy/ctao-portal-build.timer b/deploy/ctao-portal-build.timer index bb8199b..d0adda1 100644 --- a/deploy/ctao-portal-build.timer +++ b/deploy/ctao-portal-build.timer @@ -6,7 +6,7 @@ # OnBootSec covers reboot. Enable: systemctl --user enable --now ctao-portal-build.timer [Unit] -Description=CTAO portal demo — poll the content repo every 10 s +Description=CTAO portal demo — poll the code and content repos every 10 s [Timer] OnActiveSec=5 diff --git a/public/admin/config.yml b/public/admin/config.yml index 624b143..5f96dcf 100644 --- a/public/admin/config.yml +++ b/public/admin/config.yml @@ -1,6 +1,6 @@ # Sveltia CMS config — backend: Gitea behind the Cyfronet ingress (HTTPS # vhost). Editors log in via Gitea OAuth (PKCE, no client secret); edits are -# committed to the ctao/portal repo. Gitea's ROOT_URL and CORS are pinned to +# committed to the ctao/content repo. Gitea's ROOT_URL and CORS are pinned to # these origins in deploy/ctao-demo-gitea.container. backend: name: gitea diff --git a/public/search-client.js b/public/search-client.js index d2c06a0..dd78122 100644 --- a/public/search-client.js +++ b/public/search-client.js @@ -112,7 +112,7 @@ const unit = opts.unit || 'result'; status.textContent = hits.length ? hits.length + ' ' + unit + (hits.length === 1 ? '' : 's') + ' for “' + input.value.trim() + '”' - : 'No results for “' + input.value.trim() + '” — try a shorter or different term.'; + : 'No results for “' + input.value.trim() + '”. Try a shorter or different term.'; // Off-DOM build + atomic swap (no transient page-height collapse) const frag = document.createDocumentFragment(); for (const p of hits) { diff --git a/scripts/link-content.sh b/scripts/link-content.sh index df9eec6..a8046b4 100755 --- a/scripts/link-content.sh +++ b/scripts/link-content.sh @@ -6,7 +6,9 @@ set -euo pipefail cd "$(dirname "$0")/.." src="${1:-../ctao-content}" -[ -d "$src/news" ] || { echo "content clone not found at $src (git clone /ctao/content.git first)"; exit 1; } +for d in news pages uploads; do + [ -d "$src/$d" ] || { echo "content clone incomplete: $src/$d missing (git clone /ctao/content.git first)"; exit 1; } +done rm -rf src/content/news src/content/pages public/uploads mkdir -p src/content public cp -a "$src/news" src/content/news diff --git a/src/content.config.ts b/src/content.config.ts index 2bf4978..b620408 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -14,8 +14,9 @@ const news = defineCollection({ date: z.coerce.date(), category: z.string().default('news'), author: z.string().default('CTAO'), - // Public-path string, e.g. "/uploads/foo.jpg" — Sveltia uploads media to - // public/uploads, which Astro's image() helper can't validate (src/ only). + // Public-path string, e.g. "/uploads/foo.jpg" — media lives in the + // content repo's uploads/, overlaid to public/uploads at build time, + // which Astro's image() helper can't validate (src/ only). cover: z.string().optional(), // BCP-47 tag when an article is not in English (e.g. "pl") — set as lang // on the
element so screen readers pick the right voice. diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index dc8cfe5..5ff67bc 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -2,7 +2,7 @@ import '../styles/global.css'; // `ambient` opts a page into the whole-page drifting brand wash (home only — see DESIGN.md) // `type`/`image` feed the social meta: articles pass type="article" + their cover. -const { title = 'CTAO Science Portal', description = 'CTAO Science Portal — demo', ambient = false, type = 'website', image } = Astro.props; +const { title = 'CTAO Science Portal', description = 'CTAO Science Portal demo', ambient = false, type = 'website', image } = Astro.props; // Absolute URLs for canonical/OG/RSS (head pattern from the official Astro // blog template). `site` comes from astro.config.mjs and is always set. const site = Astro.site; @@ -43,7 +43,7 @@ const links = [ - + @@ -59,7 +59,7 @@ const links = [