Skip to content

feat(aws/ec2): KeyPair resource + hosted Instance HTTP serving - #714

Merged
sam-goodwin merged 11 commits into
mainfrom
claude/ec2-instance-hosted-e2e
Jul 1, 2026
Merged

feat(aws/ec2): KeyPair resource + hosted Instance HTTP serving#714
sam-goodwin merged 11 commits into
mainfrom
claude/ec2-instance-hosted-e2e

Conversation

@sam-goodwin

Copy link
Copy Markdown
Contributor

Stacked on #713. Adds AWS.EC2.KeyPair and makes the hosted AWS.EC2.Instance (the ServerHost + host.run + { fetch } pattern from #706) work end to end on a real instance.

AWS.EC2.KeyPair

Generate (or import) an EC2 key pair; the generated private key is captured as a Redacted secret (AWS returns it only once, at create).

const keyPair = yield* AWS.EC2.KeyPair("DeployKey", { keyType: "ed25519" });
// keyPair.keyName    -> AWS.EC2.Instance({ keyName })
// keyPair.privateKey -> Redacted<string>

Making the hosted Instance serve

  • Reboot/flake-safe boot. User-data only writes the systemd unit + setup script and enables the service. The service (Restart=always) installs bun (with retries), syncs the bundle from S3, and runs the program on every start — so a transient bun.sh install or cloud-init's once-per-instance user-data skip self-heals (previously a single failure left nothing serving).
  • Standard AMI. amazonLinux2023() selected al2023-ami-minimal-* (no SSM agent, stripped tools) because the glob al2023-ami-* matched it. Narrowed to al2023-ami-2023.*.
  • Instance props are runtime-skipped. Resolving a hosted resource inside the instance re-runs its props Effect; infra-resolving props (amazonLinux2023(), Network(...)) fail with Service not found: AWS::Environment. The fixture guards them behind __ALCHEMY_RUNTIME__ (folded to true in the bundle, DCE-ing the AWS SDK out).
  • Extended the instance terminate-wait (was ~64s, timed out intermittently).

Test

test/AWS/EC2/Instance.smoke.test.ts launches a real instance into a public subnet and asserts over HTTP (against the public IP) that /health is served and /ticks proves the ServerHost.run loop runs in-instance; the KeyPair private key is returned from the stack and printed. Plus KeyPair.test.ts (create/capture/list/delete). Skipped under FAST=1.

sam-goodwin and others added 7 commits June 29, 2026 19:56
`AWS.ECS.Task` (and `AWS.EC2.Instance`) programs that yield `ServerHost`
failed during plan/deploy with `Service not found: Alchemy::ServerHost`.
The service was declared and consumed but never provided, and the ECS
Task runtime context was an incomplete stub missing `exports` and `serve`.

- Platform provides `ServerHost` whenever the runtime context carries `run`
- Share one host runtime context (run + serve + exports.program) between
  EC2 Instance and ECS Task via `createHostRuntimeContext`
- Fix the generated container/instance entry to resolve the `exports`
  Effect before reading `.program` (was reading `.program` off an Effect)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complete the ECS Task container runtime so a long-running `{ fetch }` /
`ServerHost.run` program actually runs on Fargate:

- bundle the entry with a Bun HTTP server (`BunServices` + `BunHttpServer`)
  and ship every rolldown chunk into the image (was: first chunk only, so
  dynamic imports crashed the container)
- build the image for the architecture `runtimePlatform` declares (default
  linux/amd64) — an image built on an ARM64 host was rejected by Fargate
- guard the role-deletion `list*` calls against NoSuchEntityException so
  delete is idempotent

Mirror the same Bun HTTP server + multi-chunk bundling into the shared EC2
hosted runtime (`AWS.EC2.Instance`).

Add a real end-to-end smoke test: builds + pushes the image, runs the task
on Fargate behind a managed ALB, and asserts over HTTP that `{ fetch }` is
served (`/health`) and the `ServerHost.run` background loop is executing in
the container (`/ticks` climbs). Skipped under FAST=1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep only the shared host-context refactor + `exports → program` resolution
fix for `AWS.EC2.Instance` here. The Bun HTTP server + multi-chunk bundling
(so a hosted Instance actually serves `{ fetch }`) and its end-to-end test
land in a separate PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…urce

- HostProps extends PlatformProps so the test Platform resource satisfies
  the ResourceLike constraint
- drop the now-unused @ts-expect-error on the makePlan return expression

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the ECS Task container runtime onto AWS.EC2.Instance: bundle the
hosted entry with a Bun HTTP server (BunServices + BunHttpServer) and zip
every rolldown chunk (not just the entry) so dynamic imports resolve on the
instance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Launch a real EC2 instance running a bundled long-running program, and
assert over HTTP (against the instance public IP) that the { fetch } handler
is served and the ServerHost.run background loop runs on the instance
(/ticks climbs). Skipped under FAST=1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nd (#706)

Add `AWS.EC2.KeyPair` (generate or import; private key captured as a secret)
and use it from the hosted-Instance e2e fixture. Make the e2e green:

- hosted user-data is now reboot-safe: it only writes the systemd unit +
  setup script and enables the service. The service (Restart=always) installs
  bun (with retries), syncs the bundle from S3, and runs the program on every
  start — so a flaky `bun.sh` install or cloud-init's once-per-instance
  user-data skip self-heals (previously a transient failure left nothing
  serving).
- `amazonLinux2023()` now selects the standard AMI (`al2023-ami-2023.*`); the
  broader glob matched `al2023-ami-minimal-*`, which ships no SSM agent and a
  stripped toolset.
- extend the instance terminate-wait (was ~64s, timed out intermittently).

Test: real instance launched into a public subnet, served over HTTP
(`/health` + `/ticks` proves the ServerHost.run loop runs in-instance), with
the KeyPair's private key returned from the stack and printed. Skipped under
FAST=1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alchemy-version-bot

alchemy-version-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

alchemy

bun add alchemy@https://pkg.ing/alchemy/a24c74a

@alchemy.run/better-auth

bun add @alchemy.run/better-auth@https://pkg.ing/@alchemy.run/better-auth/a24c74a

@alchemy.run/pr-package

bun add @alchemy.run/pr-package@https://pkg.ing/@alchemy.run/pr-package/a24c74a

Base automatically changed from claude/heuristic-kilby-1d5b7c to main June 30, 2026 23:44
sam-goodwin and others added 3 commits June 30, 2026 16:45
- KeyPair.read: pass a Record to `hasTags` (ec2 `Tag[]` has optional
  Key/Value, not assignable to `Tags`)
- KeyPair.delete: drop the `InvalidKeyPair.NotFound` catch — `deleteKeyPair`
  is idempotent and does not have that tag in its typed error union
- Instance/hosted `keyName` accepts `Input<string>` so it can take a
  reference like `AWS.EC2.KeyPair(...).keyName` in the Effect-props form
- Instance fixture: runtime stub omits infra-derived props (were
  ill-typed `""`) and asserts the resolved AMI id is defined

Verified with `bun tsc -b` (0 errors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…osted-e2e

# Conflicts:
#	distilled
#	packages/alchemy/src/AWS/EC2/hosted.ts
@sam-goodwin
sam-goodwin merged commit 62f05d3 into main Jul 1, 2026
9 checks passed
@sam-goodwin
sam-goodwin deleted the claude/ec2-instance-hosted-e2e branch July 1, 2026 00:59
DavidJFelix pushed a commit to DavidJFelix/alchemy-effect that referenced this pull request Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant