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
16 changes: 5 additions & 11 deletions .github/workflows/deploy-relay.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ jobs:
- --filter=t3code-relay...

- name: Deploy production relay stage
id: deploy
run: vp run --filter t3code-relay deploy --stage prod --yes --github-output
run: vp run --filter t3code-relay deploy --stage prod --yes --no-input
env:
# The PublishClientConfig action writes the client env here instead
# of the repo-root .env; nothing on the runner reads it.
T3CODE_RELAY_CLIENT_CONFIG_ENV: ${{ runner.temp }}/relay-client-config.env
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
PLANETSCALE_API_TOKEN_ID: ${{ secrets.PLANETSCALE_API_TOKEN_ID }}
PLANETSCALE_API_TOKEN: ${{ secrets.PLANETSCALE_API_TOKEN }}
Expand All @@ -69,20 +71,12 @@ jobs:
uses: actions/github-script@v8
with:
script: |
const result = "${{ steps.deploy.outputs.result }}";
const changed = "${{ steps.deploy.outputs.changed }}" === "true";
const description = changed
? "Relay production deploy applied infrastructure changes."
: result === "noop"
? "Relay production deploy was a no-op."
: `Relay production deploy completed with result: ${result}.`;

await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: "success",
context: "Relay deploy / production",
description,
description: "Relay production deploy completed.",
target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
});
27 changes: 20 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -311,15 +311,28 @@ jobs:
args:
- --filter=t3code-relay...

- id: relay_state
name: Read production relay tracing config
# The deployed stack's outputs, read from Alchemy's state store without
# planning or applying. Redacted values are persisted as
# {"__redacted__": "<value>"}; the token is masked before it is written.
- name: Read production relay tracing config
shell: bash
working-directory: infra/relay
run: |
vp run --filter t3code-relay deploy \
--stage prod \
--read-state \
--github-output \
--github-env-file "$RUNNER_TEMP/relay-client-tracing.env"
set -euo pipefail
output="$(npx alchemy state read T3CodeRelay/prod/output --no-input)"
field() {
jq -er --arg key "$1" '.[$key] | if type == "object" then .__redacted__ else . end | select(. != null and . != "")' <<<"$output" \
|| { echo "Relay stack output is missing $1" >&2; exit 1; }
}
url="$(field clientTracingUrl)"
dataset="$(field clientTracingDataset)"
token="$(field clientTracingToken)"
echo "::add-mask::$token"
{
echo "T3CODE_RELAY_CLIENT_OTLP_TRACES_URL=$url"
echo "T3CODE_RELAY_CLIENT_OTLP_TRACES_DATASET=$dataset"
echo "T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=$token"
} > "$RUNNER_TEMP/relay-client-tracing.env"

- name: Upload relay client tracing config
uses: actions/upload-artifact@v7
Expand Down
2 changes: 1 addition & 1 deletion docs/operations/connect-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Bundled servers also accept runtime overrides for operator-managed deployments.

Copy `infra/relay/.env.example` to `infra/relay/.env` for relay deployment settings.
Deploy `prod` before personal stages because it owns the retained database that their branches
depend on. The deploy wrapper writes the resulting relay URL back to the root `.env`.
depend on. The stack's `PublishClientConfig` action writes the resulting relay URL back to the root `.env`.

## CLI OAuth application

Expand Down
10 changes: 6 additions & 4 deletions infra/relay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ dependencies represented at their boundary rather than mocking internal behavior

## Deployment

The relay deploys through Alchemy:
The relay deploys with the Alchemy CLI (`vp run --filter t3code-relay deploy` is `alchemy deploy`
in this directory):

```sh
vp run --filter t3code-relay deploy
Expand Down Expand Up @@ -109,9 +110,10 @@ DNS-safe sanitization as Alchemy physical resource names, so `prod` uses
`<stage>-<digest>.<RELAY_TUNNEL_ZONE_NAME>`. `RELAY_DOMAIN` remains available as an explicit API
domain override.

After a successful deploy, the wrapper updates the repository-root `.env` file with the derived relay
URL. That makes subsequent source builds point at the relay that was just deployed without copying
the URL manually.
The stack's `PublishClientConfig` action ([`src/clientConfig.ts`](./src/clientConfig.ts)) writes the
deployed relay URL and tracing configuration into the repository-root `.env`, so subsequent source
builds point at the relay that was just deployed without copying values manually. It runs only when
one of those outputs changed, and `T3CODE_RELAY_CLIENT_CONFIG_ENV` redirects it to another file.

### Deployment CI

Expand Down
15 changes: 15 additions & 0 deletions infra/relay/alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// @effect-diagnostics anyUnknownInErrorContext:off layerMergeAllWithDependencies:off - Alchemy provider helpers expose framework-owned any requirements.
import * as Alchemy from "alchemy";
import * as Output from "alchemy/Output";
import * as Axiom from "alchemy/Axiom";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Drizzle from "alchemy/Drizzle";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Planetscale from "alchemy/Planetscale";

import { PublishClientConfig, tokenDigest } from "./src/clientConfig.ts";
import * as RelayDb from "./src/db.ts";
import { RelayObservability } from "./src/observability.ts";
import { ManagedEndpointZone, RelayApiZone } from "./src/zone.ts";
Expand All @@ -30,6 +32,19 @@ export default Alchemy.Stack(
const relayApiZone = yield* RelayApiZone.pipe(Effect.orDie);
const observability = yield* RelayObservability;
const api = yield* Api;
yield* PublishClientConfig({
url: api.url,
mobileTracingUrl: observability.traces.otelTracesEndpoint,
mobileTracingDataset: observability.traces.name,
mobileTracingToken: observability.mobileIngestToken.token,
clientTracingUrl: observability.traces.otelTracesEndpoint,
clientTracingDataset: observability.traces.name,
clientTracingToken: observability.clientIngestToken.token,
tokenDigest: Output.map(
Output.all(observability.mobileIngestToken.token, observability.clientIngestToken.token),
tokenDigest,
),
});

return {
databaseName: db.database.name,
Expand Down
2 changes: 1 addition & 1 deletion infra/relay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"private": true,
"type": "module",
"scripts": {
"deploy": "node -- scripts/deploy.ts",
"deploy": "alchemy deploy",
"push:android:smoke": "node scripts/android-push-smoke.ts",
"push:android:watch": "node scripts/android-push-watch.ts",
"destroy": "alchemy destroy",
Expand Down
227 changes: 0 additions & 227 deletions infra/relay/scripts/deploy.test.ts

This file was deleted.

Loading
Loading