Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ec89c00
Add AWS CDK infrastructure for Multica platform deployment
vicky040 Oct 4, 2026
1abd5c0
Simplify infrastructure to focus on development environment only
vicky040 Oct 4, 2026
33eaaa4
Remove NAT gateway to reduce costs by ~$35/month
vicky040 Oct 4, 2026
0ac7891
feat(infra): replace platform stacks with one agent runtime machine
vicky040 Oct 7, 2026
49e638b
ci(infra): drop workflow steps and triggers nothing consumes
vicky040 Oct 7, 2026
d670eb9
fix(infra): stop the CDK tests from running in, and failing, frontend CI
vicky040 Oct 7, 2026
4bdf786
Merge pull request #1 from opex-labs-ai/feat/aws-cdk-infrastructure
parthkhanna150 Oct 14, 2026
1cdb39e
fix(infra): create the daemon log directory before starting the unit
parthkhanna150 Oct 15, 2026
d36174d
fix(infra): ship the daemon's own log to CloudWatch
parthkhanna150 Oct 15, 2026
26e010b
Merge pull request #2 from opex-labs-ai/fix/infra-daemon-log-dir
parthkhanna150 Oct 15, 2026
853cff9
fix(infra): pin the daemon id so a replacement keeps its runtimes
parthkhanna150 Oct 15, 2026
74bc2c3
Merge pull request #3 from opex-labs-ai/fix/infra-pin-daemon-id
parthkhanna150 Oct 15, 2026
2b2eed1
docs(infra): add a runbook for the deployed agent runtime
parthkhanna150 Oct 15, 2026
856851d
Merge pull request #4 from opex-labs-ai/docs/infra-runbook
parthkhanna150 Oct 15, 2026
3bcf8f8
docs(infra): record the rotated state and the workspace setup plan
parthkhanna150 Oct 15, 2026
91822a5
docs(infra): start the setup plan with taking over the runtimes
parthkhanna150 Oct 15, 2026
39cce8e
Merge pull request #5 from opex-labs-ai/docs/infra-runbook-handover
parthkhanna150 Oct 15, 2026
0a5e323
feat(infra): add a self-hosted Multica stack on a fixed public IP
vicky040 Nov 2, 2026
8f31320
Merge upstream multica-ai/multica main into feat/self-host-public-ip
vicky040 Nov 2, 2026
8004327
chore(infra): drop this installation's own details from the public hi…
vicky040 Nov 2, 2026
f460a5b
Merge branch 'main' into feat/self-host-public-ip
vicky040 Nov 2, 2026
42ec5a2
feat(infra): let the stack generate the self-host secrets
vicky040 Nov 2, 2026
c6e751a
Merge remote-tracking branch 'origin/feat/self-host-public-ip' into f…
vicky040 Nov 2, 2026
fb6fd21
ci(infra): let the deploy job pick a stack and carry its uncommitted …
vicky040 Nov 2, 2026
af10dba
Merge remote-tracking branch 'upstream/main' into feat/self-host-publ…
vicky040 Nov 2, 2026
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
12 changes: 7 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,12 @@ jobs:
- name: Build, type check, and lint
# Mobile lives in a parallel mobile-verify workflow (path-filtered
# to apps/mobile/** + packages/core/**) so it doesn't add
# ~50s of expo-lint + tsc to every web/desktop PR. Keep this
# filter in sync with the root package.json scripts, which also
# exclude @multica/mobile.
run: pnpm exec turbo build typecheck lint --filter='!@multica/docs' --filter='!@multica/mobile'
# ~50s of expo-lint + tsc to every web/desktop PR. The CDK stack in
# infrastructure/ is excluded for the same reason: it has its own
# path-filtered workflow, it imports nothing from these packages, and
# no frontend change can affect its result. Keep this filter in sync
# with the root package.json scripts, which exclude the same two.
run: pnpm exec turbo build typecheck lint --filter='!@multica/docs' --filter='!@multica/mobile' --filter='!@multica/infrastructure'

- name: Check frontend quality
uses: ./.github/actions/frontend-quality
Expand Down Expand Up @@ -194,7 +196,7 @@ jobs:
# job's responsibility -- frontend-build owns the `typecheck` task.
# `test` reaches dependency sources through the hash-only
# `^cache-inputs` edge (see turbo.json), so no `tsc` runs here.
run: pnpm exec turbo test --filter='!@multica/docs' --filter='!@multica/mobile' --filter='!@multica/views'
run: pnpm exec turbo test --filter='!@multica/docs' --filter='!@multica/mobile' --filter='!@multica/infrastructure' --filter='!@multica/views'

# Split views across two runners. GitHub reports the matrix job's combined
# result, so the aggregate gate can continue depending on this one job ID.
Expand Down
171 changes: 171 additions & 0 deletions .github/workflows/infrastructure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
name: Infrastructure

# The agent runtime stack (infrastructure/). Validation runs on every change to
# it; deploying is a manual dispatch, because the thing it replaces is the
# machine the team's agents are currently running on.
#
# ci.yml does not cover this path: its frontend jobs typecheck the workspace,
# but `cdk synth` and the template assertions belong with the stack rather than
# in a job every frontend change pays for.
on:
pull_request:
branches: [main]
paths:
- "infrastructure/**"
- ".github/workflows/infrastructure.yml"
push:
branches: [main]
paths:
- "infrastructure/**"
- ".github/workflows/infrastructure.yml"
workflow_dispatch:
inputs:
action:
description: "diff shows what would change; deploy applies it"
type: choice
default: diff
options: [diff, deploy]
stack:
description: "Which stack to act on"
type: choice
default: Multica-AgentRuntime
options: [Multica-AgentRuntime, Multica-SelfHost]
allowed_emails:
description: "Multica-SelfHost only: comma-separated addresses allowed to sign up"
type: string
default: ""

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

# The script is embedded verbatim in the instance's user data, so a syntax
# error here is a machine that half-configures itself and never reports a
# failure anywhere a deploy can see.
- name: Check shell scripts
working-directory: infrastructure
run: |
for script in assets/bootstrap.sh scripts/*.sh; do
bash -n "$script"
done

- name: Typecheck
run: pnpm --filter @multica/infrastructure typecheck

- name: Test
run: pnpm --filter @multica/infrastructure test

# No AWS credentials: the app leaves account and region unset when they
# are absent, so the templates synthesize region-agnostic. The self-host
# stack is behind a context flag and needs its signup allowlist, so it
# gets its own invocation rather than going unsynthesized.
- name: Synth
run: pnpm --filter @multica/infrastructure synth

- name: Synth the self-host stack
working-directory: infrastructure
run: pnpm exec cdk synth Multica-SelfHost -c selfHost=true -c allowedEmails=ci@example.com --quiet

# Needs, in repository settings:
# secret AWS_DEPLOY_ROLE_ARN an OIDC role allowed to assume cdk-hnb659fds-*
# variable AWS_REGION the target region
# variable MULTICA_DAEMON_ID the deployed runtime's daemon id, which names
# its runtime rows and so is not committed
# environment aws-agent-runtime
#
# Put required reviewers on that environment. An agent-runtime deploy replaces
# the instance whenever the bootstrap script changed, which kills any run in
# flight on it; a self-host deploy restarts the server.
deploy:
if: github.event_name == 'workflow_dispatch'
needs: validate
runs-on: ubuntu-latest
environment: aws-agent-runtime
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Assume the deployment role
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
aws-region: ${{ vars.AWS_REGION }}
role-session-name: multica-infrastructure-${{ github.run_id }}

# Each stack needs a value that is deliberately not committed: the agent
# runtime's daemon id names this installation's runtime rows, and the
# self-host signup allowlist is what keeps an internet-facing server from
# letting strangers create accounts. Both live outside the repository and
# arrive here instead.
- name: Resolve stack context
id: context
env:
STACK: ${{ inputs.stack }}
ALLOWED_EMAILS: ${{ inputs.allowed_emails }}
DAEMON_ID: ${{ vars.MULTICA_DAEMON_ID }}
run: |
if [ "$STACK" = "Multica-SelfHost" ]; then
if [ -z "$ALLOWED_EMAILS" ]; then
echo "::error::allowed_emails is required for Multica-SelfHost: port 443 is open to the internet, so without an allowlist anyone who finds the URL can sign up."
exit 1
fi
echo "args=-c selfHost=true -c allowedEmails=$ALLOWED_EMAILS" >> "$GITHUB_OUTPUT"
else
if [ -z "$DAEMON_ID" ]; then
echo "::error::Repository variable MULTICA_DAEMON_ID is unset. Deploying the agent runtime without it mints a new daemon id, and the existing runtimes lose their agent bindings and public visibility."
exit 1
fi
echo "args=-c daemonId=$DAEMON_ID" >> "$GITHUB_OUTPUT"
fi

# Only for a diff run: `cdk deploy` prints the same change set itself.
- name: Diff
if: inputs.action == 'diff'
working-directory: infrastructure
run: pnpm exec cdk diff ${{ inputs.stack }} ${{ steps.context.outputs.args }}

# --require-approval broadening, not never: a change that widens a
# security group or an IAM policy fails the job instead of applying
# itself, and then goes through ./scripts/deploy.sh with someone reading
# the diff.
- name: Deploy
if: inputs.action == 'deploy'
working-directory: infrastructure
run: pnpm exec cdk deploy ${{ inputs.stack }} ${{ steps.context.outputs.args }} --require-approval broadening --progress events
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Multica is a task management platform where people and agents collaborate on iss
| `apps/desktop/` | Electron and desktop-only UI/state. Application navigation goes through `apps/desktop/src/renderer/src/platform/`. |
| `apps/mobile/` | Independent Expo/React Native client: owns UI, state, hooks, providers, i18n, build, and release. Shares core types and pure utilities, including platform-independent schemas. |
| `apps/docs/` | Fumadocs documentation site |
| `infrastructure/` | AWS CDK stack for the shared agent runtime machine. A workspace package for dependency management only; it imports nothing from the other packages and nothing imports it. |

- Dependency direction is `views -> core + ui`; core and ui remain independent. Shared packages export raw TypeScript compiled by consuming apps.
- Extract logic used by both web and desktop into the appropriate shared package. Keep framework/Electron APIs in the app layer; inject platform-specific UI through props/slots.
Expand All @@ -39,11 +40,12 @@ Run these from the repository root:

| Scope | Checks |
| --- | --- |
| Frontend excluding mobile | `pnpm typecheck`, `pnpm lint`, `pnpm test` |
| Frontend excluding mobile and infrastructure | `pnpm typecheck`, `pnpm lint`, `pnpm test` |
| Go backend | `make test` |
| End-to-end | `pnpm exec playwright test` |
| Combined web/backend verification | `make check` |
| Mobile | Commands in [apps/mobile/AGENTS.md](apps/mobile/AGENTS.md#verification) |
| Infrastructure | `pnpm --filter @multica/infrastructure typecheck`, `... test`, `... synth` |

Docs-only changes can use link/reference checks and `git diff --check`; state that code tests were not run.

Expand Down
6 changes: 6 additions & 0 deletions infrastructure/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# CDK synthesis output
cdk.out
.cdk.staging

# Deploy-time context cache (account-specific, never shared)
cdk.context.json
Loading
Loading