diff options
| -rw-r--r-- | .env.template | 3 | ||||
| -rw-r--r-- | .envrc | 1 | ||||
| -rw-r--r-- | .github/dependabot.yml | 6 | ||||
| -rw-r--r-- | .github/workflows/deploy.yaml | 39 | ||||
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | .terraform.lock.hcl | 47 | ||||
| -rw-r--r-- | .terraformignore | 11 | ||||
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | dev.nix | 1 | ||||
| -rw-r--r-- | flake.lock | 30 | ||||
| -rw-r--r-- | flake.nix | 12 | ||||
| -rw-r--r-- | tofu/cloud.nix | 7 | ||||
| -rw-r--r-- | tofu/cloudflare/default.nix | 26 | ||||
| -rw-r--r-- | tofu/cloudflare/dns.nix | 64 | ||||
| -rw-r--r-- | tofu/cloudflare/ruleset.nix | 64 | ||||
| -rw-r--r-- | tofu/cloudflare/tunnels.nix | 11 | ||||
| -rw-r--r-- | tofu/default.nix | 54 | ||||
| -rw-r--r-- | tofu/deploy.nix | 15 | ||||
| -rw-r--r-- | tofu/tailscale/acl.nix | 27 | ||||
| -rw-r--r-- | tofu/tailscale/default.nix | 12 | ||||
| -rw-r--r-- | tofu/tailscale/devices.nix | 17 | ||||
| -rw-r--r-- | tofu/tailscale/dns.nix | 5 | ||||
| -rw-r--r-- | tofu/tailscale/tags.nix | 15 | ||||
| -rw-r--r-- | tofu/vars.nix | 11 | ||||
| -rw-r--r-- | tofu/versions.nix | 13 |
25 files changed, 493 insertions, 3 deletions
diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..4991713 --- /dev/null +++ b/.env.template @@ -0,0 +1,3 @@ +CLOUDFLARE_API_KEY=foo +CLOUDFLARE_EMAIL=bar +TAILSCALE_API_KEY=baz @@ -1,2 +1,3 @@ use flake watch_file dev.nix +dotenv_if_exists diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1d662ce..2f4695a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,9 @@ updates: interval: "weekly" commit-message: prefix: "actions" + - package-ecosystem: "terraform" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "tofu" diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 34f4f26..0f3f1ed 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,4 +1,4 @@ -name: Deploy systems +name: Deploy infrastructure on: check_suite: @@ -6,7 +6,9 @@ on: workflow_dispatch: jobs: - deploy: + nixos: + name: Deploy NixOS systems + runs-on: ubuntu-latest concurrency: @@ -46,3 +48,36 @@ jobs: run: | nix develop --accept-flake-config \ --command just deploy-all + + opentofu: + name: Apply OpenTofu plan + needs: nixos + + runs-on: ubuntu-latest + + concurrency: + group: tofu + cancel-in-progress: true + + steps: + - uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v9 + + - name: Setup local Nix cache + uses: DeterminateSystems/magic-nix-cache-action@v2 + + - name: Setup OpenTofu + uses: opentofu/setup-opentofu@v1 + with: + cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} + + - name: Setup OpenTofu cache + uses: terraform-cache/terraform-cache@v1 + + - name: Run plan + run: nix run .#plan + + - name: Apply + run: tofu apply @@ -8,3 +8,7 @@ repl-result-out* .env* !.envrc !.env.template + +# opentofu +.terraform/ +config.tf.json diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..4699bbf --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,47 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/cloudflare/cloudflare" { + version = "4.20.0" + constraints = "~> 4.0" + hashes = [ + "h1:KH92fiFCIurqU/qxsafm3mdnZSiXpr3fq9eoiLKiogo=", + "zh:22b06f598d4dac4131f69ca1c1e1ea5fd02d25019ccc99566d4ae8bf78e3996a", + "zh:29a85cf96a04f217a548a5e91c4e8eddd52563ce48872c44a449b2ade3a21260", + "zh:2ce0e98181c5a6b65a8ac930b816b94124fd7aee0ec4c5109a0a9acd28c3cf7b", + "zh:564f6396cf85b37a6a101d202bcc9e54590dbef27217c089c9f32a144f0a2b03", + "zh:618e2c40bc87bef36f12de8ec039faf973861d55c47bd125890737fbcb91fbee", + "zh:6e624f21eea8eeb25a13d96516a62f8879fd21ea21f17c0e933bccbc96da438e", + "zh:81ab073984a20c0a9480d98bf306d7f70bd781217bbaa68abd4ca1caab75db7d", + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", + "zh:9506a65591cc8cb0869f4023beb07ba6d02ddda073e17560867253d064cea308", + "zh:a4f9c859065ed8d626a479c68542153cd262d70551ae54bfe418092dc7e8d675", + "zh:a577841b5f33d556e2f1b2c453c78e7fa0c468edcad36b31d334f5671ce074cf", + "zh:ad17294bdfa79d117bda06cb21eb0a48f3785e45f2d4182a64f193466a34247a", + "zh:bb448bce29cb890b11fb48803d60367a34462bbe8954622e5424bedbcdf1477d", + "zh:e0749d864455a7b66953364371d715c94e44734cf8978a8c03ca2c73e212e88a", + "zh:e302c5222be4d5a1fcc59bff9e69c8f6dd7dff086b305af9b4fc9cbd2fd2c01c", + ] +} + +provider "registry.opentofu.org/tailscale/tailscale" { + version = "0.13.13" + constraints = "0.13.13" + hashes = [ + "h1:Fh799APNn48Jj2D29gcHh+HwLcA7wfAVIfMdkWyMoWw=", + "zh:07ee590ab8b568d65d52b401d15639ab0c23bda05e7b90f445a4159d7f9cecf8", + "zh:1bf72d550904475fbfc211295277d6afe0f3d0c98b89db7f718e2182febb0cd0", + "zh:26ef6e6f3a42cf5783d7aa5e1774b2fb86e0b01742349d4a5dee1164015163d9", + "zh:29c28fb821f6910cec4df54215b7338e180e44c0218ad16c63a0a8ecbb6307ab", + "zh:337d7548b8aeeeb7d6cd874601b237bb1db149c642fec416f2cb93513ac37070", + "zh:529f4fb1f54b3091ba32319ea766bfb7d49b7fb113d71bc89703155d8a1d5bdc", + "zh:541fafbe0124ceda9cf619d8248f6c1e7d5a45210604356f7896d447666f06ab", + "zh:5e1a66df1b891780a8aef54522ef1017952ca4f25103633d51b81bbe4b56b56a", + "zh:acdd72771d4cc7bb5465ea5d3eed56d86ee2b0b83b74549e8cd6dc4153222ef7", + "zh:beedd644c2db69829ec3850cd1aa3953c8c822820df16d97cf0c5b4891c03a2d", + "zh:c17fe2e6fe06f104d5150278500419f471d5d3b061dcd5673a6f6c915cc1cec0", + "zh:cc5805ae3f7f2495f7cf81655227fb68e18fc02d7fcc16896b57758a0f8611ae", + "zh:f18db5c7bf6707a5d358243a7dddfc69adf9b39ba0630206af5da6d89813b205", + "zh:f88f5b1e4c015b20a1bdf696df94f57bdfa69171ac0de149a586f89b17166010", + ] +} diff --git a/.terraformignore b/.terraformignore new file mode 100644 index 0000000..c70390f --- /dev/null +++ b/.terraformignore @@ -0,0 +1,11 @@ +result* +repl-result-out* + +.pre-commit-config.yaml +.direnv/ +.env* +!.envrc +!.env.template + +.terraform/ +.git/ @@ -39,6 +39,7 @@ there are some amazing tools i use to make/manage this flake that i would highly - [agenix](https://github.com/ryantm/agenix) - [flake-parts](https://github.com/hercules-ci/flake-parts) - [nixinate](https://github.com/MatthewCroughan/nixinate) +- [terranix](https://github.com/terranix/terranix) - [lanzaboote](https://github.com/nix-community/lanzaboote) - [nixos-wsl](https://github.com/nix-community/nixos-wsl) - [nix-openwrt-imagebuilder](https://github.com/astro/nix-openwrt-imagebuilder) @@ -33,6 +33,7 @@ fzf just jq + opentofu ] ++ lib.optional stdenv.isLinux inputs'.agenix.packages.agenix; }; @@ -555,7 +555,8 @@ "openwrt-imagebuilder": "openwrt-imagebuilder", "parts": "parts", "pre-commit": "pre-commit", - "teawiebot": "teawiebot" + "teawiebot": "teawiebot", + "terranix": "terranix" } }, "rust-analyzer-src": { @@ -644,6 +645,33 @@ "repo": "teawiebot", "type": "github" } + }, + "terranix": { + "inputs": { + "bats-assert": [], + "bats-support": [], + "flake-utils": [ + "pre-commit", + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "terranix-examples": [] + }, + "locked": { + "lastModified": 1695406838, + "narHash": "sha256-xiUfVD6rtsVWFotVtUW3Q1nQh4obKzgvpN1wqZuGXvM=", + "owner": "terranix", + "repo": "terranix", + "rev": "fc9077ca02ab5681935dbf0ecd725c4d889b9275", + "type": "github" + }, + "original": { + "owner": "terranix", + "repo": "terranix", + "type": "github" + } } }, "root": "root", @@ -121,6 +121,17 @@ pre-commit.follows = "pre-commit"; }; }; + + terranix = { + url = "github:terranix/terranix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "pre-commit/flake-utils"; + terranix-examples.follows = ""; + bats-support.follows = ""; + bats-assert.follows = ""; + }; + }; }; outputs = {parts, ...} @ inputs: @@ -131,6 +142,7 @@ ./modules ./overlay ./systems + ./tofu ./users ./ci.nix ./dev.nix diff --git a/tofu/cloud.nix b/tofu/cloud.nix new file mode 100644 index 0000000..5ee0113 --- /dev/null +++ b/tofu/cloud.nix @@ -0,0 +1,7 @@ +{ + terraform.cloud = { + hostname = "app.terraform.io"; + organization = "getchoo"; + workspaces.name = "flake"; + }; +} diff --git a/tofu/cloudflare/default.nix b/tofu/cloudflare/default.nix new file mode 100644 index 0000000..c145cb0 --- /dev/null +++ b/tofu/cloudflare/default.nix @@ -0,0 +1,26 @@ +{lib, ...}: { + imports = [ + ./dns.nix + ./ruleset.nix + ./tunnels.nix + ]; + + terraform.required_providers.cloudflare = { + source = "cloudflare/cloudflare"; + version = "~> 4"; + }; + + resource = { + cloudflare_url_normalization_settings.incoming = { + scope = "incoming"; + type = "cloudflare"; + zone_id = lib.tfRef "var.zone_id"; + }; + + cloudflare_bot_management.bots = { + enable_js = false; + fight_mode = false; + zone_id = lib.tfRef "var.zone_id"; + }; + }; +} diff --git a/tofu/cloudflare/dns.nix b/tofu/cloudflare/dns.nix new file mode 100644 index 0000000..3371566 --- /dev/null +++ b/tofu/cloudflare/dns.nix @@ -0,0 +1,64 @@ +{lib, ...}: let + mkRecord = name: { + value, + type, + ... + } @ args: + { + name = args.name or name; + zone_id = "\${var.zone_id}"; + inherit value type; + proxied = true; + } + // lib.optionalAttrs (type != "TXT") {proxied = true;}; + + atlas_tunnel = lib.tfRef "data.cloudflare_tunnel.atlas-nginx.id" + ".cfargotunnel.com"; +in { + resource.cloudflare_record = builtins.mapAttrs mkRecord { + website = { + name = "@"; + value = "website-86j.pages.dev"; + type = "CNAME"; + }; + + www = { + value = "mydadleft.me"; + type = "CNAME"; + }; + + api = { + value = atlas_tunnel; + type = "CNAME"; + }; + + miniflux = { + value = atlas_tunnel; + type = "CNAME"; + }; + + msix = { + value = atlas_tunnel; + type = "CNAME"; + }; + + # prevent email spoofing + + dmarc = { + name = "_dmarc"; + value = "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s;"; + type = "TXT"; + }; + + domainkey = { + name = "*._domainkey"; + value = "v=DKIM1; p="; + type = "TXT"; + }; + + email = { + name = "mydadleft.me"; + value = "v=spf1 -all"; + type = "TXT"; + }; + }; +} diff --git a/tofu/cloudflare/ruleset.nix b/tofu/cloudflare/ruleset.nix new file mode 100644 index 0000000..1be98aa --- /dev/null +++ b/tofu/cloudflare/ruleset.nix @@ -0,0 +1,64 @@ +{lib, ...}: { + resource.cloudflare_ruleset = { + default = { + kind = "zone"; + name = "default"; + phase = "http_config_settings"; + zone_id = lib.tfRef "var.zone_id"; + + rules = [ + { + action = "set_config"; + action_parameters = { + automatic_https_rewrites = true; + email_obfuscation = true; + opportunistic_encryption = false; + }; + description = "base redirects"; + enabled = true; + expression = "true"; + } + ]; + }; + + redirect = { + kind = "zone"; + name = "default"; + phase = "http_request_dynamic_redirect"; + zone_id = lib.tfRef "var.zone_id"; + + rules = [ + { + action = "redirect"; + action_parameters = { + from_value = { + preserve_query_string = false; + status_code = 301; + target_url = { + value = "https://www.youtube.com/watch?v=RvVdFXOFcjw"; + }; + }; + }; + description = "funny"; + enabled = true; + expression = "(http.request.uri.path eq \"/hacks\" and http.host eq \"mydadleft.me\")"; + } + { + action = "redirect"; + action_parameters = { + from_value = { + preserve_query_string = false; + status_code = 301; + target_url = { + value = "https://www.youtube.com/watch?v=RvVdFXOFcjw"; + }; + }; + }; + description = "onlyfriends"; + enabled = true; + expression = "(http.request.uri.path eq \"/onlyfriends\" and http.host eq \"mydadleft.me\")"; + } + ]; + }; + }; +} diff --git a/tofu/cloudflare/tunnels.nix b/tofu/cloudflare/tunnels.nix new file mode 100644 index 0000000..bea9811 --- /dev/null +++ b/tofu/cloudflare/tunnels.nix @@ -0,0 +1,11 @@ +{lib, ...}: { + data.cloudflare_tunnel = + lib.genAttrs + [ + "atlas-nginx" + ] + (name: { + inherit name; + account_id = lib.tfRef "var.account_id"; + }); +} diff --git a/tofu/default.nix b/tofu/default.nix new file mode 100644 index 0000000..4e6a425 --- /dev/null +++ b/tofu/default.nix @@ -0,0 +1,54 @@ +{inputs, ...}: { + perSystem = { + lib, + pkgs, + system, + ... + }: let + config = inputs.terranix.lib.terranixConfiguration { + inherit system; + modules = [ + ./cloudflare + ./tailscale + ./cloud.nix + ./vars.nix + ./versions.nix + ]; + }; + in { + apps = + lib.genAttrs ["apply" "destroy" "plan"] (fn: { + type = "app"; + + program = pkgs.writeShellApplication { + name = fn; + + runtimeInputs = [pkgs.opentofu]; + + text = '' + config_file="config.tf.json" + [ -e "$config_file" ] && rm -f "$config_file" + cp ${config} "$config_file" + tofu init && tofu ${fn} + ''; + }; + }) + // { + tofu-config = { + type = "app"; + + program = pkgs.writeShellApplication { + name = "tofu-config"; + + runtimeInputs = [pkgs.opentofu]; + + text = '' + config_file="config.tf.json" + [ -e "$config_file" ] && rm -f "$config_file" + cp ${config} "$config_file" + ''; + }; + }; + }; + }; +} diff --git a/tofu/deploy.nix b/tofu/deploy.nix new file mode 100644 index 0000000..3f15713 --- /dev/null +++ b/tofu/deploy.nix @@ -0,0 +1,15 @@ +{ + module.deploy_nixos = rec { + source = "github.com/nix-community/terraform-nixos//deploy_nixos?ref=646cacb12439ca477c05315a7bfd49e9832bc4e3"; + + build_on_target = "true"; + flake = true; + hermetic = true; + ssh_agent = false; + + nixos_config = "atlas"; + + target_user = "root"; + target_host = nixos_config; + }; +} diff --git a/tofu/tailscale/acl.nix b/tofu/tailscale/acl.nix new file mode 100644 index 0000000..46503d8 --- /dev/null +++ b/tofu/tailscale/acl.nix @@ -0,0 +1,27 @@ +{lib, ...}: { + resource = { + tailscale_acl.main = { + acl = toString (builtins.toJSON { + tagOwners = let + me = ["getchoo@github"]; + tags = map (name: "tag:${name}") ["server" "personal" "gha"]; + in + lib.genAttrs tags (_: me); + + acls = let + mkAcl = action: src: dst: {inherit action src dst;}; + in [ + (mkAcl "accept" ["tag:personal"] ["*:*"]) + (mkAcl "accept" ["tag:server" "tag:gha"] ["tag:server:*"]) + ]; + + ssh = let + mkSshAcl = action: src: dst: users: {inherit action src dst users;}; + in [ + (mkSshAcl "accept" ["tag:personal"] ["tag:server" "tag:personal"] ["autogroup:nonroot" "root"]) + (mkSshAcl "accept" ["tag:gha"] ["tag:server"] ["root"]) + ]; + }); + }; + }; +} diff --git a/tofu/tailscale/default.nix b/tofu/tailscale/default.nix new file mode 100644 index 0000000..2225fd5 --- /dev/null +++ b/tofu/tailscale/default.nix @@ -0,0 +1,12 @@ +{lib, ...}: { + imports = [ + ./acl.nix + ./devices.nix + ./dns.nix + ./tags.nix + ]; + + provider.tailscale = { + tailnet = lib.tfRef "var.tailnet"; + }; +} diff --git a/tofu/tailscale/devices.nix b/tofu/tailscale/devices.nix new file mode 100644 index 0000000..44ee3f1 --- /dev/null +++ b/tofu/tailscale/devices.nix @@ -0,0 +1,17 @@ +{lib, ...}: { + data.tailscale_device = let + toDevices = devices: + lib.genAttrs devices (name: { + name = "${name}.tailc59d6.ts.net"; + wait_for = "60s"; + }); + in + toDevices [ + "atlas" + "caroline" + "glados" + "glados-wsl" + "glados-windows" + "iphone-14" + ]; +} diff --git a/tofu/tailscale/dns.nix b/tofu/tailscale/dns.nix new file mode 100644 index 0000000..320a24b --- /dev/null +++ b/tofu/tailscale/dns.nix @@ -0,0 +1,5 @@ +{ + resource.tailscale_dns_preferences.default = { + magic_dns = true; + }; +} diff --git a/tofu/tailscale/tags.nix b/tofu/tailscale/tags.nix new file mode 100644 index 0000000..c519a25 --- /dev/null +++ b/tofu/tailscale/tags.nix @@ -0,0 +1,15 @@ +{lib, ...}: { + resource.tailscale_device_tags = let + getDeviceID = device: lib.tfRef "data.tailscale_device.${device}.id"; + toTags = n: v: {device_id = getDeviceID n;} // v; + + tags = lib.genAttrs ["server" "personal" "gha"] (n: ["tag:${n}"]); + in + builtins.mapAttrs toTags { + atlas.tags = tags.server; + caroline.tags = tags.personal; + glados.tags = tags.personal; + glados-wsl.tags = tags.personal; + iphone-14.tags = tags.personal; + }; +} diff --git a/tofu/vars.nix b/tofu/vars.nix new file mode 100644 index 0000000..2f640c2 --- /dev/null +++ b/tofu/vars.nix @@ -0,0 +1,11 @@ +{ + variable = { + # cloudflare + zone_id.default = "53286ae07c44ed39e4b1249a2adb6d4d"; + account_id.default = "44c47ae2d55db34c1bf2f378ea8202f1"; + cf_domain.default = "mydadleft.me"; + + # tailscale + tailnet.default = "getchoo.github"; + }; +} diff --git a/tofu/versions.nix b/tofu/versions.nix new file mode 100644 index 0000000..b563ce1 --- /dev/null +++ b/tofu/versions.nix @@ -0,0 +1,13 @@ +{ + terraform.required_providers = { + cloudflare = { + source = "cloudflare/cloudflare"; + version = "~> 4"; + }; + + tailscale = { + source = "tailscale/tailscale"; + version = "0.13.13"; + }; + }; +} |
