website

#astro#js#html#css

git clone https://git.pyrossh.dev/website

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


9930a5b — pyrossh 2025-11-15T15:01:25+05:30
add alchemy

Generated files not shown: bun.lock

Files changed (2) hide show
  1. alchemy.run.ts +271 -3
  2. package.json +2 -0
alchemy.run.ts CHANGED
@@ -1,15 +1,283 @@
1
+ /**
2
+ * Alchemy Infrastructure Configuration for pyrossh.dev
3
+ *
4
+ * Converted from Terraform (main.tf) to Alchemy
5
+ *
6
+ * This configuration sets up:
7
+ * - Route53 hosted zone for pyrossh.dev
8
+ * - S3 buckets for website and git repos
9
+ * - CloudFront distribution with custom functions
10
+ * - S3 bucket policies for CloudFront access
11
+ * - Route53 A record pointing to CloudFront
12
+ *
13
+ * Note: ACM Certificate creation is commented out. You'll need to:
14
+ * 1. Create the certificate manually in ACM (us-east-1 region)
15
+ * 2. Uncomment and provide the certificate ARN in the ViewerCertificate config
16
+ */
17
+
1
18
  import alchemy from "alchemy";
2
19
  import AWS from "alchemy/aws/control";
20
+ import { AccountId } from "alchemy/aws";
3
21
 
22
+ const domainName = "pyrossh.dev";
4
- const app = await alchemy("my-first-app");
23
+ const app = await alchemy("website");
5
24
 
25
+ // Route53 Zone
26
+ const hostedZone = await AWS.Route53.HostedZone("main", {
27
+ Name: domainName,
28
+ HostedZoneTags: [
29
+ { Key: "Environment", Value: "Production" },
30
+ { Key: "ProvisionedBy", Value: "Alchemy" }
31
+ ],
32
+ HostedZoneConfig: {
33
+ Comment: "ProvisionedBy Alchemy"
34
+ },
35
+ adopt: true,
36
+ });
37
+
38
+ // S3 Buckets with Public Access Block Configuration
6
- const reposBucket = await AWS.S3.Bucket("pyrossh-repos2", {
39
+ const websiteBucket = await AWS.S3.Bucket("website-bucket", {
7
- BucketName: "pyrossh-repos2",
40
+ BucketName: "pyrossh-website",
41
+ PublicAccessBlockConfiguration: {
42
+ BlockPublicAcls: true,
43
+ BlockPublicPolicy: true,
44
+ IgnorePublicAcls: true,
45
+ RestrictPublicBuckets: true
46
+ },
8
47
  Tags: [
9
48
  { Key: "Environment", Value: "Production" },
10
49
  { Key: "ProvisionedBy", Value: "Alchemy" }
11
50
  ]
12
51
  });
13
52
 
53
+ const reposBucket = await AWS.S3.Bucket("repos-bucket", {
54
+ BucketName: "pyrossh-repos",
55
+ PublicAccessBlockConfiguration: {
56
+ BlockPublicAcls: true,
57
+ BlockPublicPolicy: true,
58
+ IgnorePublicAcls: true,
59
+ RestrictPublicBuckets: true
60
+ },
61
+ Tags: [
62
+ { Key: "Environment", Value: "Production" },
63
+ { Key: "ProvisionedBy", Value: "Alchemy" }
64
+ ]
65
+ });
66
+
67
+ // CloudFront Origin Access Control
68
+ const s3Oac = await AWS.CloudFront.OriginAccessControl("s3-oac", {
69
+ OriginAccessControlConfig: {
70
+ Name: "s3-oac",
71
+ OriginAccessControlOriginType: "s3",
72
+ SigningBehavior: "always",
73
+ SigningProtocol: "sigv4"
74
+ }
75
+ });
76
+
77
+ // ACM Certificate (in us-east-1 for CloudFront)
78
+ // Note: AWS Certificate Manager needs to be created separately or use ACMPCA
79
+ // For now, assuming certificate ARN will be provided or created manually
80
+ // You may need to adjust this based on your Alchemy setup
81
+
82
+ // CloudFront Functions
83
+ const htmlRedirector = await AWS.CloudFront.Function("html-redirector", {
84
+ FunctionCode: `function handler(event) {
85
+ const request = event.request;
86
+ const uri = request.uri;
87
+
88
+ if (uri.endsWith('/')) {
89
+ request.uri += 'index.html';
90
+ } else if (!uri.startsWith('/_astro')) {
91
+ request.uri += '/index.html';
92
+ }
93
+ return request;
94
+ }`,
95
+ FunctionConfig: {
96
+ Comment: "HTML redirector function",
97
+ Runtime: "cloudfront-js-2.0"
98
+ },
99
+ Name: "html_redirector"
100
+ });
101
+
102
+ const gitRedirector = await AWS.CloudFront.Function("git-redirector", {
103
+ FunctionCode: `function handler(event) {
104
+ const request = event.request;
105
+ const uri = request.uri;
106
+ request.uri = "/" + request.uri.replace("/repos/", "");
107
+ return request;
108
+ }`,
109
+ FunctionConfig: {
110
+ Comment: "Git redirector function",
111
+ Runtime: "cloudfront-js-2.0"
112
+ },
113
+ Name: "git_redirector"
114
+ });
115
+
116
+ // CloudFront Distribution
117
+ const s3Distribution = await AWS.CloudFront.Distribution("s3-distribution", {
118
+ DistributionConfig: {
119
+ Origins: [
120
+ {
121
+ DomainName: websiteBucket.RegionalDomainName,
122
+ Id: websiteBucket.RegionalDomainName,
123
+ OriginAccessControlId: s3Oac.Id,
124
+ S3OriginConfig: {
125
+ OriginAccessIdentity: ""
126
+ }
127
+ },
128
+ {
129
+ DomainName: reposBucket.RegionalDomainName,
130
+ Id: reposBucket.RegionalDomainName,
131
+ OriginAccessControlId: s3Oac.Id,
132
+ S3OriginConfig: {
133
+ OriginAccessIdentity: ""
134
+ }
135
+ }
136
+ ],
137
+ Enabled: true,
138
+ IPV6Enabled: true,
139
+ Aliases: [domainName],
140
+ DefaultRootObject: "index.html",
141
+ CustomErrorResponses: [400, 403, 404, 405, 414, 416, 500, 501, 502, 503, 504].map(code => ({
142
+ ErrorCode: code,
143
+ ResponseCode: code,
144
+ ResponsePagePath: code < 500 ? "/404.html" : "/500.html"
145
+ })),
146
+ CacheBehaviors: [
147
+ {
148
+ PathPattern: "/repos/*.git/*",
149
+ AllowedMethods: ["GET", "HEAD"],
150
+ CachedMethods: ["GET", "HEAD"],
151
+ TargetOriginId: reposBucket.RegionalDomainName,
152
+ ForwardedValues: {
153
+ QueryString: false,
154
+ Cookies: {
155
+ Forward: "none"
156
+ }
157
+ },
158
+ MinTTL: 0,
159
+ DefaultTTL: 86400,
160
+ MaxTTL: 31536000,
161
+ Compress: true,
162
+ ViewerProtocolPolicy: "redirect-to-https",
163
+ FunctionAssociations: [{
164
+ EventType: "viewer-request",
165
+ FunctionARN: gitRedirector.FunctionARN
166
+ }]
167
+ },
168
+ {
169
+ PathPattern: "/*",
170
+ AllowedMethods: ["GET", "HEAD"],
171
+ CachedMethods: ["GET", "HEAD"],
172
+ TargetOriginId: websiteBucket.RegionalDomainName,
173
+ ForwardedValues: {
174
+ QueryString: true,
175
+ Cookies: {
176
+ Forward: "none"
177
+ }
178
+ },
179
+ ViewerProtocolPolicy: "redirect-to-https",
180
+ MinTTL: 0,
181
+ DefaultTTL: 3600,
182
+ MaxTTL: 86400,
183
+ FunctionAssociations: [{
184
+ EventType: "viewer-request",
185
+ FunctionARN: htmlRedirector.FunctionARN
186
+ }]
187
+ }
188
+ ],
189
+ DefaultCacheBehavior: {
190
+ AllowedMethods: ["GET", "HEAD"],
191
+ CachedMethods: ["GET", "HEAD"],
192
+ TargetOriginId: websiteBucket.RegionalDomainName,
193
+ ForwardedValues: {
194
+ QueryString: true,
195
+ Cookies: {
196
+ Forward: "none"
197
+ }
198
+ },
199
+ ViewerProtocolPolicy: "redirect-to-https",
200
+ MinTTL: 0,
201
+ DefaultTTL: 3600,
202
+ MaxTTL: 86400,
203
+ FunctionAssociations: [{
204
+ EventType: "viewer-request",
205
+ FunctionARN: htmlRedirector.FunctionARN
206
+ }]
207
+ },
208
+ PriceClass: "PriceClass_All",
209
+ Restrictions: {
210
+ GeoRestriction: {
211
+ RestrictionType: "none"
212
+ }
213
+ },
214
+ ViewerCertificate: {
215
+ CloudFrontDefaultCertificate: false,
216
+ // AcmCertificateArn: domainSslCertificate.CertificateArn,
217
+ // Uncomment above and create certificate separately or provide ARN
218
+ SslSupportMethod: "sni-only"
219
+ },
220
+ Comment: "Pyrossh Website Distribution"
221
+ }
222
+ });
223
+
224
+ // S3 Bucket Policies
225
+ const websiteBucketPolicy = await AWS.S3.BucketPolicy("website-bucket-policy", {
226
+ Bucket: websiteBucket.BucketName || "pyrossh-website",
227
+ PolicyDocument: {
228
+ Version: "2012-10-17",
229
+ Statement: [{
230
+ Principal: {
231
+ Service: "cloudfront.amazonaws.com"
232
+ },
233
+ Action: "s3:GetObject",
234
+ Resource: [
235
+ websiteBucket.Arn,
236
+ `${websiteBucket.Arn}/*`
237
+ ],
238
+ Effect: "Allow",
239
+ Condition: {
240
+ StringEquals: {
241
+ "AWS:SourceArn": `arn:aws:cloudfront::${await AccountId()}:distribution/${s3Distribution.Id}`
242
+ }
243
+ }
244
+ }]
245
+ }
246
+ });
247
+
248
+ const reposBucketPolicy = await AWS.S3.BucketPolicy("repos-bucket-policy", {
249
+ Bucket: reposBucket.BucketName || "pyrossh-repos",
250
+ PolicyDocument: {
251
+ Version: "2012-10-17",
252
+ Statement: [{
253
+ Principal: {
254
+ Service: "cloudfront.amazonaws.com"
255
+ },
256
+ Action: "s3:GetObject",
257
+ Resource: [
258
+ reposBucket.Arn,
259
+ `${reposBucket.Arn}/*`
260
+ ],
261
+ Effect: "Allow",
262
+ Condition: {
263
+ StringEquals: {
264
+ "AWS:SourceArn": `arn:aws:cloudfront::${await AccountId()}:distribution/${s3Distribution.Id}`
265
+ }
266
+ }
267
+ }]
268
+ }
269
+ });
270
+
271
+ // Route53 A Record
272
+ const aRecordDomain = await AWS.Route53.RecordSet("a-record-domain", {
273
+ HostedZoneId: hostedZone.Id,
274
+ Name: domainName,
275
+ Type: "A",
276
+ AliasTarget: {
277
+ DNSName: s3Distribution.DomainName,
278
+ HostedZoneId: s3Distribution.DomainName, // CloudFront hosted zone ID
279
+ EvaluateTargetHealth: false
280
+ }
281
+ });
14
282
 
15
283
  await app.finalize();
package.json CHANGED
@@ -23,12 +23,14 @@
23
23
  "astro-color-scheme": "^1.1.5",
24
24
  "astro-expressive-code": "^0.41.3",
25
25
  "astro-icon": "^1.1.5",
26
+
26
27
  "expressive-code-color-chips": "^0.1.2",
27
28
  "pretty-bytes": "^7.1.0",
28
29
  "simple-git": "^3.27.0",
29
30
  "timeago.js": "^4.0.2"
30
31
  },
31
32
  "devDependencies": {
33
+ "client-s3": "aws-sdk/client-s3",
32
34
  "@iconify-json/mdi": "^1.2.3",
33
35
  "@iconify-json/material-symbols": "^1.2.39",
34
36
  "@playwright/test": "^1.52.0",