Skip to content

Commit 229cbc9

Browse files
committed
Add a script to automate the host creation via templates
The script scripts/mkHost.sh automates the generation of a new host, based on a template file. This template looks like a .nix file with placeholder \$KEY fields that will be populated dynamically such as name, site, user, etc.
1 parent 49168fb commit 229cbc9

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed

scripts/mkHost.sh

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
#! /usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
usage() {
6+
cat <<'EOF'
7+
Usage:
8+
nix run .#mkHost -- <name> <sitename> <username> [--system <x86_64-linux|aarch64-linux>] [--help] [--template <path>] [KEY=VALUE ...]
9+
10+
Arguments:
11+
<name> Hostname / machine name (e.g. alfa, bravo, ...)
12+
<sitename> Logical site / location name (e.g. theta, ...)
13+
<username> User name for the admin user of the new machine
14+
15+
Options:
16+
-s, --system Override detected system (default: based on uname -m)
17+
Allowed: x86_64-linux, aarch64-linux
18+
-t, --template Path to a template file to use instead of /etc/nixos/templates/hosts/default.tmpl
19+
-h, --help Show this help
20+
21+
Extra template variables:
22+
Any additional arguments of the form KEY=VALUE after the options will be
23+
exported to the environment before rendering the template, so they can be
24+
used as \$KEY placeholders inside the template.
25+
EOF
26+
}
27+
28+
info() {
29+
printf "\e[1;32m[INFO]\e[0m %s\n" "$1"
30+
}
31+
32+
error() {
33+
printf "\e[1;31m[ERROR]\e[0m %s\n\n" "$1" >&2
34+
usage >&2
35+
exit 1
36+
}
37+
38+
interaction() {
39+
printf "\e[1;33m[ACTION]\e[0m %s" "$1"
40+
}
41+
42+
prompt() {
43+
local msg="$1"
44+
interaction "$msg [Y/n] "
45+
read -r answer
46+
47+
case "${answer:-Y}" in
48+
[Yy]*)
49+
return 0
50+
;;
51+
*)
52+
return 1
53+
;;
54+
esac
55+
}
56+
57+
if [[ $# -eq 0 ]]; then
58+
usage
59+
exit 0
60+
fi
61+
62+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
63+
usage
64+
exit 0
65+
fi
66+
67+
ARCH="$(uname -m)"
68+
case "$ARCH" in
69+
x86_64)
70+
SYSTEM="x86_64-linux"
71+
;;
72+
aarch64 | arm64)
73+
SYSTEM="aarch64-linux"
74+
;;
75+
*)
76+
SYSTEM=""
77+
;;
78+
esac
79+
80+
if [[ $# -lt 3 ]]; then
81+
error "missing required arguments <name> <sitename> <username>."
82+
fi
83+
84+
NAME="$1"
85+
SITE="$2"
86+
USER="$3"
87+
shift 3
88+
89+
if [[ ! "$NAME" =~ ^[a-zA-Z0-9._-]+$ ]]; then
90+
error "invalid host name '$NAME'.
91+
Allowed characters: letters, numbers, dot, underscore, dash."
92+
fi
93+
94+
if [[ ! "$SITE" =~ ^[a-zA-Z0-9._-]+$ ]]; then
95+
error "invalid site name '$SITE'.
96+
Allowed characters: letters, numbers, dot, underscore, dash."
97+
fi
98+
99+
if [[ ! "$USER" =~ ^[a-z_][a-z0-9_-]*$ ]]; then
100+
error "invalid username '$USER'.
101+
Allowed pattern: ^[a-z_][a-z0-9_-]*$ (like normal Unix usernames)."
102+
fi
103+
104+
BASE_DIR="/etc/nixos"
105+
HOSTS_DIR="$BASE_DIR/hosts"
106+
TARGET_DIR="${HOSTS_DIR}/${NAME}"
107+
TEMPLATE_FILE="$BASE_DIR/templates/hosts/default.tmpl"
108+
EXTRA_VARS=()
109+
110+
while [[ $# -gt 0 ]]; do
111+
case "$1" in
112+
-h | --help)
113+
usage
114+
exit 0
115+
;;
116+
-s | --system)
117+
if [[ $# -lt 2 ]]; then
118+
error "--system requires an argument."
119+
fi
120+
SYSTEM="$2"
121+
case "$SYSTEM" in
122+
x86_64-linux | aarch64-linux) ;;
123+
*)
124+
error "invalid --system value '$SYSTEM'.
125+
Allowed: x86_64-linux, aarch64-linux"
126+
;;
127+
esac
128+
shift 2
129+
;;
130+
-t | --template)
131+
if [[ $# -lt 2 ]]; then
132+
error "--template requires a path argument."
133+
fi
134+
TEMPLATE_FILE="$2"
135+
shift 2
136+
;;
137+
-* | --*)
138+
error "unknown option '$1'."
139+
;;
140+
*)
141+
if [[ "$1" == *=* ]]; then
142+
key="${1%%=*}"
143+
144+
case "$key" in
145+
NAME | SITE | USER | ARCH | SYSTEM | STATE_VERSION | BASE_DIR | HOSTS_DIR | TARGET_DIR | TEMPLATE_FILE | TARGET_FILE | EXTRA_VARS)
146+
error "extra variable '$key' is reserved and cannot be overridden."
147+
;;
148+
esac
149+
150+
EXTRA_VARS+=("$1")
151+
shift
152+
else
153+
error "unexpected positional argument '$1' after <name> <sitename> <username>."
154+
fi
155+
;;
156+
esac
157+
done
158+
159+
if [[ -z "${SYSTEM:-}" ]]; then
160+
error "unsupported architecture '$ARCH' from uname -m.
161+
Please specify --system x86_64-linux or aarch64-linux explicitly."
162+
fi
163+
164+
if [[ -d "$BASE_DIR" ]]; then
165+
if [[ ! -d "$HOSTS_DIR" ]]; then
166+
info "creating host directory '$HOSTS_DIR'..."
167+
mkdir -p "$HOSTS_DIR"
168+
fi
169+
else
170+
error "this script must be run on a NixOS system (with '$BASE_DIR' available)."
171+
fi
172+
173+
if [[ -e "$TARGET_DIR" ]]; then
174+
error "host directory '$TARGET_DIR' already exists."
175+
fi
176+
177+
mkdir -p "$TARGET_DIR"
178+
179+
if ! command -v nixos-generate-config >/dev/null 2>&1; then
180+
error "nixos-generate-config not found in PATH.
181+
This script must be run on a NixOS system (or with nixos-generate-config available)."
182+
fi
183+
184+
info "generating hardware configuration for $NAME..."
185+
nixos-generate-config --show-hardware-config >"${TARGET_DIR}/hardware.nix"
186+
187+
TARGET_FILE="$TARGET_DIR/default.nix"
188+
189+
if [[ ! -f "$TEMPLATE_FILE" ]]; then
190+
error "template file '$TEMPLATE_FILE' not found."
191+
fi
192+
193+
STATE_VERSION=
194+
if [[ -z "$STATE_VERSION" ]]; then
195+
if command -v nixos-option >/dev/null 2>&1; then
196+
v="$(nixos-option system.stateVersion 2>/dev/null |
197+
sed -nE 's/^[[:space:]]*"([^"]+)".*/\1/p' |
198+
head -n1)"
199+
if [[ -n "$v" && "$v" != "null" ]]; then
200+
STATE_VERSION="$v"
201+
fi
202+
fi
203+
fi
204+
205+
if [[ -z "$STATE_VERSION" ]]; then
206+
if command -v nixos-version >/dev/null 2>&1; then
207+
v="$(nixos-version | sed -E 's/^([0-9]+\.[0-9]+).*/\1/')"
208+
if [[ -n "$v" ]]; then
209+
STATE_VERSION="$v"
210+
fi
211+
fi
212+
fi
213+
214+
if [[ -z "$STATE_VERSION" ]]; then
215+
error "Could not determine system.stateVersion automatically.
216+
Please set it manually or ensure nixos-option / nixos-version are available."
217+
fi
218+
219+
for kv in "${EXTRA_VARS[@]}"; do
220+
export "$kv"
221+
done
222+
export NAME SITE USER STATE_VERSION
223+
224+
if [[ "$TEMPLATE_FILE" == "$BASE_DIR/templates/hosts/default.tmpl" ]]; then
225+
info "generating configuration for $NAME from default template..."
226+
else
227+
info "generating configuration for $NAME from template '$TEMPLATE_FILE'..."
228+
fi
229+
envsubst <"$TEMPLATE_FILE" >"$TARGET_FILE"
230+
231+
if prompt "successfully generated a new config for $NAME. Do you want to edit it now?"; then
232+
"${EDITOR:-vi}" "$TARGET_FILE"
233+
else
234+
info "skipping edit."
235+
fi
File renamed without changes.

0 commit comments

Comments
 (0)