Initial commit

This commit is contained in:
greg
2025-12-21 18:58:29 -08:00
commit 990c8971fb
8 changed files with 341 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.DS_Store
.terraform/
*.tfstate
*.tfstate.backup

4
content/images/aws.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
<path fill="#252f3e" d="M36.379 53.64c0 1.56.168 2.825.465 3.75.336.926.758 1.938 1.347 3.032.207.336.293.672.293.969 0 .418-.254.84-.8 1.261l-2.653 1.77c-.379.25-.758.379-1.093.379-.422 0-.844-.211-1.266-.59a13.28 13.28 0 0 1-1.516-1.98 34.153 34.153 0 0 1-1.304-2.485c-3.282 3.875-7.41 5.813-12.38 5.813-3.535 0-6.355-1.012-8.421-3.032-2.063-2.023-3.114-4.718-3.114-8.086 0-3.578 1.262-6.484 3.833-8.671 2.566-2.192 5.976-3.286 10.316-3.286 1.43 0 2.902.125 4.46.336 1.56.211 3.161.547 4.845.926v-3.074c0-3.2-.676-5.43-1.98-6.734C26.061 32.633 23.788 32 20.546 32c-1.473 0-2.988.168-4.547.547a33.416 33.416 0 0 0-4.547 1.433c-.676.293-1.18.461-1.473.547-.296.082-.507.125-.675.125-.59 0-.883-.422-.883-1.304v-2.063c0-.676.082-1.18.293-1.476.21-.293.59-.586 1.18-.883 1.472-.758 3.242-1.39 5.304-1.895 2.063-.547 4.254-.8 6.57-.8 5.008 0 8.672 1.136 11.032 3.41 2.316 2.273 3.492 5.726 3.492 10.359v13.64Zm-17.094 6.403c1.387 0 2.82-.254 4.336-.758 1.516-.508 2.863-1.433 4-2.695.672-.8 1.18-1.684 1.43-2.695.254-1.012.422-2.23.422-3.665v-1.765a34.401 34.401 0 0 0-3.871-.719 31.816 31.816 0 0 0-3.961-.25c-2.82 0-4.883.547-6.274 1.684-1.387 1.136-2.062 2.734-2.062 4.84 0 1.98.504 3.453 1.558 4.464 1.012 1.051 2.485 1.559 4.422 1.559Zm33.809 4.547c-.758 0-1.262-.125-1.598-.422-.34-.254-.633-.84-.887-1.64L40.715 29.98c-.25-.843-.38-1.39-.38-1.687 0-.672.337-1.05 1.013-1.05h4.125c.8 0 1.347.124 1.644.421.336.25.59.84.84 1.64l7.074 27.876 6.57-27.875c.208-.84.462-1.39.797-1.64.34-.255.93-.423 1.688-.423h3.367c.8 0 1.348.125 1.684.422.336.25.633.84.8 1.64l6.653 28.212 7.285-28.211c.25-.84.547-1.39.84-1.64.336-.255.887-.423 1.644-.423h3.914c.676 0 1.055.336 1.055 1.051 0 .21-.043.422-.086.676-.043.254-.125.59-.293 1.05L80.801 62.57c-.254.84-.547 1.387-.887 1.64-.336.255-.883.423-1.598.423h-3.62c-.801 0-1.348-.13-1.684-.422-.34-.297-.633-.844-.801-1.684l-6.527-27.16-6.485 27.117c-.21.844-.46 1.391-.8 1.684-.337.297-.926.422-1.684.422Zm54.105 1.137c-2.187 0-4.379-.254-6.484-.758-2.106-.504-3.746-1.055-4.84-1.684-.676-.379-1.137-.8-1.305-1.18a2.919 2.919 0 0 1-.254-1.18v-2.148c0-.882.336-1.304.97-1.304.25 0 .503.043.757.129.25.082.629.25 1.05.418a23.102 23.102 0 0 0 4.634 1.476c1.683.336 3.324.504 5.011.504 2.653 0 4.715-.465 6.145-1.39 1.433-.926 2.191-2.274 2.191-4 0-1.18-.379-2.145-1.136-2.946-.758-.8-2.192-1.516-4.254-2.191l-6.106-1.895c-3.074-.969-5.348-2.398-6.734-4.293-1.39-1.855-2.106-3.918-2.106-6.105 0-1.77.38-3.328 1.137-4.676a10.829 10.829 0 0 1 3.031-3.453c1.262-.965 2.696-1.684 4.38-2.188 1.683-.504 3.452-.715 5.304-.715.926 0 1.894.043 2.82.168.969.125 1.852.293 2.738.461.84.211 1.641.422 2.399.676.758.254 1.348.504 1.77.758.59.336 1.011.672 1.261 1.05.254.34.379.802.379 1.391v1.98c0 .884-.336 1.348-.969 1.348-.336 0-.883-.171-1.597-.507-2.403-1.094-5.098-1.641-8.086-1.641-2.399 0-4.293.379-5.598 1.18-1.309.797-1.98 2.02-1.98 3.746 0 1.18.421 2.191 1.261 2.988.844.8 2.403 1.602 4.633 2.316l5.98 1.895c3.032.969 5.22 2.316 6.524 4.043 1.305 1.727 1.938 3.707 1.938 5.895 0 1.812-.38 3.453-1.094 4.882-.758 1.434-1.77 2.696-3.074 3.707-1.305 1.051-2.864 1.809-4.672 2.36-1.895.586-3.875.883-6.024.883Zm0 0"/>
<path fill="#f90" d="M118 73.348c-4.432.063-9.664 1.052-13.621 3.832-1.223.883-1.012 2.062.336 1.894 4.508-.547 14.44-1.726 16.21.547 1.77 2.23-1.976 11.62-3.663 15.79-.504 1.26.59 1.769 1.726.8 7.41-6.231 9.348-19.242 7.832-21.137-.757-.925-4.388-1.79-8.82-1.726zM1.63 75.859c-.927.116-1.347 1.236-.368 2.121 16.508 14.902 38.359 23.872 62.613 23.872 17.305 0 37.43-5.43 51.281-15.66 2.273-1.688.297-4.254-2.02-3.204-15.534 6.57-32.421 9.77-47.788 9.77-22.778 0-44.8-6.273-62.653-16.633-.39-.231-.755-.304-1.064-.266z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
content/images/favico.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

1
content/images/gcp.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#ea4535" d="M68.7 37.9h1.1l3.1-3.1.2-1.3c-2.4-2.2-5.7-3.5-9.3-3.5-6.4 0-11.9 4.4-13.5 10.3.3-.2 1.1-.1 1.1-.1l6.2-1s.3-.5.5-.5c2.8-3 7.4-3.3 10.6-.8z"/><path fill="#557ebf" d="M77.4 40.3c-.8-2.6-2.2-5-4.3-6.8l-4.4 4.4c1.9 1.5 2.9 3.7 2.9 6.1v.8c2.1 0 3.9 1.7 3.9 3.9 0 2.1-1.7 3.8-3.9 3.8h-7.8l-.8.8V58l.8.8h7.8c5.6 0 10.1-4.5 10.1-10.1 0-3.4-1.7-6.5-4.4-8.3z"/><path fill="#36a852" d="M56.1 58.7h7.8v-6.2h-7.8c-.6 0-1.1-.1-1.6-.3l-1.1.3-3.1 3.1-.3 1c1.7 1.4 3.9 2.1 6.1 2.1z"/><path fill="#f9bc15" d="M56.1 38.5C50.5 38.5 46 43 46 48.6c0 3.2 1.5 6.2 4 8.1l4.5-4.5c-1.4-.7-2.3-2-2.3-3.6 0-2.1 1.7-3.9 3.9-3.9 1.5.1 2.9 1 3.5 2.3l4.5-4.5c-1.8-2.4-4.8-4-8-4z"/><path fill="#5f6368" d="M15.9 78.3c-2.1 0-3.9-.8-5.5-2.3s-2.3-3.2-2.3-5.4.8-3.9 2.3-5.4 3.4-2.3 5.5-2.3c1.9.1 3.8.8 5.2 2.2l-1.5 1.5c-1-1-2.3-1.5-3.8-1.5s-2.8.5-3.9 1.6c-1 1-1.6 2.5-1.5 3.9 0 1.5.5 2.9 1.6 3.9 1 1.2 2.4 1.7 3.8 1.7 1.6 0 2.8-.5 3.9-1.5.6-.6 1-1.5 1.1-2.6h-4.9V70h7c.1.4.1.8.1 1.3 0 2.1-.6 3.7-1.8 4.9-1.4 1.4-3.1 2.2-5.3 2.2zm16.4-1.4c-1 .9-2.1 1.4-3.5 1.4s-2.6-.5-3.5-1.4-1.4-2.1-1.4-3.5.5-2.6 1.4-3.5 2.1-1.4 3.5-1.4 2.6.5 3.5 1.4 1.4 2.1 1.4 3.5-.5 2.6-1.4 3.5zm-5.5-1.4c.5.6 1.2.9 1.9.9.8 0 1.4-.3 2-.9s.8-1.3.8-2.1c0-.9-.3-1.6-.8-2.2s-1.2-.8-2-.8c-.7 0-1.5.3-2 .8-.5.6-.8 1.3-.8 2.2 0 .9.3 1.6.8 2.1zm16.3 1.4c-1 .9-2.1 1.4-3.5 1.4s-2.6-.5-3.5-1.4-1.4-2.1-1.4-3.5.5-2.6 1.4-3.5 2.1-1.4 3.5-1.4 2.6.5 3.5 1.4 1.4 2.1 1.4 3.5-.5 2.6-1.4 3.5zm-5.5-1.4c.5.6 1.2.9 1.9.9.8 0 1.4-.3 2-.9s.8-1.3.8-2.1c0-.9-.3-1.6-.8-2.2s-1.2-.8-2-.8c-.7 0-1.5.3-2 .8-.5.6-.8 1.3-.8 2.2 0 .9.3 1.6.8 2.1zm12.6 7.2c-1.1 0-2-.3-2.8-.9s-1.3-1.3-1.6-2l1.9-.8c.2.5.5.9.9 1.2s.9.5 1.6.5c.8 0 1.5-.3 1.9-.7s.7-1.2.7-2.2v-.7h-.1c-.6.7-1.5 1.1-2.6 1.1-1.3 0-2.4-.5-3.3-1.4-1-.8-1.5-2.1-1.4-3.4-.1-1.3.4-2.6 1.4-3.5.9-1 2-1.4 3.3-1.4.6 0 1.1.1 1.5.3s.8.5 1.1.8h.1v-.8h2.1v8.9c0 1.7-.4 3-1.3 3.9-.9.8-2 1.3-3.4 1.3zm.1-6.4c.7.1 1.4-.2 1.8-.8.5-.6.8-1.3.8-2.1 0-.9-.3-1.6-.8-2.2-.4-.5-1.1-.8-1.8-.8-.8 0-1.4.3-1.9.9s-.8 1.3-.8 2.2c0 .8.3 1.6.8 2.1s1.2.9 1.9.9zm8.2-12.9v14.5h-2.2V63.4zm5.9 14.8c-1.4 0-2.6-.5-3.5-1.4s-1.4-2.1-1.4-3.5.5-2.6 1.4-3.6c.8-.8 2-1.3 3.3-1.3.6 0 1.2.1 1.7.3.4.3.8.5 1.2.9.3.3.6.6.8 1 .2.3.4.6.5 1l.2.6-6.6 2.7c.5 1 1.3 1.5 2.4 1.5 1 0 1.8-.5 2.4-1.4l1.7 1.1c-.4.6-.9 1.1-1.6 1.5s-1.5.7-2.5.7zm-2.7-5.1l4.4-1.8c-.1-.3-.4-.6-.7-.8-.4-.1-.8-.2-1.2-.2-.6 0-1.2.3-1.8.8s-.8 1.2-.8 2.1zM80 78.2c-2 0-3.6-.7-5-2s-2-3-2-5 .7-3.7 2-5 3-2 5-2 3.7.7 4.9 2.2l-1.2 1.2c-.9-1.1-2.2-1.7-3.7-1.7s-2.7.5-3.7 1.5-1.5 2.3-1.5 3.9.5 2.9 1.5 3.9 2.2 1.5 3.7 1.5c1.6 0 3-.6 4.1-1.9l1.2 1.2c-.6.7-1.4 1.3-2.3 1.7-1 .4-2 .6-3 .6zm8.6-.3h-1.7V64.6h1.7zm2.8-8.1c.9-.9 2-1.4 3.4-1.4s2.5.5 3.4 1.4 1.3 2.1 1.3 3.5-.4 2.6-1.3 3.5-2 1.4-3.4 1.4-2.5-.5-3.4-1.4-1.3-2.1-1.3-3.5.4-2.6 1.3-3.5zm1.3 5.9c.6.6 1.3.9 2.1.9s1.5-.3 2.1-.9.9-1.4.9-2.4-.3-1.8-.9-2.4-1.3-.9-2.1-.9-1.5.3-2.1.9-.9 1.4-.9 2.4.3 1.8.9 2.4zm16.3 2.2h-1.6v-1.3h-.1c-.3.4-.7.8-1.2 1.1s-1.1.5-1.7.5c-1.1 0-2-.3-2.6-1s-.9-1.6-.9-2.8v-5.6h1.7v5.3c0 1.7.8 2.6 2.3 2.6.6 0 1.3-.3 1.7-.8.4-.6.6-1.3.6-2v-5h1.7v9.2zm5.8.3c-1.2 0-2.2-.5-3.1-1.4s-1.3-2.1-1.3-3.5.4-2.5 1.3-3.5 1.9-1.4 3.1-1.4c.7 0 1.3.2 1.9.4s1 .7 1.2 1.1h.1l-.1-1.3v-4.2h1.7v13.4H118v-1.3h-.1c-.3.4-.7.8-1.2 1.1-.6.3-1.2.4-1.9.4zm.3-1.6c.7.1 1.5-.2 2-.8.6-.6.8-1.4.8-2.4s-.3-1.8-.8-2.4c-.5-.5-1.3-.9-2-.9-.8 0-1.5.3-2.1.9s-.9 1.4-.9 2.4.3 1.8.9 2.4c.5.6 1.3 1 2.1 1z"/></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

BIN
content/images/profile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

25
terraform/.terraform.lock.hcl generated Normal file
View File

@@ -0,0 +1,25 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "5.100.0"
constraints = "~> 5.0"
hashes = [
"h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=",
"zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644",
"zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2",
"zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274",
"zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b",
"zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862",
"zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93",
"zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2",
"zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e",
"zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421",
"zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4",
"zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9",
"zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9",
"zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70",
]
}

216
terraform/main.tf Normal file
View File

@@ -0,0 +1,216 @@
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "gregh-terraform-state"
key = "cdn-gregh-dev/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true
}
}
provider "aws" {
region = "us-east-1"
profile = "production"
}
# S3 bucket for CDN content
resource "aws_s3_bucket" "cdn" {
bucket = "cdn.cloud.gregh.dev"
tags = {
Name = "cdn.cloud.gregh.dev"
Environment = "production"
}
}
resource "aws_s3_bucket_public_access_block" "cdn" {
bucket = aws_s3_bucket.cdn.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_versioning" "cdn" {
bucket = aws_s3_bucket.cdn.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_cors_configuration" "cdn" {
bucket = aws_s3_bucket.cdn.id
cors_rule {
allowed_headers = ["*"]
allowed_methods = ["GET", "HEAD"]
allowed_origins = ["*"]
expose_headers = ["ETag"]
max_age_seconds = 3000
}
}
# CloudFront Origin Access Control
resource "aws_cloudfront_origin_access_control" "cdn" {
name = "cdn-gregh-dev-oac"
description = "OAC for cdn.cloud.gregh.dev"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
# S3 bucket policy to allow CloudFront access
resource "aws_s3_bucket_policy" "cdn" {
bucket = aws_s3_bucket.cdn.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCloudFrontServicePrincipal"
Effect = "Allow"
Principal = {
Service = "cloudfront.amazonaws.com"
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.cdn.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.cdn.arn
}
}
}
]
})
}
# ACM Certificate for CloudFront
resource "aws_acm_certificate" "cdn" {
domain_name = "cdn.cloud.gregh.dev"
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
tags = {
Name = "cdn.cloud.gregh.dev"
}
}
# Data source for existing hosted zone
data "aws_route53_zone" "main" {
name = "gregh.dev."
}
# Route53 records for ACM validation
resource "aws_route53_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.cdn.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.main.zone_id
}
# ACM certificate validation
resource "aws_acm_certificate_validation" "cdn" {
certificate_arn = aws_acm_certificate.cdn.arn
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
# CloudFront distribution
resource "aws_cloudfront_distribution" "cdn" {
enabled = true
is_ipv6_enabled = true
comment = "cdn.cloud.gregh.dev content"
price_class = "PriceClass_100"
aliases = ["cdn.cloud.gregh.dev"]
origin {
domain_name = aws_s3_bucket.cdn.bucket_regional_domain_name
origin_id = "S3-cdn.cloud.gregh.dev"
origin_access_control_id = aws_cloudfront_origin_access_control.cdn.id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-cdn.cloud.gregh.dev"
viewer_protocol_policy = "redirect-to-https"
compress = true
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.cdn.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
tags = {
Name = "cdn.cloud.gregh.dev"
Environment = "production"
}
}
# Route53 A record for CDN subdomain
resource "aws_route53_record" "cdn" {
zone_id = data.aws_route53_zone.main.zone_id
name = "cdn.cloud.gregh.dev"
type = "A"
alias {
name = aws_cloudfront_distribution.cdn.domain_name
zone_id = aws_cloudfront_distribution.cdn.hosted_zone_id
evaluate_target_health = false
}
}
# Outputs
output "s3_bucket_name" {
value = aws_s3_bucket.cdn.id
}
output "cloudfront_distribution_id" {
value = aws_cloudfront_distribution.cdn.id
}
output "cdn_url" {
value = "https://${aws_route53_record.cdn.name}"
}