ALPHA: wire is a tool to deploy nixos systems
wire.althaea.zone/
1# SPDX-License-Identifier: AGPL-3.0-or-later
2# Copyright 2024-2025 wire Contributors
3
4{
5 pkgs,
6 lib,
7 config,
8 ...
9}:
10{
11 config = {
12 systemd = {
13 paths = lib.mapAttrs' (
14 _name: value:
15 lib.nameValuePair "${value.name}-key" {
16 description = "Monitor changes to ${value.path}. You should Require ${value.service} instead of this.";
17 pathConfig = {
18 PathExists = value.path;
19 PathChanged = value.path;
20 Unit = "${value.name}-key.service";
21 };
22 }
23 ) config.deployment.keys;
24
25 system.activationScripts.setup-wire-rollback.text = ''
26 mkdir -p /var/lib/wire-rollback
27 chmod 700 /var/lib/wire-rollback
28 '';
29
30 services = {
31 wire-rollback = {
32 enable = config.deployment.rollback;
33 description = "Rolls back the NixOS profile if `/var/lib/wire-rollback/heartbeat` is not created in 30
34 seconds after this service starts.";
35 documentation = [
36 "https://wire.althaea.zone/guides/rollback"
37 ];
38 path = [
39 pkgs.coreutils
40 ];
41 wantedBy = [ "multi-user.target" ];
42 script = ''
43 set -euo pipefail
44
45 goal=$(<"/var/lib/wire-rollback/goal")
46
47 case $goal in
48 "check" | "switch" | "boot" | "test" | "dry-activate")
49 echo "<5>using goal $goal"
50 ;;
51 *)
52 echo "<3>'$goal' is not a valid goal."
53 exit 1
54 ;;
55 esac
56
57 sleep 30
58
59 if [ -f "/var/lib/wire-rollback/heartbeat" ]; then
60 exit 0
61 fi
62
63 echo "<1>/var/lib/wire-rollback/heartbeat does not exist, rolling back system"
64
65 # set current system
66 nix-env --rollback --profile /nix/var/nix/profiles/system
67 # get the path to the system we are now rolling back to
68 system=$(readlink -f /nix/var/nix/profiles/system)
69
70 echo "<5>rolling back to $system"
71
72 # switch to the system using goal
73 "$system/bin/switch-to-configuration $goal"
74 '';
75 unitConfig = {
76 ConditionPathExists = [
77 "/var/lib/wire-rollback/goal"
78 "!/var/lib/wire-rollback/heartbeat"
79 ];
80 };
81 serviceConfig = {
82 Type = "oneshot";
83 Restart = "no";
84 StateDirectory = "wire-rollback";
85 NotifyAccess = "all";
86 RemainAfterExit = "yes";
87
88 ExecStopPost = "${pkgs.coreutils}/bin/rm -f /var/lib/wire-rollback/goal";
89 };
90 };
91 }
92 // (lib.mapAttrs' (
93 _name: value:
94 lib.nameValuePair "${value.name}-key" {
95 description = "Service that requires ${value.path}";
96 path = [
97 pkgs.inotify-tools
98 pkgs.coreutils
99 ];
100 script = ''
101 MSG="Key ${value.path} exists."
102 systemd-notify --ready --status="$MSG"
103
104 echo "waiting to fail if the key is removed..."
105
106 while inotifywait -e delete_self "${value.path}"; do
107 MSG="Key ${value.path} no longer exists."
108
109 systemd-notify --status="$MSG"
110 echo $MSG
111
112 exit 1
113 done
114 '';
115 unitConfig = {
116 ConditionPathExists = value.path;
117 };
118 serviceConfig = {
119 Type = "simple";
120 Restart = "no";
121 NotifyAccess = "all";
122 RemainAfterExit = "yes";
123 };
124 }
125 ) config.deployment.keys);
126 };
127
128 deployment = {
129 _keys = lib.mapAttrsToList (
130 _: value:
131 value
132 // {
133 source = {
134 # Attach type to internally tag serde enum
135 t = builtins.replaceStrings [ "path" "string" "list" ] [ "Path" "String" "Command" ] (
136 builtins.typeOf value.source
137 );
138 c = value.source;
139 };
140 }
141 ) config.deployment.keys;
142
143 _hostPlatform = config.nixpkgs.hostPlatform.system;
144 };
145 };
146}