Skip to content

Commit 34f7648

Browse files
authored
Merge pull request #174 from booxter/ddns-switch
DDNS switch to dynu
2 parents 18cfd54 + f89bb3d commit 34f7648

File tree

9 files changed

+171
-23
lines changed

9 files changed

+171
-23
lines changed

nixos/beast/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,36 @@ This host is a storage/NAS node. The host-specific configuration lives in
1313
- Snapper timelines and scheduled Btrfs scrubs for data hygiene.
1414
- SMART monitoring for disk health.
1515

16+
## DDNS rollout (`jf.ihar.dev`, `au.ihar.dev`, `js.ihar.dev`)
17+
18+
`nixos/beast/default.nix` runs `services.ddclient` to update Dynu directly from
19+
this host (instead of router-managed DDNS).
20+
21+
1. Create a Dynu hostname (current: `ihrachyshka-home.freeddns.org`).
22+
1. In `nixos/beast/default.nix`, set:
23+
- `dynuHostname = "ihrachyshka-home.freeddns.org";`
24+
- `dynuUsername = "ihrachyshka";`
25+
1. Add `ddns.dynu.password` to `secrets/beast.yaml` via `sops`.
26+
1. Rebuild on beast.
27+
1. At your registrar DNS, repoint:
28+
- `jf.ihar.dev CNAME <dynu-hostname>`
29+
- `au.ihar.dev CNAME <dynu-hostname>`
30+
- `js.ihar.dev CNAME <dynu-hostname>`
31+
32+
Validation:
33+
34+
```bash
35+
dig +short jf.ihar.dev CNAME
36+
dig +short au.ihar.dev CNAME
37+
dig +short js.ihar.dev CNAME
38+
dig +short ihrachyshka-home.freeddns.org A
39+
systemctl status ddclient
40+
journalctl -u ddclient -n 100 --no-pager
41+
curl -I https://jf.ihar.dev
42+
curl -I https://au.ihar.dev
43+
curl -I https://js.ihar.dev
44+
```
45+
1646
## Storage
1747

1848
- `/volume2` is a Btrfs filesystem mounted with `compress=zstd`, `noatime`, and

nixos/beast/default.nix

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
config,
23
lib,
34
pkgs,
45
...
@@ -22,7 +23,9 @@ let
2223
nfsPorts = [
2324
2049 # nfsd
2425
];
25-
hasSopsSecrets = builtins.pathExists ../../secrets/beast.yaml;
26+
# DDNS provider target for public endpoints (jf/au/js).
27+
dynuHostname = "ihrachyshka-home.freeddns.org";
28+
dynuUsername = "ihrachyshka";
2629
in
2730
{
2831
imports = [
@@ -101,6 +104,7 @@ in
101104
"render"
102105
"video"
103106
];
107+
users.groups.ddclient-secrets = { };
104108
systemd.services.jellyfin.unitConfig.RequiresMountsFor = "/media";
105109

106110
# Reverse proxy with automatic TLS.
@@ -109,6 +113,39 @@ in
109113
defaults.email = "ihar.hrachyshka@gmail.com";
110114
};
111115

116+
# Run DDNS updates from this host (instead of the router).
117+
sops = {
118+
defaultSopsFile = ../../secrets/beast.yaml;
119+
age.keyFile = "/var/lib/sops-nix/key.txt";
120+
useSystemdActivation = true;
121+
secrets.ddnsDynuPassword = {
122+
key = "ddns/dynu/password";
123+
group = "ddclient-secrets";
124+
mode = "0440";
125+
};
126+
};
127+
128+
services.ddclient = {
129+
enable = true;
130+
interval = "1min";
131+
protocol = "dyndns2";
132+
server = "api.dynu.com";
133+
username = dynuUsername;
134+
passwordFile = config.sops.secrets.ddnsDynuPassword.path;
135+
domains = [ dynuHostname ];
136+
ssl = true;
137+
quiet = true;
138+
usev4 = "webv4,webv4=checkip.dynu.com/,webv4-skip='IP Address'";
139+
usev6 = "";
140+
};
141+
systemd.services.ddclient = {
142+
wants = [ "sops-install-secrets.service" ];
143+
after = [ "sops-install-secrets.service" ];
144+
serviceConfig = {
145+
SupplementaryGroups = [ "ddclient-secrets" ];
146+
};
147+
};
148+
112149
services.nginx = {
113150
enable = true;
114151
recommendedProxySettings = true;

scripts/sops-bootstrap.sh

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ usage() {
55
cat <<'EOF'
66
Usage:
77
scripts/sops-bootstrap.sh HOST [--user USER]
8+
scripts/sops-bootstrap.sh --help
89
910
This script:
1011
1) SSHes into HOST and generates /var/lib/sops-nix/key.txt (if missing)
@@ -44,24 +45,38 @@ user="${USER:-$(whoami)}"
4445

4546
while [[ $# -gt 0 ]]; do
4647
case "$1" in
48+
-h | --help)
49+
usage
50+
exit 0
51+
;;
4752
--user)
53+
if [[ $# -lt 2 || -z "${2:-}" ]]; then
54+
echo "Missing value for --user" >&2
55+
usage >&2
56+
exit 1
57+
fi
4858
user="$2"
4959
shift 2
5060
;;
61+
-*)
62+
echo "Unknown option: $1" >&2
63+
usage >&2
64+
exit 1
65+
;;
5166
*)
5267
if [[ -z "$host" ]]; then
5368
host="$1"
5469
shift
5570
else
56-
usage
71+
usage >&2
5772
exit 1
5873
fi
5974
;;
6075
esac
6176
done
6277

6378
if [[ -z "$host" ]]; then
64-
usage
79+
usage >&2
6580
exit 1
6681
fi
6782

scripts/sops-cat.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ usage() {
55
cat <<'EOF'
66
Usage:
77
scripts/sops-cat.sh [HOST]
8+
scripts/sops-cat.sh --help
89
910
Decrypt and print secrets/HOST.yaml.
1011
If HOST is omitted, the current short hostname is used.
@@ -24,12 +25,21 @@ resolve_repo_root() {
2425
host=""
2526
while [[ $# -gt 0 ]]; do
2627
case "$1" in
28+
-h | --help)
29+
usage
30+
exit 0
31+
;;
32+
-*)
33+
echo "Unknown option: $1" >&2
34+
usage >&2
35+
exit 1
36+
;;
2737
*)
2838
if [[ -z "$host" ]]; then
2939
host="$1"
3040
shift
3141
else
32-
usage
42+
usage >&2
3343
exit 1
3444
fi
3545
;;

scripts/sops-copy.sh

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ usage() {
55
cat <<'EOF'
66
Usage:
77
scripts/sops-copy.sh SRC_HOST DST_HOST KEY_PATH
8+
scripts/sops-copy.sh --help
89
910
Copy KEY_PATH from secrets/SRC_HOST.yaml into secrets/DST_HOST.yaml.
1011
Example:
@@ -86,14 +87,42 @@ path_to_jq_array() {
8687
}
8788

8889
main() {
89-
if [[ $# -ne 3 ]]; then
90-
usage
90+
local src_host=""
91+
local dst_host=""
92+
local key_path=""
93+
94+
while [[ $# -gt 0 ]]; do
95+
case "$1" in
96+
-h | --help)
97+
usage
98+
exit 0
99+
;;
100+
-*)
101+
echo "Unknown option: $1" >&2
102+
usage >&2
103+
exit 1
104+
;;
105+
*)
106+
if [[ -z "$src_host" ]]; then
107+
src_host="$1"
108+
elif [[ -z "$dst_host" ]]; then
109+
dst_host="$1"
110+
elif [[ -z "$key_path" ]]; then
111+
key_path="$1"
112+
else
113+
usage >&2
114+
exit 1
115+
fi
116+
shift
117+
;;
118+
esac
119+
done
120+
121+
if [[ -z "$src_host" || -z "$dst_host" || -z "$key_path" ]]; then
122+
usage >&2
91123
exit 1
92124
fi
93125

94-
local src_host="$1"
95-
local dst_host="$2"
96-
local key_path="$3"
97126
local key_expr
98127
local key_path_array
99128
key_expr="$(path_to_yq_expr "$key_path")"

scripts/sops-edit.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ usage() {
55
cat <<'EOF'
66
Usage:
77
scripts/sops-edit.sh [HOST]
8+
scripts/sops-edit.sh --help
89
910
If HOST is omitted, the current short hostname is used.
1011
If a template exists for HOST, merge it into the secret first.
@@ -27,12 +28,21 @@ resolve_repo_root() {
2728
repo_root="$(resolve_repo_root)"
2829
while [[ $# -gt 0 ]]; do
2930
case "$1" in
31+
-h | --help)
32+
usage
33+
exit 0
34+
;;
35+
-*)
36+
echo "Unknown option: $1" >&2
37+
usage >&2
38+
exit 1
39+
;;
3040
*)
3141
if [[ -z "$host" ]]; then
3242
host="$1"
3343
shift
3444
else
35-
usage
45+
usage >&2
3646
exit 1
3747
fi
3848
;;

scripts/sops-update.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ usage() {
55
cat <<'EOF'
66
Usage:
77
scripts/sops-update.sh [HOST]
8+
scripts/sops-update.sh --help
89
910
Update secrets/HOST.yaml from template defaults in secrets/_template.yaml.
1011
@@ -27,6 +28,15 @@ main() {
2728
host=""
2829
while [[ $# -gt 0 ]]; do
2930
case "$1" in
31+
-h | --help)
32+
usage
33+
exit 0
34+
;;
35+
-*)
36+
echo "Unknown option: $1" >&2
37+
usage >&2
38+
exit 1
39+
;;
3040
*)
3141
if [[ -z "$host" ]]; then
3242
host="$1"

secrets/_template.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
attic:
22
token: "REPLACE_ME"
3+
4+
ddns:
5+
dynu:
6+
password: "REPLACE_ME"

secrets/beast.yaml

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
11
attic:
2-
token: ENC[AES256_GCM,data:vT37QsyT7rwDPi61CHe4u7HuRcPhZd7b8Bgsl2vzeBsnYUmimDu6yw7AU2fL61/0oizzTMoLeoOohLejQK3Vy2MWBqwicKNaEt75TSWh5PZDqUpW1t/ziICgcwLQSL/UIMPBmLjfi+npuax6VDtF23BMkR14Z5IQhZCPUZDJcqgP8jp9WXlYaJ5HYFnNxO1z9ELJzd7IUk+DhjSJvZ8G2224X6/zqZC1KMmg6voDUSR+pSgm3kAJqWqfOoYFvbjHYk0BPzL4KhmJzpMx7p/Yb7moBA6mv2djydp52G0TfU7RRvhgpSIWXVE0DO1GfdSve3tOJjiqWzOb9lPE3Oz2ENu+h9KVAzrFigIsEWZntjXxFEd5DGX6ITMNiRHARXCNyZJ2T9XkGsqAiXN9h0yAT4HqEChfXIsErh8D+gutxFIqjKWYXiqz60arcYmm7X2+9qf1xo06rF5Sur7+Ig8FEBkT4vgRpM3Cod57Ufzb/OJy36De+A6+zpjTPWItuO9D7jW0j/qUFvXi8oIEPVLscy6/RR+gXEphe81LGDdjPko3TY1YX5NfmiiousM5to+DeSz3FsxjA0VctOZSxfNb5NFCLtgOz/HhP37Z93Jw8HSm85Bmt3TVaqpGWvoISQNXJ+7I9EuiOkjmdcvgEDBqYGfETqdtT73pF3AIMxl3TU/mmipZQtZgbDwKFGl28P6vild8E4eh4ADkkW09166WF/vKq0Z5ivRNvBrzvTOEAwnN8eNjUXEI+tIWkyQXbKGR1KyqDQ+V6CcGb7rM405mBb7QFGKPpz87g2CNZetVsymK9f+lj1ogf6Fp2bXMlbyh2eoIfiPzRm1oDfcP88sNs5GulNwHqN07yVoJK1Bsyofb2nDJyMqAK+pZ/94Szo17hds6Omoqr5/HUcnqo3AkjKDjQKLhG3OFxM12F2VsGM6rOSh6jdadb/zulWOgf2uryvN1Cc06JQGb60Zwr2i+Q4Eu7I7gpN0Dym9Y4SQfEQnGPn1IfDm+khi3ShI2HewkZsaTIdaT7yybR2WddBQ8f2/ousVkrIxU3GtH/iuZMr22kAyG+hotEF8jNPf5r79AtWZWdWLoFvgw1/MbpqZ2Wigbta5Vv04F2DtkxykLavCdW3m16E/wLUBVqJMLGD3XQLH9x+MQvfoX,iv:HD/ODKYCseYrkVoFFPBi+ShEEXQCpwDruj9XLYV2/EQ=,tag:HDqIXN+DIqd6G+/qNtmlEA==,type:str]
2+
token: ENC[AES256_GCM,data:ZgW3NcYg7yti/EHudju2I9eRShNNE8HnHh+hoCR1WBG7yxv7WZTqWa10W8TYSimJYIJiCuDiszWVibx5sqANdmaSgI7JV+NUpq8erwpTRkf5BGOa7vxLDvVPSUEJ+AuUQvLpqY2nCj/ErKpOTKsKn7jBKjaJ13qDZXhvwFMsn2KtOD4nu5VGo44H1hMUWC6OQNoXu1YxyS+jm+kKkPYxlcIEgYR/2PynSeoc7prwqG1veSfWH2S+APxW2fYjFHs4nyU9Gp5b5hUe7DgGmisKT/sMI1j2Odgma6uCRbgwBBUh6cMvbqJHvkSht2Qm1+o90E2ZEr0G/7EPavBVSPINmLQ+6jAN8fFcLUy1AUDAw108eh1uUjVFzjcn2DWfkWzoYEnEDOgapuQ5HJ41N5F+H/Hxtr8Y31EMahrp5gDq6JZ6tQPwBhDhqv04ZU+J6Sm9ReZNNahktqGRwjZ62R+Iupnfc2E5CUkjypq4Hwn0ARGU9psshjz1lL1yYppwHlQtx90F5KKo2jSo2xY+tuBoKJa/SbA2ih9Sb5LdtE7OpMTyQz6FDWA6KqL52O3gddJjoePNuBD2ivsGm+/sHUn0XeCrv1RVaaFRi10kWbIKL5nxtwHYcxDhkDHyE+c73P1DxnH3AkgRMoJ2sWYZQmybiDz86g7OqGcNYk28sjykP95YUf2XlE+NvAgR/k9FUS8ieV4gobmk/Re4mCMFPvgiJgjwmPYm2/miNE+waTDGrVpKcV2aYiZSMpw/Qos6ENxeFLDxEeSzXIYBGgWY1L8ZQnNdzaqO3ayi8Md4m39vOSPMS+o6WMh1nEe9QZhEa87TcfR7qoJiqk3OrFI+zovnbqvg2LKzRvliq+BktuAUInGCMMcaXhwzNeZj0xmNczdZP1zMxQShlv+533qpkl7bQBfyB1DbeIZ8Xa9loWaFD97XBbmI2YmBjomoyv03vYj9QSjI4NwIWESxQ3DUrinQEnLZKRoMP/2ycQ+X67T09XXLlCWW8lqguZ/KBJu801MzKHxxhy9QUhTm74KxqKfLO+Ka8bF4YVPMhDhXyOIZrTWhCdDsBdmDds6YME2zD9V6ss9+1ObbPDnAhg4ii7f4BUrra48Os1sCFkJAXU1ChU4O9CUiAS9SckM4Ha1rirpj4RHE2066Jhee,iv:H/eWpCNe6n53Ntvgmy16MiDlOoABy/kuET3dwqr1uRQ=,tag:7YgkxQZ3pxiWIcXYp2pkvw==,type:str]
3+
ddns:
4+
dynu:
5+
password: ENC[AES256_GCM,data:HvSmMm4IVoeBWxrYj8Yfun7JedU=,iv:l+Y9QRgFEX//c1JIPM9GlTiNi8RmTT62XYVVnhzF0Mk=,tag:2beGCSkMPEF8L3MnllD9SQ==,type:str]
36
sops:
47
age:
58
- recipient: age15l7h4scc9fgprgpfnlfa00s6jz6hecqmuhud3qv38lsjvq8p43lqaqq6n7
69
enc: |
710
-----BEGIN AGE ENCRYPTED FILE-----
8-
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrVXBodXA2MHNjZGpCdTBp
9-
S0ZjQkNpUGpkZGc3aWZmVytxdFdjVUVPK0ZFCi9DQVJKWkFXQzdIYW5zRmd1dHoz
10-
M2I5QXU3MnBRcW5ja29yRGlhM2t3aUEKLS0tIG10MGNZdmNPQmg4aFZCbVZtLzRk
11-
MnozVHhUOTE0SW9SSGxTRnVlQXNQU3MKzzLSIfZ/9R54wcv0e1mYhX6srO08aaaq
12-
M5u6L24X+cb9ACJCyo0/a0Hx/Vj1scln7tA1l2uUcTO56E6GU+0c4w==
11+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEWE15VWNhbW5JdVJrMGlV
12+
a09pNmtMbWttM2V0QXRTc0dpeDhMWFdHZWxJCnNYeWlENTBUb0JQN1MvL0ozMW9v
13+
VEljN21oUmlIdGhGYklrWEc5VW45Y2MKLS0tICtaNTR4ODRMQnV3YUVXSEdVbDAz
14+
VkdwMmhmNkVzZzNPeGZUZEh6YVhqM0EKYOlerQW0E0soPjQLGah/ZquBy30lSWro
15+
WlG0kqbMRYR0cDDmuy8LQW41uiH5ZOP5oRjZuuSr2Z+wd1cpGn0rAg==
1316
-----END AGE ENCRYPTED FILE-----
1417
- recipient: age1j7spzd26l7zryxsrf4r5f3jj7cv5tm7kastp6294fdl8qz975u3sku09hv
1518
enc: |
1619
-----BEGIN AGE ENCRYPTED FILE-----
17-
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4VkxGeXdpbTliei96cHNi
18-
R3hzQUVPSCtPRW9NOTBPK2I2b29aU1JTZGw4Ckl0NFFKRFZVN0I3UXZHNkhYdlJC
19-
UTZWaDFZc3NyM3VORlFDUXFLdTI5ancKLS0tIDBhYkhUSlNCdk9NQXA2QzEvMVZS
20-
aWpUNzVkUVFhQzEwRXdMaUJOTmdqM1EKf9Ff+Kt1KuLIfwp+W6Bn4ZHViZL1Azsw
21-
ypKEfH8PYqSwPipDzFOK0VOCN4Iti651D9X0oyZe7Q41liqFlvnOOQ==
20+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWaFlMQmdqQVZWTFk1bU9u
21+
aytYaDNGQ1VOeTdtSHE1MTlIbnRXTmxSZWtrClUxRnNTSnF5MnZFMWdJVWF1UnVF
22+
V2NSWDU3aTdHSDNxSTJJNFpHSlV0NUEKLS0tIER5Ly9lcFFLbzVVazErTGF4Z1Jo
23+
eXBuNnVBYjlhVVE4N0tOT0IxQlA4emMKQl/gNsP2H9sf/PVfQwjhK49aWfAd55Zl
24+
VnAMkOYr8JWllQLdl/5v5WZUlpz6726KlmM4ef1WCpPRnrXpTXJJcQ==
2225
-----END AGE ENCRYPTED FILE-----
23-
lastmodified: "2026-02-22T03:34:23Z"
24-
mac: ENC[AES256_GCM,data:7KZh12b7J/N/HxKhA8IsSxS9Kf5NcdlhYpWJRkMrMhvIXFtIKFo+PCt9b7ajob2WVF4ASs4wv2xxYmxaFTEPHunxhPHyVvt0oGkev63nOXlGu3dpXA28M6tdgkab/ho605WyDwoE4AzlSPKo6aa5NaueRnByjbncR8ow6k/HeMU=,iv:cCCAGmrGbhoYZhSpEFLccbOJsU5gmZivYccGjL5Wv3Y=,tag:65go27f6OP/SDrH+Z87Wfg==,type:str]
26+
lastmodified: "2026-02-24T01:34:36Z"
27+
mac: ENC[AES256_GCM,data:n0IXpEnpu8hzBS1JFLs5khf2LilfObeZzGzJ6U6bzRwzJuEdPj9pBPrLd6+Z9aAIqKP4mAa89q9dNsmgrzfOzPFVNP/aloDFDh+tNbQ/WMk62Fk3ocV9DKGhKIDXaiIcSnzNjarDF0DRomdE9D4W8usW1jJtkl3EwXTKlv1n+eY=,iv:05jEc2SbVL9nMS0STwUNxaj/W0zQ3srGLV9ObiKdbPQ=,tag:KaF4Y4Zqr4PY3iN4ewDdCw==,type:str]
2528
unencrypted_suffix: _unencrypted
2629
version: 3.11.0

0 commit comments

Comments
 (0)