diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000000..8f4bed69c2
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,8 @@
+# Copy to .env and fill in real values for local development. .env is gitignored --
+# never commit real values here or in .env itself.
+
+# Google OAuth client id used by the login/signup and password-reset OAuth flows
+# (see the comment on google_client_id in _config.yml for why this isn't just
+# hardcoded there). Ask a maintainer for the real value -- it's the same one
+# production uses, stored as a GitHub Actions repository secret for CI.
+GOOGLE_CLIENT_ID=
diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml
index ee6b66e0b5..fb77a80b46 100644
--- a/.github/workflows/jekyll-gh-pages.yml
+++ b/.github/workflows/jekyll-gh-pages.yml
@@ -101,6 +101,16 @@ jobs:
echo "baseurl: \"$BASEURL\"" > _config.override.yml
+ # google_client_id must never be committed to the repo (see the comment on
+ # it in _config.yml) -- injected here from a repository secret instead,
+ # into the same gitignored override file used for baseurl above. Requires
+ # a repo admin to add the GOOGLE_CLIENT_ID secret in Settings > Secrets and
+ # variables > Actions; the site builds fine without it, the Google sign-in
+ # button just won't render until it's set.
+ if [ -n "${{ secrets.GOOGLE_CLIENT_ID }}" ]; then
+ echo "google_client_id: \"${{ secrets.GOOGLE_CLIENT_ID }}\"" >> _config.override.yml
+ fi
+
- name: Generate dynamic SASS imports
run: |
source venv/bin/activate
diff --git a/Makefile b/Makefile
index 5ca228928c..7c5dbe1614 100644
--- a/Makefile
+++ b/Makefile
@@ -480,11 +480,13 @@ bundle-install:
fi
# Start Jekyll server (no auto-watch, we control rebuilds manually)
-# Supports optional _config.local.yml override for local settings (e.g. baseurl)
+# Supports optional _config.local.yml override for local settings (e.g. baseurl,
+# google_client_id -- see scripts/generate_local_config_override.sh and .env.example)
jekyll-serve: bundle-install
@touch /tmp/.notebook_watch_marker
@rm -f /tmp/.jekyll_rebuild_trigger
- bundle exec jekyll serve -H $(HOST) -P $(PORT) --no-watch > $(LOG_FILE) 2>&1 &
+ @./scripts/generate_local_config_override.sh
+ bundle exec jekyll serve -H $(HOST) -P $(PORT) --no-watch --config _config.yml,_config.local.yml > $(LOG_FILE) 2>&1 &
@make wait-for-server
# Common server wait logic
diff --git a/_config.yml b/_config.yml
index 680b6c3f74..f6098b9037 100644
--- a/_config.yml
+++ b/_config.yml
@@ -20,8 +20,17 @@ description: "Class of 2026"
owner_name: Open Coding Society
github_username: open-coding-society
github_repo: "pages"
-baseurl: ""
+baseurl: ""
future: true
+# google_client_id is intentionally NOT set here -- per John Mortensen's review on
+# PR #1371, it must not be committed to the public repo. It's injected at build time
+# into a gitignored override config instead: locally into _config.local.yml via
+# scripts/generate_local_config_override.sh reading .env (see .env.example), in CI
+# into _config.override.yml via the "Compute and apply baseurl" step in
+# .github/workflows/jekyll-gh-pages.yml (which already generates that file for
+# baseurl) reading a repository secret. Either way, assets/js/api/config.js's
+# GOOGLE_CLIENT_ID and login.md's #g_id_onload data-client_id pull the same
+# google_client_id key via Liquid -- one source of truth, just never a committed one.
# Exclude from Jekyll watch - these are processed by our conversion scripts
# This prevents double-regeneration when saving notebooks/docx files
diff --git a/_layouts/profile.html b/_layouts/profile.html
index b19f088105..49cb09a16f 100644
--- a/_layouts/profile.html
+++ b/_layouts/profile.html
@@ -58,11 +58,8 @@
+
+
diff --git a/scripts/generate_local_config_override.sh b/scripts/generate_local_config_override.sh
new file mode 100755
index 0000000000..89f5cefc5d
--- /dev/null
+++ b/scripts/generate_local_config_override.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# Writes _config.local.yml (gitignored -- see Makefile's jekyll-serve comment, this
+# is the file it already documents as "optional _config.local.yml override for local
+# settings") from local .env values that must never be committed to the repo --
+# currently just GOOGLE_CLIENT_ID (see .env.example and the comment on
+# google_client_id in _config.yml). Mirrors the same idea
+# .github/workflows/jekyll-gh-pages.yml uses for CI (its own _config.override.yml,
+# generated fresh each run there), just sourced from a local .env instead of a
+# repository secret. Safe to run even with no .env present: writes a file with
+# google_client_id left unset, and Jekyll serves fine without it (the Google sign-in
+# button just won't render).
+set -euo pipefail
+
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+if [ -f .env ]; then
+ set -a
+ # shellcheck disable=SC1091
+ source .env
+ set +a
+fi
+
+{
+ echo "# Auto-generated by scripts/generate_local_config_override.sh -- do not edit"
+ echo "# or commit; re-run this script (or just start-dev.sh) after editing .env."
+ if [ -n "${GOOGLE_CLIENT_ID:-}" ]; then
+ echo "google_client_id: \"${GOOGLE_CLIENT_ID}\""
+ fi
+} > _config.local.yml