1import * as AWS from "@/AWS";
2import { ServerHost } from "@/Server/Process.ts";
3import * as Effect from "effect/Effect";
4import * as Ref from "effect/Ref";
5import * as Schedule from "effect/Schedule";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8
9/**
10 * Alchemy-managed EC2 key pair granting SSH access to the Ubuntu instance for
11 * debugging bootstrap failures.
12 */
13export const ubuntuKeyPair = AWS.EC2.KeyPair("Ec2UbuntuKeyPair", {
14 keyType: "ed25519",
15});
16
17/**
18 * Ubuntu 24.04 variant of the hosted-instance e2e fixture (issues #1027 and
19 * #1028): Ubuntu AMIs ship without `unzip`, `dnf`, or `yum`, so serving HTTP
20 * from the hosted runtime proves the bootstrap's `apt-get` branch installed
21 * `unzip`, the AWS CLI install + S3 bundle sync succeeded, and the systemd
22 * unit's `bun --no-install` start worked end-to-end.
23 */
24export default class TestUbuntuInstance extends AWS.EC2.Instance<TestUbuntuInstance>()(
25 "Ec2UbuntuE2EInstance",
26 Effect.gen(function* () {
27 // This composition is re-executed inside the deployed instance's bundle:
28 // the AMI lookup is an `Output` resolved at plan/deploy time only, and
29 // resource yields resolve to references at runtime, so no runtime guard
30 // is needed.
31 const network = yield* AWS.EC2.Network("Ec2UbuntuE2ENetwork", {
32 cidrBlock: "10.82.0.0/16",
33 availabilityZones: 1,
34 });
35 const securityGroup = yield* AWS.EC2.SecurityGroup("Ec2UbuntuE2ESg", {
36 vpcId: network.vpcId,
37 description: "alchemy ec2 ubuntu instance e2e",
38 ingress: [
39 {
40 ipProtocol: "tcp",
41 fromPort: 3000,
42 toPort: 3000,
43 cidrIpv4: "0.0.0.0/0",
44 description: "app",
45 },
46 {
47 ipProtocol: "tcp",
48 fromPort: 22,
49 toPort: 22,
50 cidrIpv4: "0.0.0.0/0",
51 description: "ssh",
52 },
53 ],
54 egress: [
55 {
56 ipProtocol: "-1",
57 cidrIpv4: "0.0.0.0/0",
58 description: "all outbound",
59 },
60 ],
61 });
62
63 const key = yield* ubuntuKeyPair;
64
65 return {
66 main: import.meta.filename,
67 imageId: AWS.EC2.ubuntu2404(),
68 instanceType: "t3.small",
69 subnetId: network.publicSubnetIds[0],
70 securityGroupIds: [securityGroup.groupId],
71 associatePublicIpAddress: true,
72 port: 3000,
73 keyName: key.keyName,
74 };
75 }),
76 Effect.gen(function* () {
77 const host = yield* ServerHost;
78 const ticks = yield* Ref.make(0);
79
80 // Long-running background loop (the `host.run` pattern from #706).
81 yield* host.run(
82 Ref.update(ticks, (n) => n + 1).pipe(
83 Effect.repeat(Schedule.spaced("1 second")),
84 Effect.asVoid,
85 ),
86 );
87
88 return {
89 fetch: Effect.gen(function* () {
90 const request = yield* HttpServerRequest;
91 const url = new URL(request.url, "http://instance");
92 if (url.pathname === "/health") {
93 return yield* HttpServerResponse.json({ ok: true });
94 }
95 if (url.pathname === "/ticks") {
96 return yield* HttpServerResponse.json({
97 ticks: yield* Ref.get(ticks),
98 });
99 }
100 return HttpServerResponse.text("hello from ubuntu ec2 instance");
101 }),
102 };
103 }),
104) {}