Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions .github/workflows/studio-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
name: Deploy studio.geolibre.app

# Publishes the gated instance at https://studio.geolibre.app on every push to
# main.
#
# studio.geolibre.app is a *separate* private repository that only hosts the
# built site, so this cannot use actions/deploy-pages — that action deploys the
# Pages site of the repository it runs in. Instead the build is force-pushed as
# a single orphan commit to that repository's `gh-pages` branch, which its Pages
# site serves. Keeping the workflow here means the deployment is versioned
# alongside the code it ships.
#
# This is the same app as web.geolibre.app with the Clerk sign-in gate switched
# on. It is a client-side gate, not a server boundary: see docs/getting-started.md.

on:
push:
branches: [main]
workflow_dispatch:
inputs:
ref:
description: Ref to build and publish (branch, tag, or SHA)
default: main

permissions:
contents: read

concurrency:
# Never let two deploys race for the same force-pushed branch.
group: studio-deploy
cancel-in-progress: true

jobs:
deploy:
name: Build and publish
# Forks have neither the deploy token nor a reason to publish.
if: github.repository == 'opengeos/GeoLibre'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref || github.sha }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# Nothing here uses the checkout's token — npm ci fetches from the
# registry and the publish step carries its own credentials — so do
# not leave it behind in .git/config.
persist-credentials: false

- name: Record the commit that was built
id: built
# $GITHUB_SHA is the SHA of the ref chosen in the "Run workflow"
# dropdown, not the `ref` input typed into the form, so on a manual
# rollback it names main's tip while the build is of something older.
# Resolve what was actually checked out — `ref` may also be a branch or
# tag name rather than a SHA — and label the deployment with that.
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: lts/*
cache: npm
cache-dependency-path: package-lock.json

- name: Install frontend dependencies
run: npm ci

- name: Build the gated web app
run: npm run build -w geolibre-desktop
env:
# No GEOLIBRE_APP_BASE: this site is served from the root of its own
# domain, unlike the /demo/ subpath pages.yml publishes.
#
# The Clerk publishable key, which turns the sign-in gate on. It is
# public by design (it encodes only the Frontend API hostname), so a
# repository variable would do; a secret keeps rotation in one place.
# Unset means no gate, so a missing secret publishes an open app —
# the guard step below fails the run instead.
VITE_GEOLIBRE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_GEOLIBRE_CLERK_PUBLISHABLE_KEY }}
# Clerk's waitlist form. Off unless the repository variable
# GEOLIBRE_CLERK_WAITLIST is set to 1, because the instance is
# invite-only (Clerk sign-up mode "Restricted") by default and a
# waitlist form there would collect requests nobody can approve. An
# unset variable expands to "", which the app reads as unset. Flip it
# in Settings → Variables — no code change, and set the Clerk
# instance's sign-up mode to Waitlist to match.
VITE_GEOLIBRE_CLERK_WAITLIST: ${{ vars.GEOLIBRE_CLERK_WAITLIST }}
# The AI assistant and the NASA OPERA disaster-news lookup. Pages
# cannot proxy, so these are cross-origin calls to the Worker and
# https://studio.geolibre.app must be in its ALLOWED_ORIGINS.
VITE_GEOLIBRE_AI_URL: https://ai.geolibre.app
VITE_GEOLIBRE_AI_MODEL: ${{ vars.VITE_GEOLIBRE_AI_MODEL }}
# Optional third-party keys, as in pages.yml. Each is baked into the
# bundle and readable by any signed-in visitor; the matching features
# hide themselves when unset.
VITE_GEE_OAUTH_CLIENT_ID: ${{ secrets.VITE_GEE_OAUTH_CLIENT_ID }}
VITE_PROTOMAPS_API_KEY: ${{ secrets.VITE_PROTOMAPS_API_KEY }}
VITE_GOOGLE_MAPS_API_KEY: ${{ secrets.VITE_GOOGLE_MAPS_API_KEY }}
VITE_MAPILLARY_ACCESS_TOKEN: ${{ secrets.VITE_MAPILLARY_ACCESS_TOKEN || vars.VITE_MAPILLARY_ACCESS_TOKEN }}
VITE_GEOLIBRE_COLLAB_URL: wss://collab.geolibre.app

- name: Verify the gate is in the bundle
# The point of this deployment is that it is gated. A missing or
# misspelled key builds cleanly and publishes an *open* instance, so
# confirm the configured key reached the bundle before anything is
# pushed.
#
# Matches the key's exact value, not its `pk_test_`/`pk_live_` prefix:
# @clerk/shared ships those prefixes as literals in its own key
# validation, so they appear in the bundle whether or not a key is
# configured, and a prefix check would pass on an ungated build.
env:
CLERK_KEY: ${{ secrets.VITE_GEOLIBRE_CLERK_PUBLISHABLE_KEY }}
run: |
if [ -z "$CLERK_KEY" ]; then
echo "::error::VITE_GEOLIBRE_CLERK_PUBLISHABLE_KEY is unset — refusing to publish an ungated studio.geolibre.app."
exit 1
fi
if ! grep -rqsF -- "$CLERK_KEY" apps/geolibre-desktop/dist/assets; then
echo "::error::The configured Clerk key is not in the build — refusing to publish an ungated studio.geolibre.app."
exit 1
fi

- name: Prepare the site for GitHub Pages
run: |
cd apps/geolibre-desktop/dist
# Pages serves this branch through its legacy (Jekyll) pipeline, which
# silently drops files and directories beginning with an underscore.
touch .nojekyll
# Keeps the custom domain if the Pages settings are ever reset.
echo "studio.geolibre.app" > CNAME
# Published Pages sites may be no larger than 1 GB. The build is
# ~200 MB today, most of it the two DuckDB-WASM binaries; fail early
# rather than publish a site Pages will reject.
size=$(du -sm . | cut -f1)
echo "Built site: ${size} MB"
if [ "$size" -gt 900 ]; then
echo "::error::Site is ${size} MB, at the 1 GB GitHub Pages limit."
exit 1
fi

- name: Publish to opengeos/studio.geolibre.app
env:
# Fine-grained PAT with Contents: write on opengeos/studio.geolibre.app.
STUDIO_DEPLOY_TOKEN: ${{ secrets.STUDIO_DEPLOY_TOKEN }}
BUILT_SHA: ${{ steps.built.outputs.sha }}
run: |
cd apps/geolibre-desktop/dist
git init -q -b gh-pages
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -q -m "Deploy ${BUILT_SHA} from ${GITHUB_REPOSITORY}"
# A fresh orphan commit force-pushed each time, so the hosting
# repository stays the size of one build rather than accumulating a
# ~200 MB commit per merge to main.
git push -q --force \
"https://x-access-token:${STUDIO_DEPLOY_TOKEN}@github.com/opengeos/studio.geolibre.app.git" \
gh-pages
Comment thread
giswqs marked this conversation as resolved.
echo "Published ${BUILT_SHA} to https://studio.geolibre.app"
4 changes: 2 additions & 2 deletions workers/ai-proxy/worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable */
// Generated by Wrangler by running `wrangler types` (hash: d305911522b75e606a41762aa46b28e3)
// Generated by Wrangler by running `wrangler types` (hash: c930d16aa86422cf711cded8b5fc487a)
// Runtime types generated with workerd@1.20260730.1 2026-07-26 nodejs_compat
interface __BaseEnv_Env {
AI_RATE_LIMITER: RateLimit;
ALLOWED_ORIGINS: "https://geolibre.app,https://web.geolibre.app,https://viewer.geolibre.app,http://localhost:5173,http://localhost:1420,tauri://localhost,http://tauri.localhost";
ALLOWED_ORIGINS: "https://geolibre.app,https://web.geolibre.app,https://viewer.geolibre.app,https://studio.geolibre.app,http://localhost:5173,http://localhost:1420,tauri://localhost,http://tauri.localhost";
ALLOWED_MODELS: "openai/gpt-5.5,anthropic/claude-opus-5,anthropic/claude-sonnet-5,google/gemini-3.6-flash,google/gemini-3.5-flash,google/gemini-3.5-flash-lite";
DEFAULT_MODEL: "openai/gpt-5.5";
AI_GATEWAY_ID: "default";
Expand Down
5 changes: 3 additions & 2 deletions workers/ai-proxy/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
"required": [
"CF_AI_GATEWAY_TOKEN",
"CLOUDFLARE_ACCOUNT_ID",
"GEOLIBRE_AI_PROXY_TOKEN"
"GEOLIBRE_AI_PROXY_TOKEN",
"TAVILY_API_KEY"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: adding TAVILY_API_KEY to secrets.required is unrelated to this PR's stated purpose (studio.geolibre.app deploy + ALLOWED_ORIGINS). It looks like a legitimate drift fix — README.md already documents wrangler secret put TAVILY_API_KEY as a setup step — but it's worth calling out explicitly in the PR description (or splitting into its own commit) so reviewers don't wonder whether it's accidental scope creep from a rebase. Low confidence this is a problem at all; just flagging for clarity.

]
},
"vars": {
"ALLOWED_ORIGINS": "https://geolibre.app,https://web.geolibre.app,https://viewer.geolibre.app,http://localhost:5173,http://localhost:1420,tauri://localhost,http://tauri.localhost",
"ALLOWED_ORIGINS": "https://geolibre.app,https://web.geolibre.app,https://viewer.geolibre.app,https://studio.geolibre.app,http://localhost:5173,http://localhost:1420,tauri://localhost,http://tauri.localhost",
Comment thread
giswqs marked this conversation as resolved.
"ALLOWED_MODELS": "openai/gpt-5.5,anthropic/claude-opus-5,anthropic/claude-sonnet-5,google/gemini-3.6-flash,google/gemini-3.5-flash,google/gemini-3.5-flash-lite",
"DEFAULT_MODEL": "openai/gpt-5.5",
"AI_GATEWAY_ID": "default",
Expand Down
Loading