From 76d25ba1622a07e25834ce5b0c228c613d6bfd99 Mon Sep 17 00:00:00 2001 From: Roberto Hidalgo Date: Fri, 20 Jan 2023 12:55:33 -0600 Subject: [PATCH] =?UTF-8?q?club=20=F0=9F=A6=86ito?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 13 ++ .gitignore | 2 + config.joao.yaml | 14 ++ gotosocial.yaml | 341 +++++++++++++++++++++++++++++++++++++++++++++++ gts.nomad | 143 ++++++++++++++++++++ litestream.yaml | 9 ++ main.tf | 145 ++++++++++++++++++++ 7 files changed, 667 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 config.joao.yaml create mode 100644 gotosocial.yaml create mode 100644 gts.nomad create mode 100644 litestream.yaml create mode 100644 main.tf diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6992289 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 +max_line_length = 120 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8bdfa91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.terraform.lock.hcl +.terraform diff --git a/config.joao.yaml b/config.joao.yaml new file mode 100644 index 0000000..363d20f --- /dev/null +++ b/config.joao.yaml @@ -0,0 +1,14 @@ +_config: !!joao + vault: nidito + name: pati.to:club +smtp: + host: smtp.mailgun.org + port: 587 + username: no-reply@mail.club.pati.to + password: !!secret +do_token: !!secret +cdn: + bucket: club-patito + endpoint: sjc1.vultrobjects.com + key: !!secret + secret: !!secret diff --git a/gotosocial.yaml b/gotosocial.yaml new file mode 100644 index 0000000..83d0987 --- /dev/null +++ b/gotosocial.yaml @@ -0,0 +1,341 @@ +{{- $domain := "club.pati.to" -}} +########################### +##### GENERAL CONFIG ###### +########################### +# String. Log level to use throughout the application. Must be lower-case. +# Options: ["trace","debug","info","warn","error","fatal"] +# Default: "info" +log-level: "info" +log-db-queries: false + +application-name: "club patito" + +# String. Hostname that this server will be reachable at. Defaults to localhost for local testing, +# but you should *definitely* change this when running for real, or your server won't work at all. +# DO NOT change this after your server has already run once, or you will break things! +# Examples: ["gts.example.org","some.server.com"] +# Default: "localhost" +host: "{{ $domain }}" + +# String. Domain to use when federating profiles. This is useful when you want your server to be at +# eg., "gts.example.org", but you want the domain on accounts to be "example.org" because it looks better +# or is just shorter/easier to remember. +# To make this setting work properly, you need to redirect requests at "example.org/.well-known/webfinger" +# to "gts.example.org/.well-known/webfinger" so that GtS can handle them properly. +# You should also redirect requests at "example.org/.well-known/nodeinfo" in the same way. +# An empty string (ie., not set) means that the same value as 'host' will be used. +# DO NOT change this after your server has already run once, or you will break things! +# Examples: ["example.org","server.com"] +# Default: "" +account-domain: "{{ $domain }}" + +# String. Protocol to use for the server. Only change to http for local testing! +# This should be the protocol part of the URI that your server is actually reachable on. So even if you're +# running GoToSocial behind a reverse proxy that handles SSL certificates for you, instead of using built-in +# letsencrypt, it should still be https. +# Options: ["http","https"] +# Default: "https" +protocol: "https" + +# String. Address to bind the GoToSocial server to. +# This can be an IPv4 address or an IPv6 address (surrounded in square brackets), or a hostname. +# Default value will bind to all interfaces. +# You probably won't need to change this unless you're setting GoToSocial up in some fancy way or +# you have specific networking requirements. +# Examples: ["0.0.0.0", "172.128.0.16", "localhost", "[::]", "[2001:db8::fed1]"] +# Default: "0.0.0.0" +bind-address: "0.0.0.0" + +# Int. Listen port for the GoToSocial webserver + API. If you're running behind a reverse proxy and/or in a docker, +# container, just set this to whatever you like (or leave the default), and make sure it's forwarded properly. +# If you are running with built-in letsencrypt enabled, and running GoToSocial directly on a host machine, you will +# probably want to set this to 443 (standard https port), unless you have other services already using that port. +# This *MUST NOT* be the same as the letsencrypt port specified below, unless letsencrypt is turned off. +# Examples: [443, 6666, 8080] +# Default: 8080 +port: {{ env "NOMAD_PORT_gotosocial" }} + +# Array of string. CIDRs or IP addresses of proxies that should be trusted when determining real client IP from behind a reverse proxy. +# If you're running inside a Docker container behind Traefik or Nginx, for example, add the subnet of your docker network, +# or the gateway of the docker network, and/or the address of the reverse proxy (if it's not running on the host network). +# Example: ["127.0.0.1/32", "172.20.0.1"] +# Default: ["127.0.0.1/32", "::1"] (localhost ipv4 + ipv6) +trusted-proxies: + - "10.100.0.0/20" + - "127.0.0.1/32" + - "::1" + +############################ +##### DATABASE CONFIG ###### +############################ + +# Config pertaining to the Gotosocial database connection + +# String. Database type. +# Options: ["postgres","sqlite"] +# Default: "postgres" +db-type: "sqlite" + +# String. Database address or parameters. +# +# For Postgres, this should be the address or socket at which the database can be reached. +# +# For Sqlite, this should be the path to your sqlite database file. Eg., /opt/gotosocial/sqlite.db. +# If the file doesn't exist at the specified path, it will be created. +# If just a filename is provided (no directory) then the database will be created in the same directory +# as the GoToSocial binary. +# If address is set to :memory: then an in-memory database will be used (no file). +# WARNING: :memory: should NOT BE USED except for testing purposes. +# +# Examples: ["localhost","my.db.host","127.0.0.1","192.111.39.110",":memory:", "sqlite.db"] +# Default: "" +db-address: "/alloc/gotosocial.db" + + +###################### +##### WEB CONFIG ##### +###################### + +# Config pertaining to templating and serving of web pages/email notifications and the like + +# String. Directory from which gotosocial will attempt to load html templates (.tmpl files). +# Examples: ["/some/absolute/path/", "./relative/path/", "../../some/weird/path/"] +# Default: "./web/template/" +web-template-base-dir: "/gotosocial/web/template/" + +# String. Directory from which gotosocial will attempt to serve static web assets (images, scripts). +# Examples: ["/some/absolute/path/", "./relative/path/", "../../some/weird/path/"] +# Default: "./web/assets/" +web-asset-base-dir: "/gotosocial/web/assets/" + +########################### +##### INSTANCE CONFIG ##### +########################### + +# Config pertaining to instance federation settings, pages to hide/expose, etc. + +# Bool. Allow unauthenticated users to make queries to /api/v1/instance/peers?filter=open in order +# to see a list of instances that this instance 'peers' with. Even if set to 'false', then authenticated +# users (members of the instance) will still be able to query the endpoint. +# Options: [true, false] +# Default: false +instance-expose-peers: false + +# Bool. Allow unauthenticated users to make queries to /api/v1/instance/peers?filter=suspended in order +# to see a list of instances that this instance blocks/suspends. This will also allow unauthenticated +# users to see the list through the web UI. Even if set to 'false', then authenticated users (members +# of the instance) will still be able to query the endpoint. +# Options: [true, false] +# Default: false +instance-expose-suspended: false + +# Bool. This flag tweaks whether GoToSocial will deliver ActivityPub messages +# to the shared inbox of a recipient, if one is available, instead of delivering +# each message to each actor who should receive a message individually. +# +# Shared inbox delivery can significantly reduce network load when delivering +# to multiple recipients share an inbox (eg., on large Mastodon instances). +# +# See: https://www.w3.org/TR/activitypub/#shared-inbox-delivery +# +# Options: [true, false] +# Default: true +instance-deliver-to-shared-inboxes: true + +########################### +##### ACCOUNTS CONFIG ##### +########################### + +# Config pertaining to creation and maintenance of accounts on the server, as well as defaults for new accounts. + +# Bool. Do we want people to be able to just submit sign up requests, or do we want invite only? +# Options: [true, false] +# Default: true +accounts-registration-open: true + +# Bool. Do sign up requests require approval from an admin/moderator before an account can sign in/use the server? +# Options: [true, false] +# Default: true +accounts-approval-required: true + +# Bool. Are sign up requests required to submit a reason for the request (eg., an explanation of why they want to join the instance)? +# Options: [true, false] +# Default: true +accounts-reason-required: true + +# Bool. Allow accounts on this instance to set custom CSS for their profile pages and statuses. +# Enabling this setting will allow accounts to upload custom CSS via the /user settings page, +# which will then be rendered on the web view of the account's profile and statuses. +# +# For instances with public sign ups, it is **HIGHLY RECOMMENDED** to leave this setting on 'false', +# since setting it to true allows malicious accounts to make their profile pages misleading, unusable +# or even dangerous to visitors. In other words, you should only enable this setting if you trust +# the users on your instance not to produce harmful CSS. +# +# Regardless of what this value is set to, any uploaded CSS will not be federated to other instances, +# it will only be shown on profiles and statuses on *this* instance. +# +# Options: [true, false] +# Default: false +accounts-allow-custom-css: false + +######################## +##### MEDIA CONFIG ##### +######################## + +# Config pertaining to media uploads (videos, image, image descriptions, emoji). + +# Int. Maximum allowed image upload size in bytes. +# Examples: [2097152, 10485760] +# Default: 10485760 -- aka 10MB +media-image-max-size: 10485760 + +# Int. Maximum allowed video upload size in bytes. +# Examples: [2097152, 10485760] +# Default: 41943040 -- aka 40MB +media-video-max-size: 41943040 + +# Int. Minimum amount of characters required as an image or video description. +# Examples: [500, 1000, 1500] +# Default: 0 (not required) +media-description-min-chars: 0 + +# Int. Maximum amount of characters permitted in an image or video description. +# Examples: [500, 1000, 1500] +# Default: 500 +media-description-max-chars: 500 + +# Int. Number of days to cache media from remote instances before they are removed from the cache. +# A job will run every day at midnight to clean up any remote media older than the given amount of days. +# +# When remote media is removed from the cache, it is deleted from storage but the database entries for the media +# are kept so that it can be fetched again if requested by a user. +# +# If this is set to 0, then media from remote instances will be cached indefinitely. +# Examples: [30, 60, 7, 0] +# Default: 30 +media-remote-cache-days: 30 + +# Int. Max size in bytes of emojis uploaded to this instance via the admin API. +# The default is the same as the Mastodon size limit for emojis (50kb), which allows +# for good interoperability. Raising this limit may cause issues with federation +# of your emojis to other instances, so beware. +# Examples: [51200, 102400] +# Default: 51200 +media-emoji-local-max-size: 51200 + +# Int. Max size in bytes of emojis to download from other instances. +# By default this is 100kb, or twice the size of the default for media-emoji-local-max-size. +# This strikes a good balance between decent interoperability with instances that have +# higher emoji size limits, and not taking up too much space in storage. +# Examples: [51200, 102400] +# Default: 51200 +media-emoji-remote-max-size: 102400 + +########################## +##### STORAGE CONFIG ##### +########################## + +# Config pertaining to storage of user-created uploads (videos, images, etc). + +# String. Type of storage backend to use. +# Examples: ["local", "s3"] +# Default: "local" (storage on local disk) +storage-backend: "s3" +{{ with secret "cfg/svc/tree/pati.to:club" }} +storage-s3-endpoint: "{{ .Data.cdn.endpoint }}" +storage-s3-access-key: "{{ .Data.cdn.key }}" +storage-s3-secret-key: "{{ .Data.cdn.secret }}" +storage-s3-bucket: "{{ .Data.cdn.bucket }}" +{{- end }} +########################### +##### STATUSES CONFIG ##### +########################### + +# Config pertaining to the creation of statuses/posts, and permitted limits. + +# Int. Maximum amount of characters permitted for a new status. +# Note that going way higher than the default might break federation. +# Examples: [140, 500, 5000] +# Default: 5000 +statuses-max-chars: 1024 + +# Int. Maximum amount of characters allowed in the CW/subject header of a status. +# Note that going way higher than the default might break federation. +# Examples: [100, 200] +# Default: 100 +statuses-cw-max-chars: 100 + +# Int. Maximum amount of options to permit when creating a new poll. +# Note that going way higher than the default might break federation. +# Examples: [4, 6, 10] +# Default: 6 +statuses-poll-max-options: 6 + +# Int. Maximum amount of characters to permit per poll option when creating a new poll. +# Note that going way higher than the default might break federation. +# Examples: [50, 100, 150] +# Default: 50 +statuses-poll-option-max-chars: 50 + +# Int. Maximum amount of media files that can be attached to a new status. +# Note that going way higher than the default might break federation. +# Examples: [4, 6, 10] +# Default: 6 +statuses-media-max-files: 6 + + +####################### +##### SMTP CONFIG ##### +####################### +{{ with secret "cfg/svc/tree/pati.to:club" -}} +smtp-host: "{{ .Data.smtp.host }}" +smtp-port: {{ .Data.smtp.port }} +smtp-username: "{{ .Data.smtp.username }}" +smtp-password: "{{ .Data.smtp.password }}" +smtp-from: "no-reply@mail.{{ $domain }}" +{{- end }} + +############################# +##### ADVANCED SETTINGS ##### +############################# + +# Advanced settings pertaining to http timeouts, security, cookies, and more. +# +# ONLY ADJUST THESE SETTINGS IF YOU KNOW WHAT YOU ARE DOING! +# +# Most users will not need to (and should not) touch these settings, since +# they are set to sensible defaults, and may break if they are changed. +# +# Nevertheless, they are provided for the sake of allowing server admins to +# tweak their instance for performance or security reasons. + +# String. Value of the SameSite attribute of cookies set by GoToSocial. +# Defaults to 'lax' to ensure that the OIDC flow does not break, which is +# fine in most cases. If you want to harden your instance against CSRF attacks +# and don't mind if some login-related things might break, you can set this +# to 'strict' instead. +# +# For an overview of what this does, see: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite +# +# Options: ["lax", "strict"] +# Default: "lax" +advanced-cookies-samesite: "lax" + +# Int. Amount of requests to permit from a single IP address within a span of 5 minutes. +# If this amount is exceeded, a 429 HTTP error code will be returned. +# See https://docs.gotosocial.org/en/latest/api/swagger/#rate-limit. +# +# If you find yourself adjusting this limit because it's regularly being exceeded, +# you should first verify that your settings for `trusted-proxies` (above) are correct. +# In many cases, when the rate limit is exceeded it is because your instance sees all +# incoming requests as coming from the *same IP address* (you can verify this by looking +# at the client IPs in your instance logs). If this is the case, try adding that IP +# address to your `trusted-proxies` *BEFORE* you go adjusting this rate limit setting! +# +# If you set this to 0 or less, rate limiting will be disabled entirely. +# +# Examples: [1000, 500, 0] +# Default: 1000 +advanced-rate-limit-requests: 1000 diff --git a/gts.nomad b/gts.nomad new file mode 100644 index 0000000..f4e6c42 --- /dev/null +++ b/gts.nomad @@ -0,0 +1,143 @@ +job "club-patito" { + datacenters = ["nyc1"] + region = "nyc1" + + vault { + policies = ["club-patito"] + + change_mode = "signal" + change_signal = "SIGHUP" + } + + group "club-patito" { + reschedule { + delay = "5s" + delay_function = "fibonacci" + max_delay = "5m" + unlimited = true + } + + restart { + attempts = 60 + interval = "10m" + delay = "10s" + mode = "delay" + } + + network { + mode = "host" + port "gotosocial" { + host_network = "private" + } + } + + task "db-restore" { + lifecycle { + hook = "prestart" + } + + driver = "docker" + user = "nobody" + + resources { + cpu = 128 + memory = 64 + memory_max = 512 + } + + config { + image = "litestream/litestream:0.3.9" + args = ["restore", "/alloc/gotosocial.db"] + volumes = ["secrets/litestream.yaml:/etc/litestream.yml"] + } + + template { + data = file("litestream.yaml") + destination = "secrets/litestream.yaml" + } + } + + task "db-replicate" { + lifecycle { + hook = "prestart" + sidecar = true + } + + driver = "docker" + user = "nobody" + + resources { + cpu = 256 + memory = 128 + memory_max = 512 + } + + config { + image = "litestream/litestream:0.3.9" + args = ["replicate"] + volumes = ["secrets/litestream.yaml:/etc/litestream.yml"] + } + + template { + data = file("litestream.yaml") + destination = "secrets/litestream.yaml" + } + } + + task "gotosocial" { + driver = "docker" + user = "nobody" + + config { + image = "superseriousbusiness/gotosocial:0.6.0" + ports = ["gotosocial"] + args = [ + "--config-path", + "/secrets/gotosocial.yaml", + "server", + "start" + ] + } + + template { + data = file("gotosocial.yaml") + destination = "secrets/gotosocial.yaml" + } + + resources { + cpu = 256 + memory = 512 + memory_max = 1024 + } + + service { + name = "gotosocial" + port = "gotosocial" + + tags = [ + "nidito.service", + "nidito.dns.enabled", + "nidito.http.enabled", + "nidito.http.public", + "nidito.ingress.enabled", + ] + + meta { + nidito-acl = "allow external" + nidito-http-buffering = "off" + nidito-http-tls = "pati.to" + nidito-http-domain = "club.pati.to" + nidito-http-wss = "on" + nidito-http-max-body-size = "40m" + } + + check { + name = "gotosocial" + type = "tcp" + interval = "10s" + timeout = "2s" + } + } + } + } +} diff --git a/litestream.yaml b/litestream.yaml new file mode 100644 index 0000000..eb251d1 --- /dev/null +++ b/litestream.yaml @@ -0,0 +1,9 @@ +{{- with secret "cfg/infra/tree/provider:cdn" }} +access-key-id: {{ .Data.key }} +secret-access-key: {{ .Data.secret }} + +dbs: + - path: /alloc/gotosocial.db + replicas: + - url: s3://{{ .Data.bucket }}.{{ .Data.endpoint }}/club-patito/gotosocial.db +{{- end }} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..36b9615 --- /dev/null +++ b/main.tf @@ -0,0 +1,145 @@ +terraform { + backend "consul" { + path = "nidito/state/service/club-patito" + } + + required_providers { + acme = { + source = "vancluever/acme" + version = "~> 2.5.3" + } + + digitalocean = { + source = "digitalocean/digitalocean" + version = "~> 2.16.0" + } + + vault = { + source = "hashicorp/vault" + version = "~> 3.7.0" + } + } + + required_version = ">= 1.0.0" +} + +locals { + policies = { + "sys/capabilities-self" = ["update"] + "auth/token/renew-self" = ["update"] + "config/kv/provider:cdn" = ["read"] + "config/kv/pati.to:club" = ["read"] + "cfg/infra/tree/provider:cdn" = ["read"] + "cfg/svc/tree/pati.to:club" = ["read"] + } + + mx_servers = { + "mxa.mailgun.org." = 10, + "mxb.mailgun.org." = 10, + } +} + +provider "acme" { + server_url = "https://acme-v02.api.letsencrypt.org/directory" +} + +// DO tokens for compute resources +data "vault_generic_secret" "DO" { + path = "cfg/infra/tree/provider:digitalocean" +} + +provider "digitalocean" { + token = data.vault_generic_secret.DO.data.patito +} + +provider "digitalocean" { + alias = "compute" + token = data.vault_generic_secret.DO.data.token +} + +data "digitalocean_droplet" "bedstuy" { + provider = digitalocean.compute + name = "bedstuy" +} + +resource "vault_policy" "service" { + name = "club-patito" + policy = <<-HCL + %{ for path in sort(keys(local.policies)) }path "${path}" { + capabilities = ${jsonencode(local.policies[path])} + } + + %{ endfor } + HCL +} + +resource "digitalocean_record" "to_pati_club" { + domain = "pati.to" + type = "A" + ttl = 3600 + name = "club" + value = data.digitalocean_droplet.bedstuy.ipv4_address +} + +resource "digitalocean_record" "txt_smtp_domainkey" { + domain = "pati.to" + type = "TXT" + name = "mailo._domainkey.mail.club" + value = "k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDG2sUJLqDOqMnjMy3+fNI0rdrGb8VrI84yloaXE9R8aLEXkm0jlANiFEmVF7s7ZvoDoKm0v5Lm/JsvM/Rl8n9TS2QYMqhmVsEmuBHwOVhekUDW/LKvfDxfNhmbquvU4z+/fn2WBx19dLG3Ctao7pbL7B9TKR+sPJUNoFWRseucMQIDAQAB" +} + +resource "digitalocean_record" "txt_spf" { + domain = "pati.to" + type = "TXT" + name = "mail.club" + value = "v=spf1 include:mailgun.org ~all;" +} + +resource "digitalocean_record" "mx" { + for_each = local.mx_servers + domain = "pati.to" + type = "MX" + name = "mail.club" + value = each.key + priority = each.value +} + + +data "terraform_remote_state" "registration" { + backend = "consul" + workspace = "default" + config = { + address = "consul.service.casa.consul:5554" + scheme = "https" + path = "nidito/state/letsencrypt/registration" + } +} + +resource acme_certificate cert { + account_key_pem = data.terraform_remote_state.registration.outputs.account_key + common_name = "pati.to" + subject_alternative_names = ["*.pati.to"] + recursive_nameservers = ["1.1.1.1:53", "8.8.8.8:53"] + + dns_challenge { + provider = "digitalocean" + config = { + DO_AUTH_TOKEN = data.vault_generic_secret.DO.data.patito + DO_PROPAGATION_TIMEOUT = 60 + DO_TTL = 30 + } + } +} + +resource vault_generic_secret cert { + path = "nidito/tls/${acme_certificate.cert.common_name}" + data_json = jsonencode({ + private_key = acme_certificate.cert.private_key_pem, + cert = join("", [ + acme_certificate.cert.certificate_pem, + acme_certificate.cert.issuer_pem, + ]) + issuer = acme_certificate.cert.issuer_pem, + bare_cert = acme_certificate.cert.certificate_pem, + }) +}