summaryrefslogtreecommitdiff
path: root/hosts/ahmed/linus.onl/default.nix
blob: b363bb1aba0e176954e1822799c27290906914fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
{
  pkgs,
  lib,
  config,
  ...
}: let
  # The domain to serve. Also kinda embedded in the name of the module??
  domain = "linus.onl";

  # Enable HTTPS stuff.
  useACME = true;

  # When run, this command causes a rebuild of the website source. See the service defintion for how the site is rebuilt.
  startServiceCommand = "/run/current-system/sw/bin/systemctl start ${domain}-source.service";

  # Name of the "production" branch where the live content goes.
  mainBranch = "main";
in {
  config = {
    # Create a user to run the build script under.
    users.users."${domain}-builder" = {
      description = "builds ${domain}";
      group = "${domain}-builder";
      isSystemUser = true;
    };
    users.groups."${domain}-builder" = {};

    # Create the output directory.
    system.activationScripts."${domain}-create-www" = lib.stringAfter ["var"] ''
      mkdir -p /var/www/${domain}
      chown ${domain}-builder /var/www/${domain}
      chgrp ${domain}-builder /var/www/${domain}
      chmod 0755 /var/www/${domain}
    '';

    # Create a systemd service which rebuild the site.
    #
    # This can't be done using Nix because the site relies on the git build and
    # there are some inherent difficulties with including .git/ in the
    # inputSource for derivations.
    #
    # See: https://github.com/NixOS/nix/issues/6900
    # See: https://github.com/NixOS/nixpkgs/issues/8567
    systemd.services."${domain}-source" = {
      description = "generate https://${domain} source";

      serviceConfig = {
        Type = "oneshot";
        User = "${domain}-builder";
        Group = "${domain}-builder";
      };

      path = with pkgs; [
        git
        rsync
        coreutils-full
        tcl-8_5
        gnumake
      ];
      environment.TCLLIBPATH = "$TCLLIBPATH ${pkgs.tcl-cmark}/lib/tclcmark1.0";
      script = ''
        set -ex
        tmpdir="$(mktemp -d -t linus.onl-source.XXXXXXXXXXXX)"
        cd "$tmpdir"
        trap 'rm -rf $tmpdir' EXIT
        git clone --branch=${mainBranch} --filter=blob:none https://github.com/linnnus/${domain} .
        make _build
        rsync --archive --delete _build/ /var/www/${domain}
      '';

      # TODO: Harden service

      # Network must be online for us to check.
      after = ["network-online.target"];
      requires = ["network-online.target"];

      # We must generate some files for NGINX to serve, so this should be run
      # before NGINX.
      before = ["nginx.service"];
      wantedBy = ["nginx.service"];
    };

    # This service will listen for webhook events from GitHub's API. Whenever
    # it receives a "push" event, it will start the rebuild service.
    services.webhook-listener = {
      enable = true;

      commands = [
        {
          event = "push";
          command = toString (pkgs.writeShellScript "handle-push-event.sh" ''
            ${pkgs.jq}/bin/jq --exit-status '.ref == "refs/heads/${mainBranch}"' >/dev/null
            case $? in
              0)
                ${config.security.wrapperDir}/sudo ${startServiceCommand}
                ;;
              1)
                echo "Not the target ref. Exciting"
                exit 0
                ;;
              *)
                echo "Got jq error. Exiting." >&2
                exit 1
                ;;
            esac
          '');
        }
      ];

      max-idle-time = "10min";

      secret-path = config.age.secrets."linus.onl-github-secret".path;
    };

    # We have shared a secret with GitHub, which we use to verify requests. Here we decrypt that secret.
    age.secrets."linus.onl-github-secret" = {
      file = ../../../secrets/linus.onl-github-secret.txt.age;

      owner = config.services.webhook-listener.user;
      group = config.services.webhook-listener.group;
    };

    # Commands run by `webhook-listener` are run as an inpriviledged user for
    # security reasons. We have to specifically give that user permission to start this one service.
    security.sudo.extraRules = [
      {
        users = [config.services.webhook-listener.user];
        commands = [
          {
            command = startServiceCommand;
            options = ["NOPASSWD"];
          }
        ];
      }
    ];

    # Register domain name with ddns.
    services.cloudflare-dyndns.domains = [domain];

    # Register virtual host.
    services.nginx = {
      virtualHosts."${domain}" = {
        # NOTE: 'forceSSL' will cause an infite loop, if the cloudflare proxy does NOT connect over HTTPS.
        enableACME = useACME;
        forceSSL = useACME;
        root = "/var/www/${domain}";

        # I have pointed the GitHub webhook requests at <https://linus.onl/webhook>.
        # These should be forwarded to `/` on the listening socket server.
        locations."= /webhook" = {
          recommendedProxySettings = true;
          proxyPass = "http://unix:${config.services.webhook-listener.socket-path}:/";
        };
      };
    };
  };
}