diff --git a/.github/workflows/deploy-den.yml b/.github/workflows/deploy-den.yml new file mode 100644 index 00000000..4ed04072 --- /dev/null +++ b/.github/workflows/deploy-den.yml @@ -0,0 +1,99 @@ +name: Deploy Den + +on: + workflow_call: + inputs: + daytona_snapshot: + description: "Daytona snapshot name to promote into Render" + required: true + type: string + workflow_dispatch: + inputs: + daytona_snapshot: + description: "Daytona snapshot name to promote into Render" + required: true + type: string + +permissions: + contents: read + +concurrency: + group: deploy-den-${{ inputs.daytona_snapshot }} + cancel-in-progress: false + +jobs: + deploy-den: + name: Update Render Daytona Snapshot + runs-on: blacksmith-4vcpu-ubuntu-2404 + env: + RENDER_API_BASE: https://api.render.com/v1 + RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }} + RENDER_SERVICE_ID: ${{ secrets.RENDER_DEN_CONTROL_PLANE_SERVICE_ID }} + DAYTONA_SNAPSHOT: ${{ inputs.daytona_snapshot }} + steps: + - name: Validate required configuration + shell: bash + run: | + set -euo pipefail + + if [ -z "${DAYTONA_SNAPSHOT:-}" ]; then + echo "daytona_snapshot input is required" >&2 + exit 1 + fi + + if [ -z "${RENDER_API_KEY:-}" ]; then + echo "Missing required secret: RENDER_API_KEY" >&2 + exit 1 + fi + + if [ -z "${RENDER_SERVICE_ID:-}" ]; then + echo "Missing required secret: RENDER_DEN_CONTROL_PLANE_SERVICE_ID" >&2 + exit 1 + fi + + - name: Update DAYTONA_SNAPSHOT on Render + shell: bash + run: | + set -euo pipefail + + payload="$(python3 -c 'import json, sys; print(json.dumps({"value": sys.argv[1]}))' "$DAYTONA_SNAPSHOT")" + response_file="$(mktemp)" + status_code="$(curl -sS -o "$response_file" -w "%{http_code}" \ + -X PUT "${RENDER_API_BASE}/services/${RENDER_SERVICE_ID}/env-vars/DAYTONA_SNAPSHOT" \ + -H "Accept: application/json" \ + -H "Authorization: Bearer ${RENDER_API_KEY}" \ + -H "Content-Type: application/json" \ + --data "$payload")" + + if [ "$status_code" -lt 200 ] || [ "$status_code" -ge 300 ]; then + echo "Failed to update Render DAYTONA_SNAPSHOT (HTTP $status_code)" >&2 + python3 -c 'from pathlib import Path; import sys; print(Path(sys.argv[1]).read_text(errors="replace"))' "$response_file" + exit 1 + fi + + echo "Render DAYTONA_SNAPSHOT set to ${DAYTONA_SNAPSHOT}" + + - name: Trigger Render deploy + id: deploy + shell: bash + run: | + set -euo pipefail + + response_file="$(mktemp)" + status_code="$(curl -sS -o "$response_file" -w "%{http_code}" \ + -X POST "${RENDER_API_BASE}/services/${RENDER_SERVICE_ID}/deploys" \ + -H "Accept: application/json" \ + -H "Authorization: Bearer ${RENDER_API_KEY}" \ + -H "Content-Type: application/json" \ + --data '{}')" + + if [ "$status_code" -lt 200 ] || [ "$status_code" -ge 300 ]; then + echo "Failed to trigger Render deploy (HTTP $status_code)" >&2 + python3 -c 'from pathlib import Path; import sys; print(Path(sys.argv[1]).read_text(errors="replace"))' "$response_file" + exit 1 + fi + + deploy_id="$(python3 -c 'import json, sys; from pathlib import Path; text = Path(sys.argv[1]).read_text(errors="replace").strip(); data = json.loads(text) if text else {}; print(data.get("id", "") if isinstance(data, dict) else "")' "$response_file")" + + echo "deploy_id=${deploy_id}" >> "$GITHUB_OUTPUT" + echo "Triggered Render deploy ${deploy_id:-} for snapshot ${DAYTONA_SNAPSHOT}" diff --git a/.github/workflows/release-daytona-snapshot.yml b/.github/workflows/release-daytona-snapshot.yml index 6a33180a..3fd66492 100644 --- a/.github/workflows/release-daytona-snapshot.yml +++ b/.github/workflows/release-daytona-snapshot.yml @@ -7,6 +7,11 @@ on: description: "Tag to build from (e.g., v0.11.200). Defaults to current ref." required: false type: string + deploy_den: + description: "Whether to promote the published snapshot into the Den Render service" + required: false + type: boolean + default: true snapshot_name: description: "Optional explicit Daytona snapshot name" required: false @@ -21,6 +26,11 @@ on: description: "Tag to build from (e.g., v0.11.200). Defaults to release tag/current ref." required: false type: string + deploy_den: + description: "Whether to promote the published snapshot into the Den Render service" + required: false + type: boolean + default: true snapshot_name: description: "Optional explicit Daytona snapshot name" required: false @@ -41,6 +51,10 @@ jobs: publish-daytona-snapshot: name: Build and Push Daytona Snapshot runs-on: blacksmith-4vcpu-ubuntu-2404 + outputs: + release_tag: ${{ steps.resolve.outputs.release_tag }} + snapshot_name: ${{ steps.resolve.outputs.snapshot_name }} + snapshot_region: ${{ steps.resolve.outputs.snapshot_region }} steps: - name: Resolve release tag and snapshot name id: resolve @@ -49,7 +63,6 @@ jobs: INPUT_TAG: ${{ inputs.tag }} INPUT_SNAPSHOT_NAME: ${{ inputs.snapshot_name }} INPUT_SNAPSHOT_REGION: ${{ inputs.snapshot_region }} - DEFAULT_SNAPSHOT_NAME: ${{ vars.DAYTONA_SNAPSHOT }} SNAPSHOT_NAME_BASE: ${{ vars.DAYTONA_SNAPSHOT_NAME_BASE }} DEFAULT_SNAPSHOT_REGION: ${{ vars.DAYTONA_SNAPSHOT_REGION }} run: | @@ -67,11 +80,9 @@ jobs: exit 1 fi - base_name="${SNAPSHOT_NAME_BASE:-openwork-runtime}" + base_name="${SNAPSHOT_NAME_BASE:-openwork}" if [ -n "${INPUT_SNAPSHOT_NAME:-}" ]; then snapshot_name="${INPUT_SNAPSHOT_NAME}" - elif [ -n "${DEFAULT_SNAPSHOT_NAME:-}" ]; then - snapshot_name="${DEFAULT_SNAPSHOT_NAME}" else snapshot_name="${base_name}-${tag#v}" fi @@ -150,3 +161,12 @@ jobs: echo "Publishing Daytona snapshot: ${DAYTONA_SNAPSHOT_NAME}" ./scripts/create-daytona-openwork-snapshot.sh "${DAYTONA_SNAPSHOT_NAME}" + + deploy-den: + name: Promote Daytona Snapshot to Den Render Service + needs: [publish-daytona-snapshot] + if: ${{ inputs.deploy_den }} + uses: ./.github/workflows/deploy-den.yml + with: + daytona_snapshot: ${{ needs.publish-daytona-snapshot.outputs.snapshot_name }} + secrets: inherit diff --git a/ee/apps/den-controller/AGENT_FOCUS.md b/ee/apps/den-controller/AGENT_FOCUS.md index 48265d89..ba98eaab 100644 --- a/ee/apps/den-controller/AGENT_FOCUS.md +++ b/ee/apps/den-controller/AGENT_FOCUS.md @@ -67,7 +67,7 @@ Expected: worker creation `202` with a healthy cloud-backed instance once provis - `.github/workflows/deploy-den.yml` -It updates Render env vars and triggers a deploy for the configured service ID. Daytona is intended for local/dev worker testing unless you build a separate hosted Den deployment path for it. +It updates Render env vars and triggers a deploy for the configured service ID. Release snapshot publishing now calls it after a successful Daytona snapshot push so Den picks up the new `DAYTONA_SNAPSHOT` value. ## Common failure modes diff --git a/ee/apps/den-controller/README.md b/ee/apps/den-controller/README.md index 732a967a..a4f318cb 100644 --- a/ee/apps/den-controller/README.md +++ b/ee/apps/den-controller/README.md @@ -130,7 +130,7 @@ Useful optional overrides: After the snapshot is pushed, set it in `.env.daytona`: ```env -DAYTONA_SNAPSHOT=openwork-runtime +DAYTONA_SNAPSHOT=openwork-0.11.174 ``` Then start Den in Daytona mode: @@ -146,9 +146,10 @@ If you do not set `DAYTONA_SNAPSHOT`, Den falls back to `DAYTONA_SANDBOX_IMAGE`. GitHub workflow `.github/workflows/release-daytona-snapshot.yml` builds and pushes a new Daytona snapshot whenever a GitHub release is published. - Trigger: `release.published` (or manual `workflow_dispatch`) -- Snapshot naming: `DAYTONA_SNAPSHOT` repo variable if set, otherwise `openwork-runtime-` +- Snapshot naming: `snapshot_name` input if provided, otherwise `${DAYTONA_SNAPSHOT_NAME_BASE:-openwork}-` - Required secret: `DAYTONA_API_KEY` - Optional repo vars: `DAYTONA_API_URL`, `DAYTONA_TARGET`, `DAYTONA_SNAPSHOT_REGION`, `DAYTONA_SNAPSHOT_NAME_BASE` +- After the snapshot publish succeeds, the workflow calls `.github/workflows/deploy-den.yml` to set Render's `DAYTONA_SNAPSHOT` env var and trigger a Den controller deploy. ## Auth setup (Better Auth) @@ -186,7 +187,7 @@ pnpm db:migrate:sql ## CI deployment (dev == prod) -The workflow `.github/workflows/deploy-den.yml` updates Render env vars and deploys the service on every push to `dev` when this service changes. +The workflow `.github/workflows/deploy-den.yml` updates Render env vars and triggers a deploy for the Den controller service. It can be run manually with a snapshot name, and release automation calls it after successful Daytona snapshot publishing. Required GitHub Actions secrets: