Mac Studio + NetBird + Warpgate
This is the recommended first company rollout shape for an internal-only Oore CI instance:
- Mac Studio runs
ooredand the embedded runner - NetBird provides private network reachability
- Ubuntu runs the browser-facing
oore-webbehind Warpgate and HAProxy
Use this when many users need the UI but only a small operator group may access the Mac Studio. The daemon has no browser-facing route: the AWS host is its only permitted network peer.
Architecture
Browser
-> Warpgate on Ubuntu (auth)
-> HAProxy on Ubuntu
-> oore-web on a separate loopback port
-> NetBird
-> oored on Mac Studio NetBird address:8787
-> embedded runner on Mac StudioIn this shape:
- Mac Studio runs
ooredand the embedded runner. - Ubuntu runs only
oore-webplus the static frontend assets. - Warpgate overwrites
X-Warpgate-Usernamewith the authenticated email before forwarding the request to HAProxy. - HAProxy is reachable only from Warpgate, forwards that identity, and adds the frontend proof header expected by
oore-web. oore-webvalidates the frontend proof, strips browser-controlled identity/proof headers, then injects a separate backend proof when proxying/v1/*to the Mac daemon.- Users add the instance with an empty Backend URL so browser requests stay on the HTTPS frontend origin.
The two proxy proofs are intentionally different:
- Frontend proof: HAProxy ->
oore-web. It proves the identity header came through the authenticated frontend path. - Backend proof:
oore-web->oored. It proves the AWS frontend is the trusted backend peer.
Do not send the backend proof from the browser-facing proxy. Do not configure both hops with one shared value.
Fresh split deployment
Choose an oore-web loopback port that is not already owned by HAProxy. The examples use 4174 because HAProxy commonly owns 4173; keep the existing HAProxy listener unchanged.
1. Install and initialize the Mac backend
Install the backend on the Mac Studio's NetBird address. Backend-owned initialization creates the real owner immediately and avoids the browser bootstrap-token flow:
curl -fsSL https://alpha.oore.pages.dev/install | OORE_CHANNEL=alpha OORE_INSTALL_MODE=backend bashNon-interactive Mac Studio equivalent:
curl -fsSL https://alpha.oore.pages.dev/install | \
OORE_CHANNEL=alpha \
OORE_INSTALL_MODE=backend \
OORE_DAEMON_LISTEN=100.64.10.20:8787 \
OORE_SETUP_OWNER_EMAIL=owner@example.com \
OORE_SETUP_PROXY_PRESET=warpgate \
OORE_TRUSTED_PROXY_CIDRS=100.64.10.30/32 \
OORE_INSTALL_DAEMON_SERVICE=true \
OORE_NONINTERACTIVE=1 \
bashReplace 100.64.10.20 with the Mac NetBird address, 100.64.10.30/32 with the AWS frontend's NetBird address, and use the real initial owner email. Keep OORE_PUBLIC_URL and browser CORS unset because browsers reach the API through same-origin oore-web.
Backend-only macOS installs use a system LaunchDaemon running as the installing account. The installer asks for sudo so the daemon starts at boot without a GUI login session.
When the daemon binds a specific NetBird address, it also opens the same port on loopback for the embedded runner and local operator commands. It does not add a wildcard listener; the NetBird address remains the only non-loopback daemon address.
2. Create a frontend pairing code
On the Mac, after backend setup is ready, create a short-lived single-use code:
oore frontend inviteThe exchange only accepts the Ubuntu host's NetBird address (100.64.10.30/32 in this example), which is already configured through OORE_TRUSTED_PROXY_CIDRS. Treat the code as a secret until the installer consumes it; create a new code if it expires or is used.
3. Install the Ubuntu frontend
Install the frontend-only bundle on an unused loopback port:
curl -fsSL https://alpha.oore.pages.dev/install | OORE_CHANNEL=alpha bashNon-interactive Ubuntu equivalent:
curl -fsSL https://alpha.oore.pages.dev/install | \
OORE_CHANNEL=alpha \
OORE_INSTALL_MODE=frontend \
OORE_WEB_BACKEND_URL=http://100.64.10.20:8787 \
OORE_LOCAL_WEB_LISTEN=127.0.0.1:4174 \
OORE_LOCAL_WEB_MODE=login \
OORE_ENABLE_LINGER=true \
OORE_FRONTEND_PAIRING_CODE=fp_replace_with_the_code \
OORE_NONINTERACTIVE=1 \
bashThe installer exchanges the code over NetBird, saves the returned backend proof, and generates a different local HAProxy -> oore-web proof. It fails before changing service state if the selected port is occupied. Keep oore-web on loopback; HAProxy is the only local process that should call it.
For the Linux user service to survive logout and reboot, the installer can run this when OORE_ENABLE_LINGER=true:
sudo loginctl enable-linger "$USER"4. Point HAProxy at oore-web
The existing HAProxy frontend keeps its current listener. Its Oore backend must target the separate loopback port:
backend oore_web
mode http
http-request del-header X-Oore-Trusted-Proxy-Secret
http-request set-header X-Oore-Web-Trusted-Proxy-Secret "${OORE_WEB_FRONTEND_PROOF}"
server oore_web 127.0.0.1:4174 checkOORE_WEB_FRONTEND_PROOF represents the protected HAProxy runtime value matching the separate local proof generated by the frontend installer; wire it using your existing HAProxy service-secret mechanism. Warpgate must overwrite, not merely pass through, X-Warpgate-Username. Network policy must ensure clients cannot reach this HAProxy listener without Warpgate.
Manual backend-proof transfer and a manually managed frontend proof remain an advanced fallback. Configure OORE_TRUSTED_PROXY_SHARED_SECRET_FILE and OORE_WEB_UPSTREAM_TRUSTED_PROXY_SHARED_SECRET_FILE with different mode-0600 files only when you intentionally manage that distribution yourself.
5. Verify before opening access
On the Mac:
curl -fsS http://127.0.0.1:8787/readyzThe loopback readiness request must succeed. After signing in, Runners must show the embedded runner as online; backend readiness alone is not sufficient for a testable build host.
On Ubuntu:
oore-web status --url http://127.0.0.1:4174
systemctl --user status oore-web.service --no-pagerThe status command must report both the frontend launcher and Mac backend ready before HAProxy is pointed at the service.
From the browser, open the Warpgate-protected public URL. The authenticated email matching OORE_SETUP_OWNER_EMAIL signs in as the existing owner; there is no placeholder owner@local account and no database role swap.
Configure External Access public_url and allowed_origins with that same HTTPS URL after login.
6. Configure non-interactive iOS installs
Warpgate normally expects an interactive browser session, while Apple's iOS installer fetches the manifest and IPA as separate system requests. Create a Warpgate access ticket bound to a dedicated, least-privilege user and the Oore target. Give it the shortest practical expiry and use allowance for your QA workflow. Do not use an owner or staff user's ticket. Do not invite the dedicated ticket identity into Oore; the short-lived Oore artifact token, not that identity, authorizes the requested IPA.
In Oore, open Preferences → Identity settings. The iOS install ticket field appears only when the trusted-proxy identity header is x-warpgate-username. Paste the ticket and save. Oore encrypts it at rest and reports only whether it exists and whether it came from encrypted settings or the environment.
For service-managed fresh installs, add OORE_WARPGATE_TICKET=replace_with_ticket to the non-interactive backend installer environment shown earlier. For an existing instance, prefer the UI; changing a LaunchDaemon environment directly requires reinstalling it with its complete current listen, user, state-file, and environment arguments.
The Preferences value takes precedence when both sources exist. Remove the stored value in Preferences to return to the environment fallback. Restart the daemon after changing its service environment.
For iOS only, Oore appends warpgate-ticket to the manifest URL, the IPA URL inside the manifest, and the final local-storage download redirect. Warpgate accepts the ticket from that query parameter before the request reaches Oore; Oore then independently validates its short-lived artifact token. Android, OIDC, Local Only mode, and generic trusted-proxy deployments never receive the Warpgate query parameter.
The ticket can appear in browser history and ingress access logs. Keep access to those logs restricted, rotate the ticket when exposure is suspected, and retain NetBird as the private network boundary. A separate unauthenticated /install/ ingress bypass is not required in this topology.
Verify the public ingress accepts the ticket but Oore still rejects a fake artifact token:
curl -i \
'https://oore.example.com/install/ios/not-a-token/manifest.plist?warpgate-ticket=replace_with_ticket'The response must be Oore JSON with 401 invalid_token, not a Warpgate login page. Run this from a device on the same NetBird policy used by QA users.
Common mistakes
- Binding
ooredto0.0.0.0: bind only the Mac private/NetBird address used by the AWS frontend. - Reusing HAProxy's listener port for
oore-web: the launcher needs its own loopback port. - Reusing one secret for both proxy hops: compromise of one boundary would then compromise both.
- Sending
X-Oore-Trusted-Proxy-Secretfrom HAProxy:oore-webstrips it and injects its own backend proof. - Using HTTP for the browser-visible origin: External Access expects a non-loopback HTTPS origin.
- Serving the UI from one origin and API from another without adding the UI origin to
allowed_origins. - Forgetting to forward
X-Warpgate-Usernameto/v1/*. - Passing a username instead of an email in the trusted-proxy header.
- Creating the install ticket for a privileged Warpgate user instead of a dedicated target-only identity.
- Configuring a ticket while Oore uses OIDC or a different identity header; the integration intentionally remains inactive.
When to use OIDC instead
Choose OIDC instead of trusted proxy when:
- Warpgate is not the identity boundary
- users access the instance directly rather than through the proxy
- you want the browser to complete the standard OIDC redirect flow against your IdP