Cloud dev server recipe in the repo, with an optional OIDC sign-in
deployment/cloud/: a bootstrap Dockerfile (Bun, git, oauth2-proxy v7.15.3) that clones the repo and runs deployment/cloud/entry.sh from it, so the recipe updates with git; the Vite overlay (host, allowed hosts, HMR over wss); and the pull loop that follows the branch. With no OIDC env it serves Vite directly (laptop default unchanged). With OIDC_ISSUER set, oauth2-proxy owns the port and Vite listens on localhost behind it: Studio, maps and the HMR websocket all sit behind one sign-in, with PKCE S256, a confidential client and a REQUIRED group allow-list (it refuses to start without one). Secrets reach oauth2-proxy through its environment only. README gets a short 'Run it in the cloud' section linking the recipe.
This commit is contained in:
parent
c42fc4471b
commit
f57435253c
5 changed files with 161 additions and 0 deletions
|
|
@ -616,6 +616,14 @@ The studio is a plugin like any other: `plugin-designs/plugin-studio/plugin-stud
|
|||
| `bun run gates:pre` · `bun run gates:post` | The checks that keep `main` working |
|
||||
| `bun run shots` | Retake these screenshots (`ONLY=<name>` for one) |
|
||||
|
||||
### Run it in the cloud
|
||||
|
||||
**One shared dev server for the whole team, no laptop setup.** A container clones the repo, follows
|
||||
`main`, and hot-reloads every change an agent lands, so everyone sees the Plugin Studio update in
|
||||
their browser within seconds, with no rebuild. Add OIDC settings and it puts a "Sign in with *your
|
||||
provider*" page in front of everything, open only to the groups you allow. Recipe and settings:
|
||||
[deployment/cloud/](./deployment/cloud/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Repository layout
|
||||
|
|
|
|||
21
deployment/cloud/Dockerfile
Normal file
21
deployment/cloud/Dockerfile
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# dx-maps cloud dev server: one shared Vite dev server with hot reload for a whole team.
|
||||
# This image is only a bootstrap: at start it clones the repo and runs deployment/cloud/entry.sh
|
||||
# FROM THE REPO, so the recipe lives in git and updates with it. Paste this file as-is into a
|
||||
# container host (Coolify "Dockerfile" build pack, Railway, or `docker build`).
|
||||
FROM quay.io/oauth2-proxy/oauth2-proxy:v7.15.3 AS oauth2proxy
|
||||
FROM oven/bun:1-debian
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=oauth2proxy /bin/oauth2-proxy /usr/local/bin/oauth2-proxy
|
||||
RUN printf '%s\n' \
|
||||
'#!/bin/bash' \
|
||||
'set -u; export GIT_TERMINAL_PROMPT=0' \
|
||||
'T="${GIT_TOKEN:-${FORGEJO_TOKEN:-}}"' \
|
||||
'if [ -n "$T" ]; then printf "#!/bin/sh\ncase \"\$1\" in *sername*) echo \"\$GIT_TOKEN_EFF\";; *) echo;; esac\n" > /tmp/askpass.sh; chmod 700 /tmp/askpass.sh; export GIT_ASKPASS=/tmp/askpass.sh GIT_TOKEN_EFF="$T"; fi' \
|
||||
': "${REPO_URL:?set REPO_URL to the dx-maps git URL}"' \
|
||||
'[ -d /app/repo/.git ] || git clone -q --depth 1 --branch "${BRANCH:-main}" "$REPO_URL" /app/repo || { echo "clone failed"; sleep 60; exit 1; }' \
|
||||
'cp /app/repo/deployment/cloud/entry.sh /tmp/entry.sh && exec bash /tmp/entry.sh' \
|
||||
> /boot.sh && chmod 755 /boot.sh
|
||||
ENV PORT=5173 BRANCH=main PULL_INTERVAL=15
|
||||
EXPOSE 5173
|
||||
CMD ["/boot.sh"]
|
||||
45
deployment/cloud/README.md
Normal file
45
deployment/cloud/README.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Run dx-maps in the cloud: one shared dev server for a team
|
||||
|
||||
One container runs the Plugin Studio with hot reload for everyone. It clones the repo, pulls the
|
||||
branch every few seconds, and Vite hot-reloads whatever changed, so every change an agent lands
|
||||
on `main` shows up in everyone's browser within seconds. No rebuild; `bun run build` is only for
|
||||
publishing.
|
||||
|
||||
With **no OIDC settings** it runs open, like `bun run dev` on a laptop. With **OIDC settings** a
|
||||
sign-in page ("Sign in with *your provider*") sits in front of everything: the Studio, every map,
|
||||
and the hot-reload websocket. Only people in the allowed groups get in.
|
||||
|
||||
## Deploy
|
||||
|
||||
1. Build the image from [`Dockerfile`](./Dockerfile). It's a small bootstrap (Bun, git, and the
|
||||
[oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy) binary); at start it clones the repo
|
||||
and runs [`entry.sh`](./entry.sh) from it, so the recipe updates with the repo. Paste it into any
|
||||
container host (Coolify's "Dockerfile" build pack, Railway, `docker build`).
|
||||
2. Set the environment below and expose the container's `PORT` over HTTPS.
|
||||
3. Give it about 2 GB of memory (Vite plus the dependency install).
|
||||
|
||||
| Variable | Needed | Meaning |
|
||||
|---|---|---|
|
||||
| `REPO_URL` | yes | the dx-maps git URL to clone |
|
||||
| `BRANCH` | no (`main`) | the branch to follow |
|
||||
| `PULL_INTERVAL` | no (`15`) | seconds between pulls |
|
||||
| `GIT_TOKEN` | private repos | a read token for the clone and pulls |
|
||||
| `PORT` | no (`5173`) | the port the container serves on |
|
||||
| `HMR_CLIENT_PORT`, `HMR_PROTOCOL` | behind HTTPS | `443` and `wss`, so browsers reach the hot-reload websocket |
|
||||
|
||||
## Sign-in (optional)
|
||||
|
||||
Set all of these to turn the gate on. Register a **confidential** client at your OIDC provider
|
||||
with the redirect URL `https://<your-host>/oauth2/callback`; PKCE (S256) is used as well.
|
||||
|
||||
| Variable | Meaning |
|
||||
|---|---|
|
||||
| `OIDC_ISSUER` | the provider's issuer URL (its `/.well-known/openid-configuration` must resolve) |
|
||||
| `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET` | the registered client |
|
||||
| `OIDC_REDIRECT_URL` | `https://<your-host>/oauth2/callback` |
|
||||
| `OIDC_ALLOWED_GROUPS` | comma-separated groups allowed in; **required** (the server refuses to start without it, because many providers allow open sign-up) |
|
||||
| `OIDC_DISPLAY_NAME` | the name on the button, e.g. "Sign in with *Acme*" |
|
||||
| `OIDC_GROUPS_CLAIM` | the token claim holding groups (default `groups`) |
|
||||
| `OIDC_COOKIE_SECRET` | 32 characters; keeps people signed in across restarts (random if unset) |
|
||||
|
||||
Secrets reach oauth2-proxy through its environment, never its command line.
|
||||
65
deployment/cloud/entry.sh
Executable file
65
deployment/cloud/entry.sh
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
#!/bin/bash
|
||||
# dx-maps cloud dev server, run by the image's /boot.sh from the cloned repo (see Dockerfile).
|
||||
# 1. install, 2. pull BRANCH every PULL_INTERVAL s (Vite hot-reloads what changed),
|
||||
# 3. serve: Vite alone, or behind an OIDC sign-in when OIDC_ISSUER is set.
|
||||
set -u
|
||||
cd /app/repo
|
||||
BRANCH="${BRANCH:-main}"; PORT="${PORT:-5173}"
|
||||
echo "$(date -u +%FT%TZ) START at $(git rev-parse --short HEAD)" | tee -a /tmp/pull.log
|
||||
bun install --frozen-lockfile || bun install
|
||||
|
||||
(
|
||||
while true; do
|
||||
sleep "${PULL_INTERVAL:-15}"
|
||||
OLD=$(git rev-parse HEAD)
|
||||
if git fetch -q --depth 1 origin "$BRANCH"; then
|
||||
NEW=$(git rev-parse FETCH_HEAD)
|
||||
if [ "$OLD" != "$NEW" ]; then
|
||||
git reset -q --hard FETCH_HEAD
|
||||
echo "$(date -u +%FT%TZ) PULLED ${OLD:0:8} -> ${NEW:0:8}" >> /tmp/pull.log
|
||||
fi
|
||||
fi
|
||||
done
|
||||
) &
|
||||
|
||||
CONFIG=/app/repo/deployment/cloud/vite.cloud.config.mjs
|
||||
|
||||
if [ -z "${OIDC_ISSUER:-}" ]; then
|
||||
# Open, like a laptop: no sign-in.
|
||||
export VITE_HOST=0.0.0.0 VITE_PORT="$PORT"
|
||||
exec bun x vite --config "$CONFIG"
|
||||
fi
|
||||
|
||||
# OIDC gate: oauth2-proxy owns the public port; Vite listens on localhost behind it, so the Plugin
|
||||
# Studio, every map and the hot-reload websocket are all behind the same sign-in.
|
||||
: "${OIDC_CLIENT_ID:?}" "${OIDC_CLIENT_SECRET:?}" "${OIDC_REDIRECT_URL:?}"
|
||||
if [ -z "${OIDC_ALLOWED_GROUPS:-}" ]; then
|
||||
echo "refusing to start: set OIDC_ALLOWED_GROUPS (comma-separated). With open sign-up, anyone could log in." >&2
|
||||
exit 1
|
||||
fi
|
||||
ALLOW=(); IFS=',' read -ra G <<< "$OIDC_ALLOWED_GROUPS"
|
||||
for g in "${G[@]}"; do g="${g// /}"; [ -n "$g" ] && ALLOW+=(--allowed-group="$g"); done
|
||||
|
||||
# Secrets reach oauth2-proxy through its environment, never its command line.
|
||||
export OAUTH2_PROXY_CLIENT_SECRET="$OIDC_CLIENT_SECRET"
|
||||
export OAUTH2_PROXY_COOKIE_SECRET="${OIDC_COOKIE_SECRET:-$(head -c 32 /dev/urandom | base64 | tr -d '=+/' | head -c 32)}"
|
||||
|
||||
VITE_HOST=127.0.0.1 VITE_PORT=5174 bun x vite --config "$CONFIG" &
|
||||
oauth2-proxy \
|
||||
--provider=oidc \
|
||||
--oidc-issuer-url="$OIDC_ISSUER" \
|
||||
--client-id="$OIDC_CLIENT_ID" \
|
||||
--redirect-url="$OIDC_REDIRECT_URL" \
|
||||
--code-challenge-method=S256 \
|
||||
--scope="${OIDC_SCOPE:-openid profile email groups}" \
|
||||
--oidc-groups-claim="${OIDC_GROUPS_CLAIM:-groups}" \
|
||||
"${ALLOW[@]}" \
|
||||
--email-domain='*' \
|
||||
--provider-display-name="${OIDC_DISPLAY_NAME:-your identity provider}" \
|
||||
--upstream=http://127.0.0.1:5174/ \
|
||||
--http-address="0.0.0.0:$PORT" \
|
||||
--reverse-proxy=true \
|
||||
--cookie-secure=true \
|
||||
--cookie-samesite=lax &
|
||||
wait -n
|
||||
exit $?
|
||||
22
deployment/cloud/vite.cloud.config.mjs
Normal file
22
deployment/cloud/vite.cloud.config.mjs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Overlay for the cloud dev server: the repo's own vite.config.js plus what a shared, public
|
||||
// host needs. Every value comes from the environment (see deployment/cloud/README.md).
|
||||
import base from '/app/repo/vite.config.js'
|
||||
import { mergeConfig } from 'vite'
|
||||
|
||||
const env = process.env
|
||||
export default async (e) => {
|
||||
const b = typeof base === 'function' ? await base(e) : base
|
||||
const hmr = env.HMR_CLIENT_PORT
|
||||
? { clientPort: Number(env.HMR_CLIENT_PORT), protocol: env.HMR_PROTOCOL || 'wss' }
|
||||
: undefined
|
||||
return mergeConfig(b, {
|
||||
cacheDir: '/tmp/vite-cache',
|
||||
server: {
|
||||
host: env.VITE_HOST || '0.0.0.0',
|
||||
port: Number(env.VITE_PORT || 5173),
|
||||
strictPort: true,
|
||||
allowedHosts: true,
|
||||
...(hmr ? { hmr } : {}),
|
||||
},
|
||||
})
|
||||
}
|
||||
Loading…
Reference in a new issue