Skip to content

Commit 15722fa

Browse files
Merge pull request #1 from CUSPUO/infra
feat: AWS infrastructure for static site hosting
2 parents 4c2da5a + 0fff40e commit 15722fa

16 files changed

Lines changed: 1317 additions & 0 deletions

.github/workflows/deploy.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Deploy Site
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths-ignore:
8+
- 'infra/**'
9+
- '*.md'
10+
- 'LICENSE.txt'
11+
- '.gitignore'
12+
13+
permissions:
14+
id-token: write
15+
contents: read
16+
17+
concurrency:
18+
group: deploy-production
19+
cancel-in-progress: false
20+
21+
jobs:
22+
deploy:
23+
runs-on: ubuntu-latest
24+
25+
steps:
26+
- name: Checkout repository
27+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
28+
29+
- name: Configure AWS credentials
30+
uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 # v4.1.0
31+
with:
32+
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
33+
aws-region: us-east-1
34+
35+
- name: Sync all files to S3
36+
run: |
37+
aws s3 sync . s3://${{ secrets.S3_BUCKET_NAME }} \
38+
--delete \
39+
--exclude ".git/*" \
40+
--exclude ".github/*" \
41+
--exclude ".claude/*" \
42+
--exclude "README.*" \
43+
--exclude "LICENSE.txt" \
44+
--exclude "infra/*" \
45+
--exclude "*.md" \
46+
--exclude ".gitignore" \
47+
--cache-control "public, max-age=86400"
48+
49+
- name: Set short cache on HTML files
50+
run: |
51+
aws s3 cp s3://${{ secrets.S3_BUCKET_NAME }}/ s3://${{ secrets.S3_BUCKET_NAME }}/ \
52+
--recursive \
53+
--exclude "*" \
54+
--include "*.html" \
55+
--metadata-directive REPLACE \
56+
--cache-control "public, max-age=300" \
57+
--content-type "text/html; charset=utf-8"
58+
59+
- name: Invalidate CloudFront cache
60+
run: |
61+
aws cloudfront create-invalidation \
62+
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
63+
--paths "/*"

.github/workflows/terraform.yml

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
name: Terraform
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- 'infra/**'
9+
pull_request:
10+
paths:
11+
- 'infra/**'
12+
13+
permissions:
14+
id-token: write
15+
contents: read
16+
pull-requests: write
17+
18+
concurrency:
19+
group: terraform-${{ github.ref }}
20+
cancel-in-progress: false
21+
22+
env:
23+
TF_VAR_aws_profile: ""
24+
25+
jobs:
26+
plan:
27+
name: Terraform Plan
28+
runs-on: ubuntu-latest
29+
if: github.event_name == 'pull_request'
30+
31+
steps:
32+
- name: Checkout repository
33+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
34+
35+
- name: Configure AWS credentials
36+
uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 # v4.1.0
37+
with:
38+
role-to-assume: ${{ secrets.AWS_TERRAFORM_ROLE_ARN }}
39+
aws-region: us-east-1
40+
41+
- name: Setup Terraform
42+
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
43+
with:
44+
terraform_wrapper: false
45+
46+
- name: Terraform Init
47+
working-directory: infra
48+
run: terraform init
49+
50+
- name: Terraform Validate
51+
working-directory: infra
52+
run: terraform validate
53+
54+
- name: Terraform Plan
55+
id: plan
56+
working-directory: infra
57+
run: |
58+
set -o pipefail
59+
terraform plan -no-color -out=tfplan 2>&1 | tee plan_output.txt
60+
echo "plan_exitcode=$?" >> "$GITHUB_OUTPUT"
61+
62+
- name: Post Plan to PR
63+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
64+
with:
65+
script: |
66+
const fs = require('fs');
67+
const plan = fs.readFileSync('infra/plan_output.txt', 'utf8');
68+
const truncated = plan.length > 60000
69+
? plan.substring(0, 60000) + '\n\n... (truncated)'
70+
: plan;
71+
const body = [
72+
'### Terraform Plan',
73+
'```',
74+
truncated,
75+
'```',
76+
`*Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`
77+
].join('\n');
78+
github.rest.issues.createComment({
79+
issue_number: context.issue.number,
80+
owner: context.repo.owner,
81+
repo: context.repo.repo,
82+
body: body
83+
});
84+
85+
- name: Setup Infracost
86+
uses: infracost/actions/setup@e9d6e6cd65e168e76b0de50ff9957d2fe8bb1832 # v3.0.1
87+
with:
88+
api-key: ${{ secrets.INFRACOST_API_KEY }}
89+
90+
- name: Run Infracost Breakdown
91+
working-directory: infra
92+
run: infracost breakdown --path=. --format=json --out-file=/tmp/infracost.json
93+
94+
- name: Post Infracost Comment
95+
run: |
96+
infracost comment github \
97+
--path=/tmp/infracost.json \
98+
--repo=${{ github.repository }} \
99+
--pull-request=${{ github.event.pull_request.number }} \
100+
--github-token=${{ github.token }} \
101+
--behavior=update
102+
103+
apply:
104+
name: Terraform Apply
105+
runs-on: ubuntu-latest
106+
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
107+
environment: production-infra
108+
109+
steps:
110+
- name: Checkout repository
111+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
112+
113+
- name: Configure AWS credentials
114+
uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 # v4.1.0
115+
with:
116+
role-to-assume: ${{ secrets.AWS_TERRAFORM_ROLE_ARN }}
117+
aws-region: us-east-1
118+
119+
- name: Setup Terraform
120+
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
121+
with:
122+
terraform_wrapper: false
123+
124+
- name: Terraform Init
125+
working-directory: infra
126+
run: terraform init
127+
128+
- name: Terraform Apply
129+
working-directory: infra
130+
run: terraform apply -auto-approve

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Terraform
2+
infra/.terraform/
3+
infra/*.tfstate
4+
infra/*.tfstate.backup
5+
infra/terraform.tfvars
6+
7+
# Claude
8+
.claude/

404.html

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<!DOCTYPE HTML>
2+
<!--
3+
Read Only by HTML5 UP
4+
html5up.net | @ajlkn
5+
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
6+
-->
7+
<html>
8+
<head>
9+
<title>404 - Page Not Found | UO - CUSP</title>
10+
<meta charset="utf-8" />
11+
<meta name="viewport" content="width=device-width, initial-scale=1" />
12+
<!--[if lte IE 8]><script src="/assets/js/ie/html5shiv.js"></script><![endif]-->
13+
<link rel="stylesheet" href="/assets/css/main.css" />
14+
<!--[if lte IE 8]><link rel="stylesheet" href="/assets/css/ie8.css" /><![endif]-->
15+
<link rel="shortcut icon" href="/images/favicon.ico" type="image/x-icon"/>
16+
</head>
17+
<body>
18+
19+
<!-- Header -->
20+
<section id="header">
21+
<header>
22+
<span class="image avatar"><img src="/images/cuspuo_logo.jpeg" alt="CUSP UO Logo" /></span>
23+
<h1 id="logo"><a href="/">CUSP Urban Observatory</a></h1>
24+
<p>Better cities through imaging</p>
25+
</header>
26+
<nav id="nav">
27+
<ul>
28+
<li><a href="/">Home</a></li>
29+
<li><a href="/#one">About</a></li>
30+
<li><a href="/#people">People</a></li>
31+
<li><a href="/#research">Research</a></li>
32+
<li><a href="/UOpublications.html">Publications</a></li>
33+
<li><a href="/#media">Media</a></li>
34+
<li><a href="/#support">Support</a></li>
35+
<li><a href="/#contact">Contact</a></li>
36+
</ul>
37+
</nav>
38+
<footer>
39+
<ul class="icons">
40+
<li><a href="https://twitter.com/cuspuo" class="icon fa-twitter" target="_blank"><span class="label">Twitter</span></a></li>
41+
<li><a href="https://github.com/CUSPUO" class="icon fa-github" target="_blank"><span class="label">Github</span></a></li>
42+
<li><a href="mailto:greg.dobler@nyu.edu" class="icon fa-envelope"><span class="label">Email</span></a></li>
43+
</ul>
44+
</footer>
45+
</section>
46+
47+
<!-- Wrapper -->
48+
<div id="wrapper">
49+
50+
<!-- Main -->
51+
<div id="main">
52+
53+
<!-- 404 Section -->
54+
<section id="404">
55+
<div class="container">
56+
<header class="major">
57+
<h2>404 - Page Not Found</h2>
58+
<p>Oops! The page you're looking for doesn't exist.</p>
59+
</header>
60+
<p>The page you requested could not be found. It may have been moved, deleted, or never existed in the first place.</p>
61+
<ul class="actions">
62+
<li><a href="/" class="button special">Return to Homepage</a></li>
63+
</ul>
64+
</div>
65+
</section>
66+
67+
</div>
68+
69+
<!-- Footer -->
70+
<section id="footer">
71+
<div class="container">
72+
<ul class="copyright">
73+
<li>&copy; CUSPUO. All rights reserved.</li><li>Design: <a href="http://html5up.net">HTML5 UP</a></li>
74+
</ul>
75+
</div>
76+
</section>
77+
78+
</div>
79+
80+
<!-- Scripts -->
81+
<script src="/assets/js/jquery.min.js"></script>
82+
<script src="/assets/js/jquery.scrollzer.min.js"></script>
83+
<script src="/assets/js/jquery.scrolly.min.js"></script>
84+
<script src="/assets/js/skel.min.js"></script>
85+
<script src="/assets/js/util.js"></script>
86+
<!--[if lte IE 8]><script src="/assets/js/ie/respond.min.js"></script><![endif]-->
87+
<script src="/assets/js/main.js"></script>
88+
89+
</body>
90+
</html>

infra/.terraform.lock.hcl

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

infra/acm.tf

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
resource "aws_acm_certificate" "site" {
2+
domain_name = local.primary_domain
3+
subject_alternative_names = [for san in local.all_sans : san if san != local.primary_domain]
4+
validation_method = "DNS"
5+
6+
lifecycle {
7+
create_before_destroy = true
8+
}
9+
}
10+
11+
resource "aws_route53_record" "acm_validation" {
12+
for_each = {
13+
for dvo in aws_acm_certificate.site.domain_validation_options :
14+
dvo.domain_name => {
15+
name = dvo.resource_record_name
16+
type = dvo.resource_record_type
17+
value = dvo.resource_record_value
18+
zone_id = local.domain_to_zone[dvo.domain_name]
19+
}
20+
if contains(keys(local.domain_to_zone), dvo.domain_name)
21+
}
22+
23+
zone_id = each.value.zone_id
24+
name = each.value.name
25+
type = each.value.type
26+
ttl = 300
27+
records = [each.value.value]
28+
allow_overwrite = true
29+
}
30+
31+
resource "aws_acm_certificate_validation" "site" {
32+
certificate_arn = aws_acm_certificate.site.arn
33+
validation_record_fqdns = [for record in aws_route53_record.acm_validation : record.fqdn]
34+
35+
timeouts {
36+
create = "30m"
37+
}
38+
}

0 commit comments

Comments
 (0)