├── .gitignore
├── README.md
├── datasources
└── tfc
│ └── workspaces.csv
├── modules
└── pleroma
│ ├── .gitignore
│ ├── README.md
│ ├── acm.tf
│ ├── cloudfront.tf
│ ├── data.tf
│ ├── iam.tf
│ ├── lb.tf
│ ├── main.tf
│ ├── outputs.tf
│ ├── postgres.tf
│ ├── s3.tf
│ ├── s3_backup.tf
│ ├── s3_logs.tf
│ ├── ses.tf
│ ├── sg.tf
│ ├── terraform.tf
│ └── variables.tf
├── snippets
└── provider_aws.tf
└── workspaces
├── manhole
├── .terraform.lock.hcl
├── data.tf
├── main.tf
├── pleroma_bot.tf
├── provider_aws.tf
└── terraform.tf
├── pleroma
├── .terraform.lock.hcl
├── data.tf
├── files
│ └── keybase.txt
├── gl.tf
├── keybase.tf
├── logstash.tf
├── main.tf
├── provider_aws.tf
└── terraform.tf
├── route53
├── .terraform.lock.hcl
├── main.tf
├── provider_aws.tf
└── terraform.tf
├── tfc
├── .terraform.lock.hcl
├── csv.tf
├── main.tf
└── terraform.tf
└── vpc
├── .terraform.lock.hcl
├── main.tf
├── outputs_vpc.tf
├── provider_aws.tf
└── terraform.tf
/.gitignore:
--------------------------------------------------------------------------------
1 | # Local .terraform directories
2 | **/.terraform/*
3 |
4 | # .tfstate files
5 | *.tfstate
6 | *.tfstate.*
7 |
8 | # Crash log files
9 | crash.log
10 |
11 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most
12 | # .tfvars files are managed as part of configuration and so should be included in
13 | # version control.
14 | #
15 | # example.tfvars
16 |
17 | # Ignore override files as they are usually used to override resources locally and so
18 | # are not checked in
19 | override.tf
20 | override.tf.json
21 | *_override.tf
22 | *_override.tf.json
23 |
24 | # Include override files you do wish to add to version control using negated pattern
25 | #
26 | # !example_override.tf
27 |
28 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
29 | # example: *tfplan*
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Terraform code for dabr.ca
2 |
3 | This repository contains Terraform code for [dabr.ca](https://dabr.ca/), a microblogging site powered by [Pleroma](https://pleroma.social/).
4 |
5 | In the event that the administrator of dabr.ca is unable to fulfil their responsibilities, anyone interested can use this repo to step up as a successor. If you want to run your own instance, you may need to do a find-and-replace for hard-coded values such as domain names.
6 |
7 | ## Setup
8 |
9 | 1. Have an AWS account ready.
10 | 2. Register a domain and add it to Route 53.
11 | 3. [Create a VPC](https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest).
12 | 4. Use the module in this repository to create infrastructure resources.
13 | 5. Run [Ansible playbook](https://github.com/dabr-ca/config) to install and configure Pleroma on the instance.
14 |
15 | ## Components
16 |
17 | * EC2 instance with ELB for TLS termination
18 | * RDS instance with PostgresSQL engine
19 | * S3 bucket with CloudFront for storing user-uploaded files
20 | * Security groups, IAM roles, ACM certificates, Route 53 records, etc
21 |
22 | Check [module README](./modules/pleroma/README.md) for details.
23 |
24 | ## Cost
25 |
26 | Most of the AWS resources are eligible for 12-month Free Tier, [after which cost is approximately 500 USD/y](https://calculator.aws/#/estimate?id=45a11934bdf6900573ad46263707edfc2ad4d44c). You can utilize [EC2 Instance Savings Plan](https://aws.amazon.com/savingsplans/compute-pricing/) and [RDS Reserved Instances](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithReservedDBInstances.html) to **cut the price down by more than half** if you are committed to run your site on AWS for more than 1 year.
27 |
--------------------------------------------------------------------------------
/datasources/tfc/workspaces.csv:
--------------------------------------------------------------------------------
1 | name,auto_apply,global_remote_state,modules
2 | pleroma,true,false,pleroma
3 | route53,true,false,
4 | tfc,true,false,
5 | vpc,true,true,vpc
6 |
--------------------------------------------------------------------------------
/modules/pleroma/.gitignore:
--------------------------------------------------------------------------------
1 | .terraform.lock.hcl
2 |
--------------------------------------------------------------------------------
/modules/pleroma/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Requirements
3 |
4 | | Name | Version |
5 | |------|---------|
6 | | [terraform](#requirement\_terraform) | >= 1.2.9 |
7 | | [aws](#requirement\_aws) | ~> 4.45 |
8 | | [random](#requirement\_random) | ~> 3.4 |
9 |
10 | ## Providers
11 |
12 | | Name | Version |
13 | |------|---------|
14 | | [aws](#provider\_aws) | 4.67.0 |
15 | | [aws.us-east-1](#provider\_aws.us-east-1) | 4.67.0 |
16 | | [random](#provider\_random) | 3.5.1 |
17 |
18 | ## Modules
19 |
20 | No modules.
21 |
22 | ## Resources
23 |
24 | | Name | Type |
25 | |------|------|
26 | | [aws_acm_certificate.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate) | resource |
27 | | [aws_acm_certificate.us-east-1](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate) | resource |
28 | | [aws_acm_certificate_validation.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation) | resource |
29 | | [aws_acm_certificate_validation.us-east-1](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation) | resource |
30 | | [aws_cloudfront_distribution.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource |
31 | | [aws_cloudfront_origin_access_control.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_control) | resource |
32 | | [aws_db_instance.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance) | resource |
33 | | [aws_db_subnet_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_subnet_group) | resource |
34 | | [aws_eip.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource |
35 | | [aws_iam_instance_profile.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource |
36 | | [aws_iam_role.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
37 | | [aws_iam_role_policy.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
38 | | [aws_instance.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource |
39 | | [aws_lb.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb) | resource |
40 | | [aws_lb_listener.main_http](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource |
41 | | [aws_lb_listener.main_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource |
42 | | [aws_lb_target_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource |
43 | | [aws_lb_target_group_attachment.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group_attachment) | resource |
44 | | [aws_route53_record.acm](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
45 | | [aws_route53_record.dkim](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
46 | | [aws_route53_record.files](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
47 | | [aws_route53_record.spf](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
48 | | [aws_route53_record.web](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
49 | | [aws_s3_bucket.backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
50 | | [aws_s3_bucket.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
51 | | [aws_s3_bucket.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
52 | | [aws_s3_bucket_acl.logs_cloudfront](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
53 | | [aws_s3_bucket_lifecycle_configuration.backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
54 | | [aws_s3_bucket_lifecycle_configuration.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
55 | | [aws_s3_bucket_policy.logs_elb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
56 | | [aws_s3_bucket_policy.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
57 | | [aws_s3_bucket_public_access_block.backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
58 | | [aws_s3_bucket_public_access_block.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
59 | | [aws_s3_bucket_public_access_block.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
60 | | [aws_s3_object.healthcheck](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource |
61 | | [aws_security_group.backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
62 | | [aws_security_group.db](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
63 | | [aws_security_group.lb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
64 | | [aws_ses_domain_dkim.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ses_domain_dkim) | resource |
65 | | [aws_ses_domain_identity.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ses_domain_identity) | resource |
66 | | [aws_ses_domain_identity_verification.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ses_domain_identity_verification) | resource |
67 | | [aws_ssm_parameter.postgres_address](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
68 | | [aws_ssm_parameter.postgres_heartbeat_url](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
69 | | [aws_ssm_parameter.postgres_name](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
70 | | [aws_ssm_parameter.postgres_password](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
71 | | [aws_ssm_parameter.postgres_username](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
72 | | [aws_ssm_parameter.s3_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
73 | | [aws_ssm_parameter.s3_bucket_backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
74 | | [aws_vpc_security_group_egress_rule.backend_all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource |
75 | | [aws_vpc_security_group_egress_rule.lb_all](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource |
76 | | [aws_vpc_security_group_ingress_rule.backend_lb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
77 | | [aws_vpc_security_group_ingress_rule.backend_mosh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
78 | | [aws_vpc_security_group_ingress_rule.backend_ssh](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
79 | | [aws_vpc_security_group_ingress_rule.db_backend](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
80 | | [aws_vpc_security_group_ingress_rule.lb_http](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
81 | | [aws_vpc_security_group_ingress_rule.lb_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
82 | | [aws_vpc_security_group_ingress_rule.lb_icmp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
83 | | [random_id.s3_bucket_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
84 | | [random_password.postgres](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
85 | | [aws_ami.ubuntu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source |
86 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
87 | | [aws_canonical_user_id.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source |
88 | | [aws_cloudfront_cache_policy.managed](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_cache_policy) | data source |
89 | | [aws_cloudfront_log_delivery_canonical_user_id.cloudfront](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_log_delivery_canonical_user_id) | data source |
90 | | [aws_cloudfront_origin_request_policy.managed](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_origin_request_policy) | data source |
91 | | [aws_elb_service_account.elb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/elb_service_account) | data source |
92 | | [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
93 | | [aws_iam_policy_document.ec2-assume-role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
94 | | [aws_iam_policy_document.logs_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
95 | | [aws_iam_policy_document.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
96 | | [aws_key_pair.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/key_pair) | data source |
97 | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
98 | | [aws_route53_zone.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |
99 | | [aws_subnet.ec2](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source |
100 |
101 | ## Inputs
102 |
103 | | Name | Description | Type | Default | Required |
104 | |------|-------------|------|---------|:--------:|
105 | | [domain](#input\_domain) | Domain of the Pleroma instance. This domain points to ELB. The domain must already exist in Route 53. | `string` | n/a | yes |
106 | | [ec2\_instance\_type](#input\_ec2\_instance\_type) | n/a | `string` | `"t3.micro"` | no |
107 | | [ec2\_key\_name](#input\_ec2\_key\_name) | Name of key pair to log into the EC2 instance. The key pair must already exist. | `string` | n/a | yes |
108 | | [files\_domain](#input\_files\_domain) | Domain for serving user-uploaded files. This domain points to CloudFront, whose origin is an S3 bucket. | `string` | n/a | yes |
109 | | [name](#input\_name) | Name of the service. It will be used to name EC2, ELB, and RDS instances. | `string` | `"pleroma"` | no |
110 | | [private\_subnet\_ids](#input\_private\_subnet\_ids) | List of IDs of subnets to create private resources (e.g. databases) in. | `list(string)` | n/a | yes |
111 | | [public\_subnet\_ids](#input\_public\_subnet\_ids) | List of IDs of subnets to create public resources (e.g. load balancers) in. | `list(string)` | n/a | yes |
112 | | [rds\_allocated\_storage](#input\_rds\_allocated\_storage) | n/a | `number` | `20` | no |
113 | | [rds\_instance\_class](#input\_rds\_instance\_class) | n/a | `string` | `"db.t4g.micro"` | no |
114 | | [rds\_storage\_type](#input\_rds\_storage\_type) | n/a | `string` | `"gp2"` | no |
115 | | [vpc\_id](#input\_vpc\_id) | ID of the VPC where the resources are created. | `string` | n/a | yes |
116 |
117 | ## Outputs
118 |
119 | | Name | Description |
120 | |------|-------------|
121 | | [bucket\_logs](#output\_bucket\_logs) | S3 bucket for storing CloudFront and ELB logs. |
122 | | [bucket\_main](#output\_bucket\_main) | S3 bucket for storing user-uploaded files. |
123 | | [instance](#output\_instance) | The main EC2 instance. |
124 | | [lb](#output\_lb) | The Application Load Balancer. |
125 |
--------------------------------------------------------------------------------
/modules/pleroma/acm.tf:
--------------------------------------------------------------------------------
1 | resource "aws_acm_certificate" "main" {
2 | domain_name = data.aws_route53_zone.main.name
3 | validation_method = "DNS"
4 | key_algorithm = "EC_secp384r1"
5 |
6 | subject_alternative_names = [
7 | "*.${data.aws_route53_zone.main.name}",
8 | ]
9 |
10 | lifecycle {
11 | create_before_destroy = true
12 | }
13 | }
14 |
15 | resource "aws_acm_certificate_validation" "main" {
16 | certificate_arn = aws_acm_certificate.main.arn
17 | validation_record_fqdns = [for record in aws_route53_record.acm : record.fqdn]
18 | }
19 |
20 | # CloudFront requires ACM to be in us-east-1, so duplicate the resources.
21 | resource "aws_acm_certificate" "us-east-1" {
22 | provider = aws.us-east-1
23 |
24 | domain_name = data.aws_route53_zone.main.name
25 | validation_method = "DNS"
26 | # CloudFront only supports P-256
27 | key_algorithm = "EC_prime256v1"
28 |
29 | subject_alternative_names = [
30 | "*.${data.aws_route53_zone.main.name}",
31 | ]
32 |
33 | lifecycle {
34 | create_before_destroy = true
35 | }
36 | }
37 |
38 | resource "aws_acm_certificate_validation" "us-east-1" {
39 | provider = aws.us-east-1
40 |
41 | certificate_arn = aws_acm_certificate.us-east-1.arn
42 | validation_record_fqdns = [for record in aws_route53_record.acm : record.fqdn]
43 | }
44 |
45 | resource "aws_route53_record" "acm" {
46 | for_each = {
47 | for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
48 | name = dvo.resource_record_name
49 | record = dvo.resource_record_value
50 | type = dvo.resource_record_type
51 | }
52 | }
53 |
54 | allow_overwrite = true
55 |
56 | zone_id = data.aws_route53_zone.main.zone_id
57 | name = each.value.name
58 | records = [each.value.record]
59 | ttl = 60
60 | type = each.value.type
61 | }
62 |
--------------------------------------------------------------------------------
/modules/pleroma/cloudfront.tf:
--------------------------------------------------------------------------------
1 | resource "aws_cloudfront_distribution" "main" {
2 | aliases = [var.files_domain]
3 | enabled = true
4 | http_version = "http2and3"
5 | is_ipv6_enabled = true
6 | price_class = "PriceClass_All"
7 | retain_on_delete = true
8 | wait_for_deployment = false
9 |
10 | default_cache_behavior {
11 | target_origin_id = aws_s3_bucket.main.bucket_regional_domain_name
12 |
13 | compress = true
14 | viewer_protocol_policy = "redirect-to-https"
15 | allowed_methods = ["GET", "HEAD"]
16 | cached_methods = ["GET", "HEAD"]
17 | cache_policy_id = data.aws_cloudfront_cache_policy.managed["CachingOptimized"].id
18 | origin_request_policy_id = data.aws_cloudfront_origin_request_policy.managed["CORS-S3Origin"].id
19 | }
20 |
21 | ordered_cache_behavior {
22 | path_pattern = "proxy/*"
23 | target_origin_id = aws_lb.main.dns_name
24 |
25 | compress = true
26 | viewer_protocol_policy = "redirect-to-https"
27 | allowed_methods = ["GET", "HEAD"]
28 | cached_methods = ["GET", "HEAD"]
29 | cache_policy_id = data.aws_cloudfront_cache_policy.managed["CachingOptimized"].id
30 | origin_request_policy_id = data.aws_cloudfront_origin_request_policy.managed["CORS-CustomOrigin"].id
31 | }
32 |
33 | origin {
34 | origin_id = aws_s3_bucket.main.bucket_regional_domain_name
35 | domain_name = aws_s3_bucket.main.bucket_regional_domain_name
36 | origin_access_control_id = aws_cloudfront_origin_access_control.main.id
37 | }
38 |
39 | origin {
40 | origin_id = aws_lb.main.dns_name
41 | domain_name = data.aws_route53_zone.main.name
42 |
43 | custom_origin_config {
44 | origin_protocol_policy = "https-only"
45 | http_port = 80
46 | https_port = 443
47 | origin_ssl_protocols = ["TLSv1.2"]
48 | }
49 | }
50 |
51 | restrictions {
52 | geo_restriction {
53 | restriction_type = "none"
54 | }
55 | }
56 |
57 | viewer_certificate {
58 | acm_certificate_arn = aws_acm_certificate_validation.us-east-1.certificate_arn
59 | minimum_protocol_version = "TLSv1.2_2021"
60 | ssl_support_method = "sni-only"
61 | }
62 |
63 | logging_config {
64 | bucket = aws_s3_bucket.logs.bucket_regional_domain_name
65 | prefix = "cloudfront/"
66 | }
67 | }
68 |
69 | resource "aws_cloudfront_origin_access_control" "main" {
70 | name = var.files_domain
71 | description = var.files_domain
72 | origin_access_control_origin_type = "s3"
73 | signing_behavior = "always"
74 | signing_protocol = "sigv4"
75 | }
76 |
77 | resource "aws_route53_record" "files" {
78 | for_each = toset(["A", "AAAA"])
79 |
80 | zone_id = data.aws_route53_zone.main.id
81 | name = var.files_domain
82 | type = each.key
83 |
84 | alias {
85 | zone_id = aws_cloudfront_distribution.main.hosted_zone_id
86 | name = aws_cloudfront_distribution.main.domain_name
87 | evaluate_target_health = false
88 | }
89 | }
90 |
91 | # Managed policies
92 | locals {
93 | managed_cache_policies = [
94 | "Amplify",
95 | "CachingDisabled",
96 | "CachingOptimized",
97 | "CachingOptimizedForUncompressedObjects",
98 | "Elemental-MediaPackage",
99 | ]
100 | managed_origin_request_policies = [
101 | "AllViewer",
102 | "CORS-CustomOrigin",
103 | "CORS-S3Origin",
104 | "Elemental-MediaTailor-PersonalizedManifests",
105 | "UserAgentRefererHeaders",
106 | ]
107 | }
108 |
109 | data "aws_cloudfront_cache_policy" "managed" {
110 | for_each = toset(local.managed_cache_policies)
111 |
112 | name = "Managed-${each.key}"
113 | }
114 |
115 | data "aws_cloudfront_origin_request_policy" "managed" {
116 | for_each = toset(local.managed_origin_request_policies)
117 |
118 | name = "Managed-${each.key}"
119 | }
120 |
--------------------------------------------------------------------------------
/modules/pleroma/data.tf:
--------------------------------------------------------------------------------
1 | data "aws_region" "current" {}
2 |
3 | data "aws_caller_identity" "current" {}
4 |
5 | data "aws_key_pair" "main" {
6 | key_name = var.ec2_key_name
7 | }
8 |
9 | data "aws_ami" "ubuntu" {
10 | most_recent = true
11 | owners = ["099720109477"] # Canonical
12 |
13 | filter {
14 | name = "name"
15 | values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
16 | }
17 | }
18 |
19 | data "aws_route53_zone" "main" {
20 | name = var.domain
21 | }
22 |
--------------------------------------------------------------------------------
/modules/pleroma/iam.tf:
--------------------------------------------------------------------------------
1 | resource "aws_iam_role" "main" {
2 | name = local.name
3 | assume_role_policy = data.aws_iam_policy_document.ec2-assume-role.json
4 | }
5 |
6 | data "aws_iam_policy_document" "ec2-assume-role" {
7 | statement {
8 | actions = ["sts:AssumeRole"]
9 |
10 | principals {
11 | type = "Service"
12 | identifiers = ["ec2.amazonaws.com"]
13 | }
14 | }
15 | }
16 |
17 | resource "aws_iam_instance_profile" "main" {
18 | name = aws_iam_role.main.name
19 | role = aws_iam_role.main.name
20 | }
21 |
22 | resource "aws_iam_role_policy" "main" {
23 | role = aws_iam_role.main.name
24 | policy = data.aws_iam_policy_document.main.json
25 | }
26 |
27 | data "aws_iam_policy_document" "main" {
28 | # Allow accessing S3 buckets
29 | statement {
30 | actions = [
31 | "s3:ListAllMyBuckets"
32 | ]
33 | resources = ["*"]
34 | }
35 | statement {
36 | actions = [
37 | "s3:ListBucket",
38 | "s3:PutObject",
39 | "s3:PutObjectAcl", # required by Ansible aws_s3 module
40 | "s3:GetObject",
41 | "s3:DeleteObject",
42 | ]
43 | resources = [
44 | aws_s3_bucket.main.arn,
45 | aws_s3_bucket.backup.arn,
46 | "${aws_s3_bucket.main.arn}/*",
47 | "${aws_s3_bucket.backup.arn}/*",
48 | ]
49 | }
50 | # Allow reading from parameter store
51 | # https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html
52 | statement {
53 | actions = [
54 | "ssm:GetParameter",
55 | "ssm:GetParameters",
56 | "ssm:GetParameterHistory",
57 | "ssm:GetParametersByPath",
58 | ]
59 | resources = [
60 | "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.id}:parameter/${local.name}/*"
61 | ]
62 | }
63 | statement {
64 | actions = [
65 | "ssm:DescribeParameters",
66 | ]
67 | resources = ["*"]
68 | }
69 | # Allow sending emails
70 | # SednRawEmail is required by Swoosh
71 | # https://hexdocs.pm/swoosh/Swoosh.Adapters.AmazonSES.html
72 | statement {
73 | actions = [
74 | "ses:SendRawEmail"
75 | ]
76 | resources = [
77 | aws_ses_domain_identity.main.arn
78 | ]
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/modules/pleroma/lb.tf:
--------------------------------------------------------------------------------
1 | resource "aws_lb" "main" {
2 | name = local.name
3 | subnets = var.public_subnet_ids
4 | security_groups = [aws_security_group.lb.id]
5 |
6 | access_logs {
7 | enabled = true
8 | bucket = aws_s3_bucket.logs.id
9 | prefix = "elb"
10 | }
11 | }
12 |
13 | resource "aws_lb_listener" "main_http" {
14 | load_balancer_arn = aws_lb.main.arn
15 | port = 80
16 | protocol = "HTTP"
17 |
18 | default_action {
19 | type = "redirect"
20 | redirect {
21 | port = "443"
22 | protocol = "HTTPS"
23 | status_code = "HTTP_301"
24 | }
25 | }
26 | }
27 |
28 | resource "aws_lb_listener" "main_https" {
29 | load_balancer_arn = aws_lb.main.arn
30 | port = 443
31 | protocol = "HTTPS"
32 | certificate_arn = aws_acm_certificate_validation.main.certificate_arn
33 | ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01"
34 |
35 | default_action {
36 | type = "forward"
37 | target_group_arn = aws_lb_target_group.main.arn
38 | }
39 | }
40 |
41 | resource "aws_lb_target_group" "main" {
42 | name = local.name
43 | port = 4000
44 | protocol = "HTTP"
45 | vpc_id = var.vpc_id
46 | }
47 |
48 | resource "aws_lb_target_group_attachment" "main" {
49 | target_group_arn = aws_lb_target_group.main.arn
50 | target_id = aws_instance.main.id
51 | }
52 |
53 | resource "aws_route53_record" "web" {
54 | zone_id = data.aws_route53_zone.main.id
55 | name = var.domain
56 | type = "A"
57 |
58 | alias {
59 | zone_id = aws_lb.main.zone_id
60 | name = aws_lb.main.dns_name
61 | evaluate_target_health = false
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/modules/pleroma/main.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | name = var.name
3 | }
4 |
5 | # Place EC2 in the same AZ as the RDS to avoid cross-AZ data charge
6 | data "aws_subnet" "ec2" {
7 | filter {
8 | name = "availability-zone"
9 | values = [local.db_az]
10 | }
11 | filter {
12 | name = "subnet-id"
13 | values = var.public_subnet_ids
14 | }
15 | }
16 |
17 | resource "aws_instance" "main" {
18 | ami = data.aws_ami.ubuntu.id
19 | instance_type = var.ec2_instance_type
20 | subnet_id = data.aws_subnet.ec2.id
21 | key_name = data.aws_key_pair.main.key_name
22 | iam_instance_profile = aws_iam_instance_profile.main.name
23 |
24 | vpc_security_group_ids = [aws_security_group.backend.id]
25 |
26 | root_block_device {
27 | volume_type = "gp3"
28 | tags = {
29 | Name = "${local.name}-root"
30 | }
31 | }
32 |
33 | tags = {
34 | Name = local.name
35 | }
36 |
37 | lifecycle {
38 | ignore_changes = [ami]
39 | }
40 | }
41 |
42 | resource "aws_eip" "main" {
43 | instance = aws_instance.main.id
44 | }
45 |
--------------------------------------------------------------------------------
/modules/pleroma/outputs.tf:
--------------------------------------------------------------------------------
1 | output "instance" {
2 | description = "The main EC2 instance."
3 | value = {
4 | id = aws_instance.main.id
5 | arn = aws_instance.main.arn
6 | public_ip = aws_eip.main.public_ip
7 | private_ip = aws_instance.main.private_ip
8 | }
9 | }
10 |
11 | output "lb" {
12 | description = "The Application Load Balancer."
13 | value = aws_lb.main
14 | }
15 |
16 | output "bucket_main" {
17 | description = "S3 bucket for storing user-uploaded files."
18 | value = aws_s3_bucket.main
19 | }
20 |
21 | output "bucket_logs" {
22 | description = "S3 bucket for storing CloudFront and ELB logs."
23 | value = aws_s3_bucket.logs
24 | }
25 |
--------------------------------------------------------------------------------
/modules/pleroma/postgres.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | # AWS: DBName must begin with a letter and contain only alphanumeric characters
3 | postgres_name = "pleroma"
4 | postgres_username = "pleroma"
5 | }
6 |
7 | resource "aws_db_instance" "main" {
8 | engine = "postgres"
9 | engine_version = "13.7" # default as of 2022-11
10 | parameter_group_name = "default.postgres13"
11 | identifier = local.name
12 | db_name = local.postgres_name
13 | instance_class = var.rds_instance_class
14 | storage_type = var.rds_storage_type
15 | allocated_storage = var.rds_allocated_storage
16 | multi_az = false
17 | db_subnet_group_name = aws_db_subnet_group.main.name
18 | vpc_security_group_ids = [aws_security_group.db.id]
19 |
20 | backup_retention_period = 7
21 | performance_insights_enabled = true
22 |
23 | apply_immediately = true
24 | deletion_protection = true
25 |
26 | username = local.postgres_username
27 | password = random_password.postgres.result
28 |
29 | lifecycle {
30 | # FIXME
31 | ignore_changes = [engine_version, allocated_storage]
32 | }
33 | }
34 |
35 | locals {
36 | # Indirectly referencing via a local variable avoids force reading of data
37 | # source.
38 | db_az = aws_db_instance.main.availability_zone
39 | }
40 |
41 | resource "aws_db_subnet_group" "main" {
42 | name = local.name
43 | subnet_ids = var.private_subnet_ids
44 | }
45 |
46 | resource "random_password" "postgres" {
47 | length = 16
48 | special = false
49 | }
50 |
51 | resource "aws_ssm_parameter" "postgres_address" {
52 | name = "/${local.name}/postgres/address"
53 | type = "String"
54 | value = aws_db_instance.main.address
55 | }
56 |
57 | resource "aws_ssm_parameter" "postgres_name" {
58 | name = "/${local.name}/postgres/name"
59 | type = "String"
60 | value = local.postgres_name
61 | }
62 |
63 | resource "aws_ssm_parameter" "postgres_username" {
64 | name = "/${local.name}/postgres/username"
65 | type = "String"
66 | value = local.postgres_username
67 | }
68 |
69 | resource "aws_ssm_parameter" "postgres_password" {
70 | name = "/${local.name}/postgres/password"
71 | type = "SecureString"
72 | value = random_password.postgres.result
73 | }
74 |
75 | resource "aws_ssm_parameter" "postgres_heartbeat_url" {
76 | name = "/${local.name}/postgres/heartbeat_url"
77 | type = "String"
78 | value = "." # populate this value manually
79 |
80 | lifecycle {
81 | ignore_changes = [value]
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/modules/pleroma/s3.tf:
--------------------------------------------------------------------------------
1 | resource "random_id" "s3_bucket_suffix" {
2 | byte_length = 4
3 | }
4 |
5 | resource "aws_s3_bucket" "main" {
6 | bucket = "${var.name}-${random_id.s3_bucket_suffix.hex}"
7 | }
8 |
9 | resource "aws_s3_bucket_public_access_block" "main" {
10 | bucket = aws_s3_bucket.main.id
11 |
12 | # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html
13 | block_public_acls = false # needed by Pleroma S3 uploader
14 | ignore_public_acls = true # ignore public ACL set by the Pleroma so that direct access is blocked
15 | block_public_policy = true
16 | restrict_public_buckets = true
17 | }
18 |
19 | resource "aws_s3_bucket_policy" "main" {
20 | bucket = aws_s3_bucket.main.id
21 | policy = data.aws_iam_policy_document.bucket_policy.json
22 | }
23 |
24 | data "aws_iam_policy_document" "bucket_policy" {
25 | # Allow CloudFront to read from the bucket
26 | statement {
27 | principals {
28 | type = "Service"
29 | identifiers = [
30 | "cloudfront.amazonaws.com"
31 | ]
32 | }
33 | actions = [
34 | "s3:GetObject"
35 | ]
36 | resources = [
37 | "${aws_s3_bucket.main.arn}/*",
38 | ]
39 | condition {
40 | test = "StringEquals"
41 | variable = "AWS:SourceArn"
42 | values = [aws_cloudfront_distribution.main.arn]
43 | }
44 | }
45 | }
46 |
47 | resource "aws_ssm_parameter" "s3_bucket" {
48 | name = "/${local.name}/s3_bucket"
49 | type = "String"
50 | value = aws_s3_bucket.main.bucket
51 | }
52 |
53 | resource "aws_s3_object" "healthcheck" {
54 | bucket = aws_s3_bucket.main.id
55 | key = "healthcheck"
56 | content = "OK"
57 | content_type = "text/plain"
58 | }
59 |
--------------------------------------------------------------------------------
/modules/pleroma/s3_backup.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | pg_dump_prefix = "pg_dump/"
3 | }
4 |
5 | resource "aws_s3_bucket" "backup" {
6 | bucket = "${var.name}-backup-${random_id.s3_bucket_suffix.hex}"
7 | }
8 |
9 | resource "aws_s3_bucket_public_access_block" "backup" {
10 | bucket = aws_s3_bucket.backup.id
11 |
12 | # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html
13 | block_public_acls = true
14 | ignore_public_acls = true
15 | block_public_policy = true
16 | restrict_public_buckets = true
17 | }
18 |
19 | resource "aws_s3_bucket_lifecycle_configuration" "backup" {
20 | bucket = aws_s3_bucket.backup.id
21 |
22 | rule {
23 | id = "ArchiveDatabaseDumps"
24 | status = "Enabled"
25 |
26 | filter {
27 | prefix = local.pg_dump_prefix
28 | }
29 |
30 | transition {
31 | days = 30 # min = 30
32 | storage_class = "STANDARD_IA"
33 | }
34 | }
35 |
36 | rule {
37 | id = "DeleteDatabaseDumps"
38 | status = "Enabled"
39 |
40 | filter {
41 | prefix = local.pg_dump_prefix
42 | }
43 |
44 | expiration {
45 | days = 365
46 | }
47 | }
48 | }
49 |
50 | resource "aws_ssm_parameter" "s3_bucket_backup" {
51 | name = "/${local.name}/s3_bucket_backup"
52 | type = "String"
53 | value = aws_s3_bucket.backup.bucket
54 | }
55 |
--------------------------------------------------------------------------------
/modules/pleroma/s3_logs.tf:
--------------------------------------------------------------------------------
1 | resource "aws_s3_bucket" "logs" {
2 | bucket = "${var.name}-logs-${random_id.s3_bucket_suffix.hex}"
3 | }
4 |
5 | resource "aws_s3_bucket_public_access_block" "logs" {
6 | bucket = aws_s3_bucket.logs.id
7 |
8 | # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html
9 | block_public_acls = true
10 | ignore_public_acls = true
11 | block_public_policy = true
12 | restrict_public_buckets = true
13 | }
14 |
15 | resource "aws_s3_bucket_lifecycle_configuration" "logs" {
16 | bucket = aws_s3_bucket.logs.id
17 |
18 | rule {
19 | id = "ExpireOldLogs"
20 | status = "Enabled"
21 |
22 | filter {}
23 |
24 | expiration {
25 | days = 365
26 | }
27 | }
28 | }
29 |
30 | # Allow CloudFront to deliver logs to the bucket
31 | # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html#AccessLogsBucketAndFileOwnership
32 | # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudfront_log_delivery_canonical_user_id
33 |
34 | data "aws_cloudfront_log_delivery_canonical_user_id" "cloudfront" {}
35 |
36 | data "aws_canonical_user_id" "current" {}
37 |
38 | resource "aws_s3_bucket_acl" "logs_cloudfront" {
39 | bucket = aws_s3_bucket.logs.id
40 |
41 | access_control_policy {
42 | grant {
43 | grantee {
44 | id = data.aws_cloudfront_log_delivery_canonical_user_id.cloudfront.id
45 | type = "CanonicalUser"
46 | }
47 | permission = "FULL_CONTROL"
48 | }
49 | owner {
50 | id = data.aws_canonical_user_id.current.id
51 | }
52 | }
53 | }
54 |
55 | # Allow ELB to deliver logs to the bucket
56 | # https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html
57 | # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/elb_service_account
58 |
59 | resource "aws_s3_bucket_policy" "logs_elb" {
60 | bucket = aws_s3_bucket.logs.id
61 | policy = data.aws_iam_policy_document.logs_bucket_policy.json
62 | }
63 |
64 | data "aws_elb_service_account" "elb" {}
65 |
66 | data "aws_iam_policy_document" "logs_bucket_policy" {
67 | statement {
68 | principals {
69 | type = "AWS"
70 | identifiers = [
71 | data.aws_elb_service_account.elb.arn
72 | ]
73 | }
74 | actions = [
75 | "s3:PutObject",
76 | ]
77 | resources = [
78 | "${aws_s3_bucket.logs.arn}/*"
79 | ]
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/modules/pleroma/ses.tf:
--------------------------------------------------------------------------------
1 | resource "aws_ses_domain_identity" "main" {
2 | domain = data.aws_route53_zone.main.name
3 | }
4 |
5 | resource "aws_ses_domain_dkim" "main" {
6 | domain = aws_ses_domain_identity.main.domain
7 | }
8 |
9 | resource "aws_ses_domain_identity_verification" "main" {
10 | domain = aws_ses_domain_identity.main.domain
11 | }
12 |
13 | resource "aws_route53_record" "dkim" {
14 | count = 3
15 |
16 | zone_id = data.aws_route53_zone.main.id
17 | name = "${element(aws_ses_domain_dkim.main.dkim_tokens, count.index)}._domainkey"
18 | type = "CNAME"
19 | ttl = 600
20 | records = ["${element(aws_ses_domain_dkim.main.dkim_tokens, count.index)}.dkim.amazonses.com"]
21 | }
22 |
23 | resource "aws_route53_record" "spf" {
24 | zone_id = data.aws_route53_zone.main.id
25 | name = aws_ses_domain_identity.main.domain
26 | type = "TXT"
27 | ttl = 600
28 | records = [var.domain_spf_record]
29 | }
30 |
--------------------------------------------------------------------------------
/modules/pleroma/sg.tf:
--------------------------------------------------------------------------------
1 | # Backend
2 | resource "aws_security_group" "backend" {
3 | name = local.name
4 | vpc_id = var.vpc_id
5 | }
6 |
7 | resource "aws_vpc_security_group_ingress_rule" "backend_ssh" {
8 | security_group_id = aws_security_group.backend.id
9 | ip_protocol = "tcp"
10 | from_port = 22
11 | to_port = 22
12 | cidr_ipv4 = "0.0.0.0/0"
13 | description = "SSH from everywhere"
14 | }
15 |
16 | resource "aws_vpc_security_group_ingress_rule" "backend_mosh" {
17 | security_group_id = aws_security_group.backend.id
18 | ip_protocol = "udp"
19 | from_port = 60000
20 | to_port = 61000
21 | cidr_ipv4 = "0.0.0.0/0"
22 | description = "Mosh from everywhere"
23 | }
24 |
25 | resource "aws_vpc_security_group_ingress_rule" "backend_lb" {
26 | security_group_id = aws_security_group.backend.id
27 | ip_protocol = "all"
28 | referenced_security_group_id = aws_security_group.lb.id
29 | description = "All from load balancer"
30 | }
31 |
32 | resource "aws_vpc_security_group_egress_rule" "backend_all" {
33 | security_group_id = aws_security_group.backend.id
34 | ip_protocol = "all"
35 | cidr_ipv4 = "0.0.0.0/0"
36 | description = "All to everywhere"
37 | }
38 |
39 | # Load balancer
40 | resource "aws_security_group" "lb" {
41 | name = "${local.name}-lb"
42 | vpc_id = var.vpc_id
43 | }
44 |
45 | resource "aws_vpc_security_group_ingress_rule" "lb_icmp" {
46 | security_group_id = aws_security_group.lb.id
47 | ip_protocol = "icmp"
48 | from_port = -1
49 | to_port = -1
50 | cidr_ipv4 = "0.0.0.0/0"
51 | description = "ICMP from everywhere"
52 | }
53 |
54 | resource "aws_vpc_security_group_ingress_rule" "lb_http" {
55 | security_group_id = aws_security_group.lb.id
56 | ip_protocol = "tcp"
57 | from_port = 80
58 | to_port = 80
59 | cidr_ipv4 = "0.0.0.0/0"
60 | description = "HTTP from everywhere"
61 | }
62 |
63 | resource "aws_vpc_security_group_ingress_rule" "lb_https" {
64 | security_group_id = aws_security_group.lb.id
65 | ip_protocol = "tcp"
66 | from_port = 443
67 | to_port = 443
68 | cidr_ipv4 = "0.0.0.0/0"
69 | description = "HTTPS from everywhere"
70 | }
71 |
72 | resource "aws_vpc_security_group_egress_rule" "lb_all" {
73 | security_group_id = aws_security_group.lb.id
74 | ip_protocol = "all"
75 | cidr_ipv4 = "0.0.0.0/0"
76 | description = "All to everywhere"
77 | }
78 |
79 | # Database
80 | resource "aws_security_group" "db" {
81 | name = "${local.name}-db"
82 | vpc_id = var.vpc_id
83 | }
84 |
85 | resource "aws_vpc_security_group_ingress_rule" "db_backend" {
86 | security_group_id = aws_security_group.db.id
87 | ip_protocol = "all"
88 | referenced_security_group_id = aws_security_group.backend.id
89 | description = "All from backend"
90 | }
91 |
--------------------------------------------------------------------------------
/modules/pleroma/terraform.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.2.9"
3 | required_providers {
4 | # gp3 requires > 4.45
5 | # https://github.com/hashicorp/terraform-provider-aws/issues/27702
6 | aws = {
7 | source = "hashicorp/aws"
8 | version = "~> 4.45"
9 | configuration_aliases = [aws.us-east-1]
10 | }
11 | random = {
12 | source = "hashicorp/random"
13 | version = "~> 3.4"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/modules/pleroma/variables.tf:
--------------------------------------------------------------------------------
1 | variable "name" {
2 | description = "Name of the service. It will be used to name EC2, ELB, and RDS instances."
3 | type = string
4 | default = "pleroma"
5 | }
6 |
7 | variable "domain" {
8 | description = "Domain of the Pleroma instance. This domain points to ELB. The domain must already exist in Route 53."
9 | type = string
10 | }
11 |
12 | variable "files_domain" {
13 | description = "Domain for serving user-uploaded files. This domain points to CloudFront, whose origin is an S3 bucket."
14 | type = string
15 | }
16 |
17 | variable "vpc_id" {
18 | description = "ID of the VPC where the resources are created."
19 | type = string
20 | }
21 |
22 | variable "private_subnet_ids" {
23 | description = "List of IDs of subnets to create private resources (e.g. databases) in."
24 | type = list(string)
25 | validation {
26 | condition = length(var.private_subnet_ids) >= 3
27 | error_message = "At least 3 private subnets are required."
28 | }
29 | }
30 |
31 | variable "public_subnet_ids" {
32 | description = "List of IDs of subnets to create public resources (e.g. load balancers) in."
33 | type = list(string)
34 | validation {
35 | condition = length(var.public_subnet_ids) >= 3
36 | error_message = "At least 3 public subnets are required."
37 | }
38 | }
39 |
40 | variable "ec2_instance_type" {
41 | type = string
42 | default = "t3.micro"
43 | }
44 |
45 | variable "ec2_key_name" {
46 | description = "Name of key pair to log into the EC2 instance. The key pair must already exist."
47 | type = string
48 | }
49 |
50 | variable "rds_instance_class" {
51 | type = string
52 | default = "db.t4g.micro"
53 | }
54 |
55 | variable "rds_storage_type" {
56 | type = string
57 | default = "gp2" # gp3 is no eligible for free tier
58 | }
59 |
60 | variable "rds_allocated_storage" {
61 | type = number
62 | default = 20
63 | }
64 |
65 | variable "domain_spf_record" {
66 | description = "Customize domain SPF record. By default only SES are allowed to send mails from the domain."
67 | type = string
68 | default = "v=spf1 include:amazonses.com -all"
69 | }
70 |
--------------------------------------------------------------------------------
/snippets/provider_aws.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | aws = {
4 | source = "hashicorp/aws"
5 | version = "~> 4.67"
6 | }
7 | }
8 | }
9 |
10 | variable "region" {
11 | type = string
12 | default = "us-west-2"
13 | description = "AWS region. Default to us-west-2."
14 | }
15 |
16 | provider "aws" {
17 | region = var.region
18 | }
19 |
20 | provider "aws" {
21 | alias = "us-east-1"
22 | region = "us-east-1"
23 | }
24 |
--------------------------------------------------------------------------------
/workspaces/manhole/.terraform.lock.hcl:
--------------------------------------------------------------------------------
1 | # This file is maintained automatically by "terraform init".
2 | # Manual edits may be lost in future updates.
3 |
4 | provider "registry.terraform.io/hashicorp/aws" {
5 | version = "4.67.0"
6 | constraints = "~> 4.67"
7 | hashes = [
8 | "h1:dCRc4GqsyfqHEMjgtlM1EympBcgTmcTkWaJmtd91+KA=",
9 | "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060",
10 | "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6",
11 | "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183",
12 | "zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1",
13 | "zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29",
14 | "zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7",
15 | "zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043",
16 | "zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362",
17 | "zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b",
18 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
19 | "zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf",
20 | "zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b",
21 | "zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c",
22 | "zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c",
23 | "zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d",
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/workspaces/manhole/data.tf:
--------------------------------------------------------------------------------
1 | data "terraform_remote_state" "vpc" {
2 | backend = "remote"
3 |
4 | config = {
5 | organization = "dabr-ca"
6 | workspaces = {
7 | name = "vpc"
8 | }
9 | }
10 | }
11 |
12 | data "aws_route53_zone" "main" {
13 | name = local.domain
14 | }
15 |
16 | data "aws_lb" "main" {
17 | name = local.name
18 | }
19 |
20 | data "aws_lb_listener" "main_https" {
21 | load_balancer_arn = data.aws_lb.main.arn
22 | port = 443
23 | }
24 |
25 | data "aws_ami" "ubuntu22" {
26 | most_recent = true
27 | owners = ["099720109477"] # Canonical
28 |
29 | filter {
30 | name = "name"
31 | values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/workspaces/manhole/main.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | name = "dabr-ca"
3 | domain = "dabr.ca"
4 | }
5 |
6 | data "aws_instance" "main" {
7 | filter {
8 | name = "tag:Name"
9 | values = [local.name]
10 | }
11 | }
12 |
13 | resource "aws_instance" "manhole" {
14 | ami = data.aws_ami.ubuntu22.id
15 | instance_type = data.aws_instance.main.instance_type
16 | subnet_id = data.aws_instance.main.subnet_id
17 | key_name = data.aws_instance.main.key_name
18 | iam_instance_profile = data.aws_instance.main.iam_instance_profile
19 | vpc_security_group_ids = data.aws_instance.main.vpc_security_group_ids
20 |
21 | root_block_device {
22 | volume_type = "gp3"
23 | }
24 |
25 | tags = {
26 | Name = "manhole"
27 | }
28 |
29 | lifecycle {
30 | ignore_changes = [ami]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/workspaces/manhole/pleroma_bot.tf:
--------------------------------------------------------------------------------
1 | resource "aws_ssm_parameter" "pleroma_bot_token" {
2 | name = "/${local.name}/pleroma_bot_token"
3 | type = "SecureString"
4 | value = "." # populate this value manually
5 |
6 | lifecycle {
7 | ignore_changes = [value]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/workspaces/manhole/provider_aws.tf:
--------------------------------------------------------------------------------
1 | ../../snippets/provider_aws.tf
--------------------------------------------------------------------------------
/workspaces/manhole/terraform.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.2.9"
3 | }
4 |
--------------------------------------------------------------------------------
/workspaces/pleroma/.terraform.lock.hcl:
--------------------------------------------------------------------------------
1 | # This file is maintained automatically by "terraform init".
2 | # Manual edits may be lost in future updates.
3 |
4 | provider "registry.terraform.io/hashicorp/aws" {
5 | version = "4.67.0"
6 | constraints = "~> 4.45, ~> 4.67"
7 | hashes = [
8 | "h1:dCRc4GqsyfqHEMjgtlM1EympBcgTmcTkWaJmtd91+KA=",
9 | ]
10 | }
11 |
12 | provider "registry.terraform.io/hashicorp/random" {
13 | version = "3.5.1"
14 | constraints = "~> 3.4"
15 | hashes = [
16 | "h1:VSnd9ZIPyfKHOObuQCaKfnjIHRtR7qTw19Rz8tJxm+k=",
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/workspaces/pleroma/data.tf:
--------------------------------------------------------------------------------
1 | data "terraform_remote_state" "vpc" {
2 | backend = "remote"
3 |
4 | config = {
5 | organization = "dabr-ca"
6 | workspaces = {
7 | name = "vpc"
8 | }
9 | }
10 | }
11 |
12 | data "aws_route53_zone" "main" {
13 | name = local.domain
14 | }
15 |
16 | data "aws_lb_listener" "main_https" {
17 | load_balancer_arn = module.pleroma.lb.arn
18 | port = 443
19 | }
20 |
--------------------------------------------------------------------------------
/workspaces/pleroma/files/keybase.txt:
--------------------------------------------------------------------------------
1 | ==================================================================
2 | https://keybase.io/wzyboy
3 | --------------------------------------------------------------------
4 |
5 | I hereby claim:
6 |
7 | * I am an admin of https://dabr.ca
8 | * I am wzyboy (https://keybase.io/wzyboy) on keybase.
9 | * I have a public key with fingerprint 34C2 38A1 522D BD63 9B33 957B 8532 9745 B099 9CAB
10 |
11 | To do so, I am signing this object:
12 |
13 | {
14 | "body": {
15 | "key": {
16 | "eldest_kid": "01168f2e1b2877c6c6013d03ecb213b801a17f5576419ccf6ae444adebe94359def40a",
17 | "fingerprint": "34c238a1522dbd639b33957b85329745b0999cab",
18 | "host": "keybase.io",
19 | "key_id": "85329745b0999cab",
20 | "kid": "01168f2e1b2877c6c6013d03ecb213b801a17f5576419ccf6ae444adebe94359def40a",
21 | "uid": "9977be95ac45cc41069f690be38b6600",
22 | "username": "wzyboy"
23 | },
24 | "service": {
25 | "hostname": "dabr.ca",
26 | "protocol": "https:"
27 | },
28 | "type": "web_service_binding",
29 | "version": 1
30 | },
31 | "ctime": 1717554735,
32 | "expire_in": 157680000,
33 | "prev": "759176828d70996d50bad18c6a1ee15cfd2b5f1d30687ef30ac145705770e7d2",
34 | "seqno": 29,
35 | "tag": "signature"
36 | }
37 |
38 | which yields the signature:
39 |
40 | -----BEGIN PGP MESSAGE-----
41 |
42 | owGtUj1oFEEY3ZgoeCAIwjUi6mhjOOP8z85ZWdhYRtDOdX6+vSxJds/dvcTziIUi
43 | WKgEQaKohbWKqQUjitgIgoWWglgJYmFhJzp7JJ2l0wzzvvfevO/jW901GbXa1+ja
44 | yfV7Dx5PvPtmozR5T0fIFn6IuiM0D+MLFjxUdTKfedRFmBAZpxSIpbFSTjqJCfOY
45 | gbOUMBtjYohKhVCSE+1cKg1wzo0HC5ozoT2kHBvUQWmW96Dsl1leB1vGHWWxIYJS
46 | b71k2jKmhbKxYFQrLizWWjtjg3CuqBpFCGdNBTNZEbDwSMbx/sH/z7kHYzutlQoV
47 | YRwXznGCpU6lxhZYbKXEuCFWUOZmEQJ7+dLQFkO00kEBW8ocNGNt+tise2PLGde4
48 | 98uiLlyxEMC5uu5X3UZUD/tjF7DJpj6xWe7DAINiCcoqK3LUJYHp6qwxJIooIbhi
49 | ooPgYj8rIckaRuguxuE0/8BSsFRCk4DR2KswsFnpBbbGk9hJQwCIcKmnVqTEMyxj
50 | BSnDxhEuFBZKYVCeoqalC3mBulSHoKYXTKusl5t6UAJaaV0fTEXtVrSvfXCKv44f
51 | zR594e6zO6Otpdu+rdm4qLVz9xZyik1EP4pk+syHWzOD7pObL9eevb16483HQzuO
52 | nHt++MvGgVdXPk9Ev5PbX+Xynk8/zx9/uHdx9eyv6WNP7574s//7xvppd9lO/gU=
53 | =QPyp
54 | -----END PGP MESSAGE-----
55 |
56 | And finally, I am proving ownership of this host by posting or
57 | appending to this document.
58 |
59 | View my publicly-auditable identity here: https://keybase.io/wzyboy
60 |
61 | ==================================================================
62 |
63 | ==================================================================
64 | https://keybase.io/wzyboy
65 | --------------------------------------------------------------------
66 |
67 | I hereby claim:
68 |
69 | * I am an admin of https://dabr.ca
70 | * I am wzyboy (https://keybase.io/wzyboy) on keybase.
71 | * I have a public key with fingerprint CDEF 6ED1 A4C7 ADE1 D6AB C4DB AE9C 2216 8EBA 0BDB
72 |
73 | To do so, I am signing this object:
74 |
75 | {
76 | "body": {
77 | "key": {
78 | "eldest_kid": "0101d8255c38b754f928ecb171d81a32fd30ef33c01fb0ef40df702189cadc88c23c0a",
79 | "fingerprint": "cdef6ed1a4c7ade1d6abc4dbae9c22168eba0bdb",
80 | "host": "keybase.io",
81 | "key_id": "ae9c22168eba0bdb",
82 | "kid": "0101d8255c38b754f928ecb171d81a32fd30ef33c01fb0ef40df702189cadc88c23c0a",
83 | "uid": "9977be95ac45cc41069f690be38b6600",
84 | "username": "wzyboy"
85 | },
86 | "service": {
87 | "hostname": "dabr.ca",
88 | "protocol": "https:"
89 | },
90 | "type": "web_service_binding",
91 | "version": 1
92 | },
93 | "ctime": 1671753572,
94 | "expire_in": 157680000,
95 | "prev": "073de9d05f74d170f894c844412f9aac765d99d88eb5ae0cd9046bebb1b52dec",
96 | "seqno": 24,
97 | "tag": "signature"
98 | }
99 |
100 | which yields the signature:
101 |
102 | -----BEGIN PGP MESSAGE-----
103 |
104 | owGtUmtMFFcY3QU2XeSlphKlUmEQJAo6MzvPLVKrojSIBGg1hQLOnXtnGWF3cXdF
105 | Hl1RK4IkAgI/WpCCldoAPni2BbGptQrWIiFQWqVqbUBLMEFICoXYxwzRf/3Z++fe
106 | e75zzv3uyVfu5arRay+dwXxLuz1GtbcngEZsmDLlY8AKczFjPpaBFjeUCZHdkZYh
107 | Q8yI4QROQI6kadHAAZamJJ7kkAgIVkEJwUBK0IAjyWAQcUICyonCocTiJMHxogBF
108 | jhNJpSRgYZgkW0zIlmWTLQ7FVoRIYhAkBEpkBYgIyAhApCAQEC+SJMFwCAg4gEAR
109 | plvtqkJpDgh2tFG2KphySVts7z/4/3PfBxfteJ5lAeJpQaRoUaQInOElhscBUtwZ
110 | BsdVoh3ZLIIZKexDebnAmos5wzAFy5ZFpMaq/uNFHQrAtlFU3bNsVodVtGYqYLrD
111 | kWU3qiJHbtaiCwJpL/RpQLZAJUBFkY1sdtlqwYyEwhQdsmpIMCzB0gaaJcMwlJMl
112 | 21CarDJoluFwZanvoGw1FdYAEQ9xWmIpSCSwuMTxlMhRFEWQEi8IIsvQkOchp+RJ
113 | CwgXIY9TDEAAEIAmIRIx9UsHLFbMSFJKo4JJMbXLJovgOGhDmHPJSReDm0ar1/j7
114 | Brr1T1kGG3ovPhpp/m705dTpXNSR0yxxX/oS2T2yVFN2uOvKpX3LFib02n+Czrcd
115 | KTi9YCYKOjcjkMAUn5zON5/X/+KvP7DcxUu7IufpruP2sg9rbrZ81Pf+ztdKhauD
116 | PXDg18f6wR+jN9XOz8jg4ZPOy8fueleWh37sHIv6jKwIfHYvcd3zq6kxhtpt7Xvc
117 | VlM/3No+EdxxtlDr4pwZYUff9fBMtufsi6gedsTtTNB61KS7PrBgk0ENicNkfMfx
118 | G3VjbXHr7ww21c+7tizMiYXbn9QHPF9WUhSKbdlh9zyRTAy3aqtD7nf13fbdkdHt
119 | U3h4M722q/DrkKQx+/pzeMnAdIdvWPnvfl9ePGX+OZ9vD4ifGy6K3XMn0mP2+6GI
120 | o/HVPUkTATHRpcEVmTrdgF+QVLy3PDtrhbcuvrBxw43xvmMjjYmnU9zX/PGT19xM
121 | c9WFR2vO5s1Wen1zrz5vfFVKoPnTrTGReUVP+3trP/gtInzoftP1c1WNumBP3e7V
122 | 7V9EG7Qkt+umf0J47KqhQw91j1sfbHH/iql6O/b1KZP+grfNNyY0+VqSa42u4L3P
123 | 24YuR9Q56ZlO52zCKy1xlmbjurUBbm9MRsGjGbd6zQOQtWzqG02Sl/+5YeW8Dk99
124 | 1mq70lhh1r/qVny3xCcDjYd3R/mgv8dStgVXvhMWsxfNTeh79uvKvr2W79epd6au
125 | dG6NPDXpcn3a9NeRiDf3N4W8Zew/E1/3yb8=
126 | =xt0T
127 | -----END PGP MESSAGE-----
128 |
129 | And finally, I am proving ownership of this host by posting or
130 | appending to this document.
131 |
132 | View my publicly-auditable identity here: https://keybase.io/wzyboy
133 |
134 | ==================================================================
135 |
--------------------------------------------------------------------------------
/workspaces/pleroma/gl.tf:
--------------------------------------------------------------------------------
1 | # Set up glitch-lily as an alternative FE. This is not considered a part of
2 | # Pleroma so it's not codified in the module.
3 |
4 | locals {
5 | gl_domain = "gl.${local.domain}"
6 | }
7 |
8 | resource "aws_lb_listener_rule" "gl" {
9 | listener_arn = data.aws_lb_listener.main_https.arn
10 |
11 | condition {
12 | host_header {
13 | values = [local.gl_domain]
14 | }
15 | }
16 |
17 | action {
18 | type = "forward"
19 | target_group_arn = aws_lb_target_group.gl.arn
20 | }
21 | }
22 |
23 | resource "aws_lb_target_group" "gl" {
24 | name = "${local.name}-gl"
25 | port = 4080
26 | protocol = "HTTP"
27 | vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id
28 | }
29 |
30 | resource "aws_lb_target_group_attachment" "gl" {
31 | target_group_arn = aws_lb_target_group.gl.arn
32 | target_id = module.pleroma.instance.id
33 | }
34 |
35 | resource "aws_route53_record" "gl" {
36 | zone_id = data.aws_route53_zone.main.id
37 | name = local.gl_domain
38 | type = "A"
39 |
40 | alias {
41 | zone_id = module.pleroma.lb.zone_id
42 | name = module.pleroma.lb.dns_name
43 | evaluate_target_health = false
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/workspaces/pleroma/keybase.tf:
--------------------------------------------------------------------------------
1 | # Set up Keybase verification.
2 |
3 | resource "aws_lb_listener_rule" "keybase" {
4 | listener_arn = data.aws_lb_listener.main_https.arn
5 |
6 | condition {
7 | host_header {
8 | values = [local.domain]
9 | }
10 | }
11 | condition {
12 | path_pattern {
13 | values = ["/keybase.txt"]
14 | }
15 | }
16 |
17 | action {
18 | type = "redirect"
19 | redirect {
20 | host = "files.${local.domain}"
21 | status_code = "HTTP_302"
22 | }
23 | }
24 | }
25 |
26 | # ALB does not allow fixed-response body to be > 1024 bytes.
27 | resource "aws_s3_object" "keybase" {
28 | bucket = module.pleroma.bucket_main.bucket
29 | key = "keybase.txt"
30 | content = file("./files/keybase.txt")
31 | content_type = "text/plain"
32 | }
33 |
--------------------------------------------------------------------------------
/workspaces/pleroma/logstash.tf:
--------------------------------------------------------------------------------
1 | # Set up a static IAM user so that Logstash can read logs from a non-AWS environment
2 |
3 | resource "aws_iam_user" "logstash" {
4 | name = "logstash"
5 | }
6 |
7 | resource "aws_iam_user_policy" "logstash" {
8 | user = aws_iam_user.logstash.name
9 | policy = data.aws_iam_policy_document.logstash.json
10 | }
11 |
12 | resource "aws_iam_access_key" "logstash" {
13 | user = aws_iam_user.logstash.name
14 | }
15 |
16 | data "aws_iam_policy_document" "logstash" {
17 | statement {
18 | actions = ["s3:ListBucket"]
19 | resources = [module.pleroma.bucket_logs.arn]
20 | }
21 | statement {
22 | actions = ["s3:GetObject"]
23 | resources = ["${module.pleroma.bucket_logs.arn}/*"]
24 | }
25 | }
26 |
27 | output "logstash" {
28 | description = "Information for setting up Logstash."
29 | value = {
30 | bucket = module.pleroma.bucket_logs.id
31 | access_key_id = aws_iam_access_key.logstash.id
32 | secret_access_key = aws_iam_access_key.logstash.secret
33 | }
34 | sensitive = true
35 | }
36 |
--------------------------------------------------------------------------------
/workspaces/pleroma/main.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | name = "dabr-ca"
3 | domain = "dabr.ca"
4 | }
5 |
6 | module "pleroma" {
7 | source = "../../modules/pleroma"
8 | providers = {
9 | aws.us-east-1 = aws.us-east-1
10 | }
11 |
12 | name = local.name
13 | domain = local.domain
14 | files_domain = "files.${local.domain}"
15 | vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id
16 | private_subnet_ids = data.terraform_remote_state.vpc.outputs.subnet_ids.private[*]
17 | public_subnet_ids = data.terraform_remote_state.vpc.outputs.subnet_ids.public[*]
18 | ec2_key_name = "wzyboy@tarball"
19 |
20 | rds_instance_class = "db.m6g.large"
21 | rds_storage_type = "gp3"
22 | rds_allocated_storage = 25
23 |
24 | domain_spf_record = "v=spf1 include:amazonses.com include:spf.messagingengine.com -all"
25 | }
26 |
--------------------------------------------------------------------------------
/workspaces/pleroma/provider_aws.tf:
--------------------------------------------------------------------------------
1 | ../../snippets/provider_aws.tf
--------------------------------------------------------------------------------
/workspaces/pleroma/terraform.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.2.9"
3 | cloud {
4 | organization = "dabr-ca"
5 | workspaces {
6 | name = "pleroma"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/workspaces/route53/.terraform.lock.hcl:
--------------------------------------------------------------------------------
1 | # This file is maintained automatically by "terraform init".
2 | # Manual edits may be lost in future updates.
3 |
4 | provider "registry.terraform.io/hashicorp/aws" {
5 | version = "4.67.0"
6 | constraints = "~> 4.67"
7 | hashes = [
8 | "h1:dCRc4GqsyfqHEMjgtlM1EympBcgTmcTkWaJmtd91+KA=",
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/workspaces/route53/main.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | domain = "dabr.ca"
3 | }
4 |
5 | resource "aws_route53_zone" "main" {
6 | name = local.domain
7 | }
8 |
9 | # Set up MX to receive emails
10 | resource "aws_route53_record" "mx" {
11 | zone_id = aws_route53_zone.main.id
12 |
13 | name = local.domain
14 | type = "MX"
15 | ttl = 300
16 | records = [
17 | "10 in1-smtp.messagingengine.com.",
18 | "20 in2-smtp.messagingengine.com.",
19 | ]
20 | }
21 |
22 | # Set up DKIM for FastMail
23 | # NOTE: SPF is set up in ../pleroma
24 | resource "aws_route53_record" "dkim" {
25 | for_each = toset(["fm1", "fm2", "fm3"])
26 |
27 | zone_id = aws_route53_zone.main.id
28 | name = "${each.key}._domainkey.${local.domain}."
29 | type = "CNAME"
30 | ttl = "300"
31 | records = ["${each.key}.${local.domain}.dkim.fmhosted.com."]
32 | }
33 |
34 | # Status page
35 | resource "aws_route53_record" "status" {
36 | zone_id = aws_route53_zone.main.id
37 |
38 | name = "status.${local.domain}"
39 | type = "CNAME"
40 | ttl = 300
41 | records = ["statuspage.betteruptime.com"]
42 | }
43 |
--------------------------------------------------------------------------------
/workspaces/route53/provider_aws.tf:
--------------------------------------------------------------------------------
1 | ../../snippets/provider_aws.tf
--------------------------------------------------------------------------------
/workspaces/route53/terraform.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.5.5"
3 | cloud {
4 | organization = "dabr-ca"
5 | workspaces {
6 | name = "route53"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/workspaces/tfc/.terraform.lock.hcl:
--------------------------------------------------------------------------------
1 | # This file is maintained automatically by "terraform init".
2 | # Manual edits may be lost in future updates.
3 |
4 | provider "registry.terraform.io/hashicorp/tfe" {
5 | version = "0.39.0"
6 | constraints = "~> 0.39.0"
7 | hashes = [
8 | "h1:6gbiHfdJAUopCXoqj3khdv4eSMpgQSy8HuCwVqSPJic=",
9 | "zh:084f4de9d660780bc326d86fa93ecd383455dc040f52931766a5360f13791bea",
10 | "zh:09d12b42d7bbf9fd11663aa98266eae20d6df4f989f4fca05d4a15b917572a00",
11 | "zh:0e6da14680a01c3ad4c53ea375edb28ca15927b5ad0fa775efc8dc17b684c68b",
12 | "zh:29e6c0725c84576d7533eff03c6af71f48decfce79ffcbe655111870f6c712b9",
13 | "zh:32bbc784492251b3dac028fa8b95ad87fb9828ba52a7f6df85a1c3e00c444918",
14 | "zh:605f46c284ea2eac591ec42b58259761dbe5130d02d31bc0c9388d36c5b74c9f",
15 | "zh:6b7082dc3608b05540aee00c9fe1a31e196d49f1422c8cc9da03b252ee1f9d9c",
16 | "zh:8293aa197bf70f75f56ca9e51b168d7e7b18e0339a2d86bdf5926727b34e85d8",
17 | "zh:d115d053faa140f45bd99156be6c5a629c61257c7811715793f6d8512cc09a80",
18 | "zh:d95ec293fa70e946b6cd657912b33155f8be3413e6128ed2bfa5a493f788e439",
19 | "zh:e89c4a0eff9350265084c8200802a68de6aeeeeb74aa776be1d62f36a0f32552",
20 | "zh:f79757b6c54c4c80e9c797fd1bac9e535bb3e106318ea590d85b3effff87b19f",
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/workspaces/tfc/csv.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | default_terraform_version = "1.5.5"
3 |
4 | csv = csvdecode(file("../../datasources/tfc/workspaces.csv"))
5 | workspaces = {
6 | for workspace in local.csv :
7 | workspace["name"] => {
8 | name = trimspace(workspace["name"])
9 | auto_apply = tobool(workspace["auto_apply"])
10 | global_remote_state = tobool(workspace["global_remote_state"])
11 | modules = compact(split(":", trimspace(workspace["modules"])))
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/workspaces/tfc/main.tf:
--------------------------------------------------------------------------------
1 | # This workspace uses tfe provider to manages all workspaces on Terraform Cloud,
2 | # including itself.
3 |
4 | data "tfe_organization" "dabr-ca" {
5 | name = "dabr-ca"
6 | }
7 |
8 | locals {
9 | # https://app.terraform.io/app/dabr-ca/settings/version-control
10 | gh_oauth_token_id = "ot-YGoaidJBNjpHQTj3"
11 | }
12 |
13 | resource "tfe_workspace" "workspaces" {
14 | for_each = local.workspaces
15 |
16 | name = each.key
17 | organization = data.tfe_organization.dabr-ca.name
18 | terraform_version = local.default_terraform_version
19 | allow_destroy_plan = false
20 | auto_apply = each.value.auto_apply
21 | execution_mode = "remote"
22 | global_remote_state = each.value.global_remote_state
23 |
24 | trigger_prefixes = flatten([
25 | "/datasources/${each.key}",
26 | [for m in each.value.modules : "/modules/${m}"],
27 | ])
28 |
29 | vcs_repo {
30 | identifier = "dabr-ca/infra"
31 | oauth_token_id = local.gh_oauth_token_id
32 | }
33 | working_directory = "/workspaces/${each.key}"
34 | }
35 |
--------------------------------------------------------------------------------
/workspaces/tfc/terraform.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_providers {
3 | tfe = {
4 | source = "hashicorp/tfe"
5 | version = "~> 0.39.0"
6 | }
7 | }
8 |
9 | cloud {
10 | organization = "dabr-ca"
11 | workspaces {
12 | name = "tfc"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/workspaces/vpc/.terraform.lock.hcl:
--------------------------------------------------------------------------------
1 | # This file is maintained automatically by "terraform init".
2 | # Manual edits may be lost in future updates.
3 |
4 | provider "registry.terraform.io/hashicorp/aws" {
5 | version = "4.67.0"
6 | constraints = ">= 3.63.0, ~> 4.67"
7 | hashes = [
8 | "h1:dCRc4GqsyfqHEMjgtlM1EympBcgTmcTkWaJmtd91+KA=",
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/workspaces/vpc/main.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | name = "main"
3 | cidr_block = "10.31.0.0/16"
4 | subnets = cidrsubnets(local.cidr_block, 4, 4, 4, 4, 4, 4)
5 | subnet_groups = chunklist(local.subnets, 3)
6 | }
7 |
8 | module "vpc" {
9 | source = "terraform-aws-modules/vpc/aws"
10 | version = "4.0.2"
11 |
12 | name = local.name
13 | cidr = local.cidr_block
14 |
15 | azs = ["${var.region}a", "${var.region}b", "${var.region}c"]
16 | private_subnets = local.subnet_groups[0]
17 | public_subnets = local.subnet_groups[1]
18 |
19 | enable_nat_gateway = false
20 | single_nat_gateway = true
21 | map_public_ip_on_launch = true
22 | }
23 |
24 | resource "aws_key_pair" "tarball" {
25 | key_name = "wzyboy@tarball"
26 | public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDB3eYUem12rVaP+2ijbGqFqTM4bfnYcYmHjDq7j6IjT wzyboy@tarball"
27 | }
28 |
--------------------------------------------------------------------------------
/workspaces/vpc/outputs_vpc.tf:
--------------------------------------------------------------------------------
1 | output "vpc_id" {
2 | value = module.vpc.vpc_id
3 | }
4 |
5 | output "vpc_cidr_block" {
6 | value = module.vpc.vpc_cidr_block
7 | }
8 |
9 | output "subnet_ids" {
10 | description = "Map of subnet type => [id]"
11 | value = {
12 | private = module.vpc.private_subnets
13 | public = module.vpc.public_subnets
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/workspaces/vpc/provider_aws.tf:
--------------------------------------------------------------------------------
1 | ../../snippets/provider_aws.tf
--------------------------------------------------------------------------------
/workspaces/vpc/terraform.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | cloud {
3 | organization = "dabr-ca"
4 | workspaces {
5 | name = "vpc"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------