aboutsummaryrefslogtreecommitdiff
path: root/content/blog/nix/first-webapp-gotosocial.md
blob: 5711c175fb296722ff3b691f6ef68a6f0b167069 (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
---
title: Deploying a web application to nixos
description: A full example with my gotosocial instance
date: 2023-10-06
tags:
- nix
---

## Introduction

Gotosocial is just a service that was running on one of my FreeBSD servers. Being a simple web application it is a good candidate to showcase what I like most about nixos and its declarative configurations!

## A bit about the nix language

I recommend you read [the official documentation](https://nixos.wiki/wiki/Overview_of_the_Nix_Language), but here is the minimal to get you started:
- every statement ends with a semicolon.
- The basic block structures are in fact Sets, meaning lists of key-value pairs where the keys are unique.
- The `{...}: { }` that structure the whole file is a module definition. In the first curly braces are arguments.
- The `let ...; in { }` construct is a way to define local variables for usage in the block following the `in`.
- You can write strings with double quotes or double single quotes. This makes it so that you almost never need to escape characters! The double single quote also allows to write multi line strings that will smartly strip the starting white spaces.
- file system paths are not strings!
- list elements are separated by white spaces.
- You can merge the keys in two sets with `//`, often used in conjunction with `let` local variables.
- imports work by merging sets and appending lists.

Statements can be grouped but nothing is mandatory. For example the following are completely equivalent:
```nix
environment = {
	etc."gotosocial.yaml" = {
		mode = "0444";
		source = ./gotosocial.yaml;
	};
	systemPackages = [ pkgs.sqlite ];
};
```

```nix
environment.etc."gotosocial.yaml" = {
	mode = "0444";
	source = ./gotosocial.yaml;
};
environment.systemPackages = [ pkgs.sqlite ];
```

```nix
environment.etc."gotosocial.yaml".mode = "0444";
environment.etc."gotosocial.yaml".source = ./gotosocial.yaml;
environment.systemPackages = [ pkgs.sqlite ];
```

## Configuration

The following configuration defines in order:
- gotosocial's YAML configuration file
- Borg backup jobs for the SQLite database and the local storage
- Nginx virtual host
- gotosocial container

```nix
{ config, pkgs, ... }:
{
	imports = [
	  ../lib/nginx.nix
	];
	environment = {
		etc."gotosocial.yaml" = {
			mode = "0444";
			source = ./gotosocial.yaml;
		};
		systemPackages = [ pkgs.sqlite ];
	};
	services = {
		borgbackup.jobs = let defaults = {
			compression = "auto,zstd";
			encryption.mode = "none";
			environment.BORG_RSH = "ssh -i /etc/borg.key";
			prune.keep = {
				daily = 14;
				weekly = 4;
				monthly = 3;
			};
			repo = "ssh://borg@kaladin.adyxax.org/srv/borg/dalinar.adyxax.org";
			startAt = "daily";
		}; in {
			"gotosocial-db" = defaults // {
				paths = "/tmp/gotosocial-sqlite.db";
				postHook = "rm -f /tmp/gotosocial-sqlite.db";
				preHook = ''
					rm -f /tmp/gotosocial-sqlite.db
					echo 'VACUUM INTO "/tmp/gotosocial-sqlite.db"' | \
					/run/current-system/sw/bin/sqlite3 /srv/gotosocial/sqlite.db
				'';
			};
			"gotosocial-storage" = defaults // { paths = "/srv/gotosocial/storage"; };
		};
		nginx.virtualHosts."fedi.adyxax.org" = {
			forceSSL = true;
			locations = {
				"/" = {
					proxyPass = "http://127.0.0.1:8082";
					proxyWebsockets = true;
				};
			};
			sslCertificate = "/etc/nginx/adyxax.org.crt";
			sslCertificateKey = "/etc/nginx/adyxax.org.key";
		};
	};
	virtualisation.oci-containers.containers.gotosocial = {
		cmd = [ "--config-path" "/gotosocial.yaml" ];
		image = "superseriousbusiness/gotosocial:0.11.1";
		ports = ["127.0.0.1:8082:8080"];
		volumes = [
			"/etc/gotosocial.yaml:/gotosocial.yaml:ro"
			"/srv/gotosocial/:/gotosocial/storage/"
		];
	};
}
```

## Nginx

I will go into details in a next article about imports and how I organize my configurations, just know that in this case imports work intuitively. Here is the `lib/nginx.nix` file defining common configuration for Nginx:
```nix
{ config, pkgs, ... }:
{
	environment.etc = let permissions = { mode = "0400"; uid= config.ids.uids.nginx; }; in {
		"nginx/adyxax.org.crt" = permissions // { source = ../../01-legacy/adyxax.org.crt; };
		"nginx/adyxax.org.key" = permissions // { source = ../../01-legacy/adyxax.org.key; };
	};
	networking.firewall.allowedTCPPorts = [ 80 443 ];
	services.nginx = {
		clientMaxBodySize = "40M";
		enable = true;
		enableReload = true;
		recommendedGzipSettings = true;
		recommendedOptimisation = true;
		recommendedProxySettings = true;
	};
}
```

## Deploying

Being an existing service for me, I transferred gotosocial's storage data and database using rsync. With that done, bringing the service back up was only a matter of migrating the DNS and running the now familiar:
```sh
nixos-rebuild  switch
```

## Conclusion

I hope you find this way of declaratively configuring a whole operating system as elegant as I do. The nix configuration language is a bit rough, but I find it is not hard to wrap your head around the basics.