fix(emulator): move mock OAuth off 8114 to avoid pnpm dev conflict (#1385)

## Summary
- The emulator's mock OAuth server bound to `${PORT_PREFIX}14` (8114)
inside the VM and the host forwarded the same port, colliding with `pnpm
dev`'s mock-oauth-server on 8114.
- Moves the emulator's mock OAuth to `EMULATOR_MOCK_OAUTH_PORT` (default
`26704`, joining the existing `267xx` host port block) and binds the
VM-internal mock to the same port. Same port on both sides keeps the
OIDC issuer URL (`http://localhost:26704`) resolvable identically from
the browser and from the backend inside the VM.
- Plumbed via `runtime-config.iso` as
`STACK_EMULATOR_MOCK_OAUTH_HOST_PORT`, read by cloud-init into
`STACK_OAUTH_MOCK_URL` + new `STACK_OAUTH_MOCK_PORT`;
`mock-oauth-server` now prefers `STACK_OAUTH_MOCK_PORT` so `pnpm dev`
(which doesn't set it) stays on 8114.

## Files
- `docker/local-emulator/qemu/run-emulator.sh` — new
`EMULATOR_MOCK_OAUTH_PORT`, hostfwd/ensure_ports_free/runtime.env
updates
- `docker/local-emulator/qemu/cloud-init/emulator/user-data` — reads the
host port, sets `STACK_OAUTH_MOCK_URL` + `STACK_OAUTH_MOCK_PORT`
- `apps/mock-oauth-server/src/index.ts` — honors `STACK_OAUTH_MOCK_PORT`
- `packages/stack-cli/src/commands/emulator.ts` — default + runtime.env
entry

## Test plan
- [ ] `pnpm emulator:build` succeeds and new snapshot boots
- [ ] `stack emulator start` with `pnpm dev` running on 8114 — no port
collision
- [ ] OAuth sign-in via mock provider completes end-to-end in the
emulator
- [ ] `pnpm dev` mock OAuth unchanged (still 8114)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* The mock OAuth server port is now configurable in the local emulator
with a sensible default, allowing custom port assignments via
environment variable.

* **Improvements**
* Updated port forwarding and environment variable handling to ensure
consistent mock OAuth endpoint configuration across host and guest
systems in the emulator.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
BilalG1 2026-04-27 09:39:34 -07:00 committed by GitHub
parent 2f719903b1
commit 04d57d91ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 21 additions and 9 deletions

View File

@ -5,7 +5,7 @@ import Provider, { errors } from 'oidc-provider';
const stackPortPrefix = process.env.NEXT_PUBLIC_STACK_PORT_PREFIX ?? "81";
const defaultMockOAuthPort = Number(`${stackPortPrefix}14`);
const port = Number(process.env.PORT ?? defaultMockOAuthPort);
const port = Number(process.env.STACK_OAUTH_MOCK_PORT ?? process.env.PORT ?? defaultMockOAuthPort);
const backendPortForRedirects = `${stackPortPrefix}02`;
const emulatorBackendPort = process.env.STACK_EMULATOR_BACKEND_PORT ?? "32102";
const providerIds = [

View File

@ -130,6 +130,11 @@ write_files:
HP_DASHBOARD="$STACK_EMULATOR_DASHBOARD_HOST_PORT"
HP_MINIO="$STACK_EMULATOR_MINIO_HOST_PORT"
HP_INBUCKET="$STACK_EMULATOR_INBUCKET_HOST_PORT"
# Mock OAuth binds to this port inside the VM and the host forwards the
# same port through, so the OIDC issuer URL is reachable identically
# from the browser and from the backend. Falls back to ${P}14 for
# older ISOs that don't set it.
HP_MOCK_OAUTH="${STACK_EMULATOR_MOCK_OAUTH_HOST_PORT:-${P}14}"
cat <<COMPUTED
STACK_SKIP_MIGRATIONS=true
@ -153,7 +158,8 @@ write_files:
STACK_CLICKHOUSE_URL=http://${DEPS_HOST}:8123
STACK_EMAIL_MONITOR_VERIFICATION_CALLBACK_URL=http://localhost:${HP_DASHBOARD}/handler/email-verification
STACK_EMAIL_MONITOR_INBUCKET_API_URL=http://${DEPS_HOST}:9001
STACK_OAUTH_MOCK_URL=http://localhost:${P}14
STACK_OAUTH_MOCK_URL=http://localhost:${HP_MOCK_OAUTH}
STACK_OAUTH_MOCK_PORT=${HP_MOCK_OAUTH}
STACK_FREESTYLE_API_ENDPOINT=http://${DEPS_HOST}:8180
STACK_STRIPE_MOCK_PORT=12111
NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY=pk_test_mock_publishable_key_for_local_emulator

View File

@ -35,6 +35,7 @@ EMULATOR_DASHBOARD_PORT="${EMULATOR_DASHBOARD_PORT:-26700}"
EMULATOR_BACKEND_PORT="${EMULATOR_BACKEND_PORT:-26701}"
EMULATOR_MINIO_PORT="${EMULATOR_MINIO_PORT:-26702}"
EMULATOR_INBUCKET_PORT="${EMULATOR_INBUCKET_PORT:-26703}"
EMULATOR_MOCK_OAUTH_PORT="${EMULATOR_MOCK_OAUTH_PORT:-26704}"
RED='\033[0;31m'
GREEN='\033[0;32m'
@ -214,6 +215,7 @@ write_runtime_config_iso() {
printf "STACK_EMULATOR_BACKEND_HOST_PORT=%s\n" "$EMULATOR_BACKEND_PORT"
printf "STACK_EMULATOR_MINIO_HOST_PORT=%s\n" "$EMULATOR_MINIO_PORT"
printf "STACK_EMULATOR_INBUCKET_HOST_PORT=%s\n" "$EMULATOR_INBUCKET_PORT"
printf "STACK_EMULATOR_MOCK_OAUTH_HOST_PORT=%s\n" "$EMULATOR_MOCK_OAUTH_PORT"
printf "STACK_EMULATOR_VM_DIR_HOST=%s\n" "$vm_dir_host"
} > "$cfg_dir/runtime.env"
cp "$base_env" "$cfg_dir/base.env"
@ -351,11 +353,12 @@ build_qemu_cmd() {
netdev+=",hostfwd=tcp:127.0.0.1:${EMULATOR_BACKEND_PORT}-:${PORT_PREFIX}02"
netdev+=",hostfwd=tcp:127.0.0.1:${EMULATOR_MINIO_PORT}-:9090"
netdev+=",hostfwd=tcp:127.0.0.1:${EMULATOR_INBUCKET_PORT}-:9001"
# Mock OAuth server: browser redirects land on `localhost:${PORT_PREFIX}14`
# (backend sets STACK_OAUTH_MOCK_URL to that value), so we forward host:port
# ↔ VM:port on the same number. Collides with pnpm dev, but the two modes
# are mutually exclusive.
netdev+=",hostfwd=tcp:127.0.0.1:${PORT_PREFIX}14-:${PORT_PREFIX}14"
# Mock OAuth server: the VM-internal mock binds to $EMULATOR_MOCK_OAUTH_PORT
# (overrides the pnpm-dev default of ${PORT_PREFIX}14 via STACK_OAUTH_MOCK_PORT
# threaded through runtime-config.iso). Host and guest use the same port so
# the OIDC issuer URL `http://localhost:${EMULATOR_MOCK_OAUTH_PORT}` resolves
# identically from the browser and from the backend inside the VM.
netdev+=",hostfwd=tcp:127.0.0.1:${EMULATOR_MOCK_OAUTH_PORT}-:${EMULATOR_MOCK_OAUTH_PORT}"
# In snapshot-resume mode the QEMU command-line MUST match the device set
# used at snapshot capture time, otherwise migration replay fails (broken
@ -499,7 +502,7 @@ tail_vm_logs() {
}
ensure_ports_free() {
local ports=("$EMULATOR_DASHBOARD_PORT" "$EMULATOR_BACKEND_PORT" "$EMULATOR_MINIO_PORT" "$EMULATOR_INBUCKET_PORT" "${PORT_PREFIX}14")
local ports=("$EMULATOR_DASHBOARD_PORT" "$EMULATOR_BACKEND_PORT" "$EMULATOR_MINIO_PORT" "$EMULATOR_INBUCKET_PORT" "$EMULATOR_MOCK_OAUTH_PORT")
local port
for port in "${ports[@]}"; do
if lsof -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; then
@ -761,7 +764,7 @@ cmd_start() {
info "Starting QEMU local emulator"
info "Arch: $ARCH | Accel: $ACCEL"
info "Ports: Dashboard=$EMULATOR_DASHBOARD_PORT Backend=$EMULATOR_BACKEND_PORT MinIO=$EMULATOR_MINIO_PORT Inbucket=$EMULATOR_INBUCKET_PORT"
info "Ports: Dashboard=$EMULATOR_DASHBOARD_PORT Backend=$EMULATOR_BACKEND_PORT MinIO=$EMULATOR_MINIO_PORT Inbucket=$EMULATOR_INBUCKET_PORT MockOAuth=$EMULATOR_MOCK_OAUTH_PORT"
local using_snapshot=0
if snapshot_available; then

View File

@ -14,6 +14,7 @@ const DEFAULT_EMULATOR_BACKEND_PORT = 26701;
const DEFAULT_EMULATOR_DASHBOARD_PORT = 26700;
const DEFAULT_EMULATOR_MINIO_PORT = 26702;
const DEFAULT_EMULATOR_INBUCKET_PORT = 26703;
const DEFAULT_EMULATOR_MOCK_OAUTH_PORT = 26704;
const DEFAULT_PORT_PREFIX = "81";
const GITHUB_API = "https://api.github.com";
const DEFAULT_REPO = "stack-auth/stack-auth";
@ -188,6 +189,7 @@ function prepareRuntimeConfigIso(): void {
const backendPort = envPort("EMULATOR_BACKEND_PORT", DEFAULT_EMULATOR_BACKEND_PORT);
const minioPort = envPort("EMULATOR_MINIO_PORT", DEFAULT_EMULATOR_MINIO_PORT);
const inbucketPort = envPort("EMULATOR_INBUCKET_PORT", DEFAULT_EMULATOR_INBUCKET_PORT);
const mockOAuthPort = envPort("EMULATOR_MOCK_OAUTH_PORT", DEFAULT_EMULATOR_MOCK_OAUTH_PORT);
const runtimeEnv = [
`STACK_EMULATOR_PORT_PREFIX=${portPrefix}`,
@ -195,6 +197,7 @@ function prepareRuntimeConfigIso(): void {
`STACK_EMULATOR_BACKEND_HOST_PORT=${backendPort}`,
`STACK_EMULATOR_MINIO_HOST_PORT=${minioPort}`,
`STACK_EMULATOR_INBUCKET_HOST_PORT=${inbucketPort}`,
`STACK_EMULATOR_MOCK_OAUTH_HOST_PORT=${mockOAuthPort}`,
`STACK_EMULATOR_VM_DIR_HOST=${vmDir}`,
"",
].join("\n");