Compare commits

...

156 commits
v0.3.0 ... main

Author SHA1 Message Date
0f4ff679d2
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 4s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-04-03 22:10:16 +02:00
e28cf9be3f
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-04-03 22:00:34 +02:00
1dac706aeb
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-04-02 22:00:44 +02:00
29916c0841 fix(deps): update rust crate axum-client-ip to v1
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 0s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
| datasource | package        | from  | to    |
| ---------- | -------------- | ----- | ----- |
| crate      | axum-client-ip | 0.7.0 | 1.0.0 |
2025-04-02 19:43:49 +02:00
3c9587bd4c
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-04-01 22:00:24 +02:00
7dcd2c6a4c
fix(deps): update rust crate clap to v4.5.35
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 3s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.34 | 4.5.35 |
2025-04-01 19:20:25 +02:00
5a98e8205f
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 4s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-03-31 22:00:32 +02:00
2183c81d70
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 1s
2025-03-30 22:00:20 +02:00
4ab2f709a9
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 4s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-03-29 22:00:33 +01:00
269a37c920
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 4s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-03-28 22:00:17 +01:00
6ac1133486
fix(deps): update rust crate axum to v0.8.3
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 0s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
| datasource | package | from  | to    |
| ---------- | ------- | ----- | ----- |
| crate      | axum    | 0.8.1 | 0.8.3 |
2025-03-28 21:50:30 +01:00
c5c51645ba
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-03-27 22:00:14 +01:00
fe58c295ff
fix(deps): update rust crate clap to v4.5.34
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ test (push) Successful in 13s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.33 | 4.5.34 |
2025-03-27 03:10:24 +01:00
f356463079
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 4s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-03-26 22:00:30 +01:00
caa7c1165f
fix(deps): update rust crate clap to v4.5.33
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 0s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.32 | 4.5.33 |
2025-03-26 20:20:14 +01:00
7dca0efdf2
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 4s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-03-24 22:00:31 +01:00
5f2ec1089f
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
2025-03-23 22:00:37 +01:00
ba2babb0ae
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 2s
/ test (push) Successful in 11s
/ report-size (push) Successful in 7s
2025-03-21 22:00:34 +01:00
f9da4aecd4
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 2s
/ test (push) Successful in 11s
/ report-size (push) Successful in 7s
2025-03-20 22:00:15 +01:00
2d00dd3818
chore(deps): pin rust crate insta to =1.42.2
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 2s
/ test (push) Successful in 11s
/ report-size (push) Successful in 6s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | insta   | 1.42.2 | 1.42.2 |
2025-03-19 23:20:34 +01:00
2daf620a4a
chore: pin nix-flake-outputs-size
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
Renovate seems to be erroring out when trying to do it.
2025-03-19 23:10:10 +01:00
316f2bf576
feat: add config file to webnsupdate
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 1s
/ test (push) Successful in 12s
/ report-size (push) Successful in 2s
Move flags to config file, and add more options. Mirror some in the
module.
2025-03-19 23:05:38 +01:00
3d660314cf
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 2s
/ test (push) Successful in 12s
/ report-size (push) Successful in 7s
2025-03-19 22:00:49 +01:00
f207cbe859
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 2s
/ test (push) Successful in 12s
/ report-size (push) Successful in 7s
2025-03-18 22:00:32 +01:00
c589fb40c3
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 2s
/ test (push) Successful in 12s
/ report-size (push) Successful in 6s
2025-03-16 22:00:43 +01:00
08ea3271c1
ci: use nix-fast-build to speedup checks
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ test (push) Successful in 11s
/ report-size (push) Successful in 2s
Should be a noticeable improvement c:
2025-03-16 19:27:05 +01:00
8787adae30
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-03-14 22:00:31 +01:00
130c949723
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-03-13 22:00:46 +01:00
e236aa424b
fix(deps): update rust crate tokio to v1.44.1
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 0s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 6s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | tokio   | 1.44.0 | 1.44.1 |
2025-03-13 09:40:24 +01:00
baeb98a2e6
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-03-12 01:00:32 +01:00
9b41d7d2a5
fix(deps): update rust crate http to v1.3.1
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from  | to    |
| ---------- | ------- | ----- | ----- |
| crate      | http    | 1.3.0 | 1.3.1 |
2025-03-11 21:50:30 +01:00
33f8b1570d
fix(deps): update rust crate ring to v0.17.14
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from    | to      |
| ---------- | ------- | ------- | ------- |
| crate      | ring    | 0.17.13 | 0.17.14 |
2025-03-11 21:20:24 +01:00
06995416d2
fix(deps): update rust crate http to v1.3.0
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from  | to    |
| ---------- | ------- | ----- | ----- |
| crate      | http    | 1.2.0 | 1.3.0 |
2025-03-11 18:00:26 +01:00
f8848e669e
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 6s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
2025-03-10 22:00:53 +01:00
72843b8f52
fix(deps): update rust crate clap to v4.5.32
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.31 | 4.5.32 |
2025-03-10 21:50:28 +01:00
4cdffc20bc chore(config): migrate config .renovaterc.json
All checks were successful
/ check-renovaterc (push) Successful in 3s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 8s
/ check (module-nginx-test) (push) Successful in 10s
/ check (nextest) (push) Successful in 4s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-03-10 08:09:25 +01:00
cf66c77136
chore(deps): lock file maintenance
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-03-09 22:00:32 +01:00
5fc53886f2
chore(deps): pin dependencies
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 0s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 6s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
2025-03-09 21:50:22 +01:00
881983dd6c ci: validate renovaterc
All checks were successful
/ check-renovaterc (push) Successful in 2s
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
Also use config:best-practices
2025-03-09 21:38:51 +01:00
632250d544
fix(deps): update rust crate serde to v1.0.219
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from    | to      |
| ---------- | ------- | ------- | ------- |
| crate      | serde   | 1.0.218 | 1.0.219 |
2025-03-09 20:20:28 +01:00
c71a8b418c
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 6s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
2025-03-07 22:00:42 +01:00
873dd980ff
fix(deps): update rust crate tokio to v1.44.0
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | tokio   | 1.43.0 | 1.44.0 |
2025-03-07 21:20:23 +01:00
3e1140ffe5
fix(deps): update rust crate ring to v0.17.13
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 1s
| datasource | package | from    | to      |
| ---------- | ------- | ------- | ------- |
| crate      | ring    | 0.17.12 | 0.17.13 |
2025-03-07 01:40:27 +01:00
6c78e8d78e
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-03-06 02:20:27 +01:00
3ed76c094c
fix(deps): update rust crate ring to v0.17.12
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from    | to      |
| ---------- | ------- | ------- | ------- |
| crate      | ring    | 0.17.11 | 0.17.12 |
2025-03-06 02:10:27 +01:00
f16c3b9138
fix(typo): typos corrected typ to typo
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
This is wrong for us (we use typ instead of type, not typo, because type
is a rust keyword).
2025-03-04 09:46:38 +01:00
42482574ac
chore(deps): lock file maintenance
Some checks failed
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Failing after 3s
/ report-size (push) Successful in 2s
2025-03-03 22:00:50 +01:00
855963bc85
fix(deps): update rust crate serde_json to v1.0.140
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package    | from    | to      |
| ---------- | ---------- | ------- | ------- |
| crate      | serde_json | 1.0.139 | 1.0.140 |
2025-03-03 10:20:22 +01:00
7cc182b23e
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-03-02 02:10:30 +01:00
bc62fd7c1d
chore(deps): update rust crate insta to v1.42.2
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | insta   | 1.42.1 | 1.42.2 |
2025-03-02 02:00:39 +01:00
cdef8078cc
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-03-01 22:00:54 +01:00
a6f0785dc3
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 8s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
2025-03-01 00:00:35 +01:00
5d4c0fdb70
ci(renovate): don't overlap schedules
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
Branch creation schedule and automerge schedule should not overlap, or
we'll get PRs that don't contain updates, which stop renovate from
creating further PRs.
2025-02-28 23:52:07 +01:00
2a2f9ef06c
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
2025-02-27 23:00:49 +01:00
ef6e955b90
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-02-24 17:00:45 +01:00
b581e2adf1
fix(deps): update rust crate clap to v4.5.31
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.30 | 4.5.31 |
2025-02-24 16:50:22 +01:00
48034ec6e5
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-02-23 23:00:42 +01:00
593bee9024
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 2s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-02-23 00:10:23 +01:00
09345f2193
fix(deps): update rust crate ring to v0.17.11
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
| datasource | package | from    | to      |
| ---------- | ------- | ------- | ------- |
| crate      | ring    | 0.17.10 | 0.17.11 |
2025-02-22 19:50:25 +01:00
71d1e43ef2
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-02-21 23:00:24 +01:00
528aad1d8e
fix(deps): update rust crate ring to v0.17.10
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
| datasource | package | from   | to      |
| ---------- | ------- | ------ | ------- |
| crate      | ring    | 0.17.9 | 0.17.10 |
2025-02-21 18:30:23 +01:00
cb7e4d554b
fix(deps): update rust crate serde to v1.0.218
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
| datasource | package | from    | to      |
| ---------- | ------- | ------- | ------- |
| crate      | serde   | 1.0.217 | 1.0.218 |
2025-02-20 06:30:30 +01:00
01f53b2bf0
fix(deps): update rust crate serde_json to v1.0.139
All checks were successful
/ build (push) Successful in 2s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package    | from    | to      |
| ---------- | ---------- | ------- | ------- |
| crate      | serde_json | 1.0.138 | 1.0.139 |
2025-02-20 04:10:23 +01:00
60662ff1f0
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-02-19 17:41:04 +01:00
eaed7b2302
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 6s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-02-17 23:00:41 +01:00
8a04c2726f
fix(deps): update rust crate clap to v4.5.30
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.29 | 4.5.30 |
2025-02-17 20:20:29 +01:00
1a88dbaeb2
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 4s
/ report-size (push) Successful in 2s
2025-02-16 20:30:52 +01:00
c41008f800
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
2025-02-15 23:00:22 +01:00
3c18f07a2a
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 2s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-02-14 02:10:35 +01:00
0a5348097d
fix(deps): update rust crate ring to v0.17.9
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 6s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | ring    | 0.17.8 | 0.17.9 |
2025-02-14 02:00:26 +01:00
bdb27d7cb1
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-02-12 23:00:37 +01:00
41c30372fb
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-02-11 21:40:30 +01:00
e99bc52de2
fix(deps): update rust crate clap to v4.5.29
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.28 | 4.5.29 |
2025-02-11 21:20:45 +01:00
338e296683
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
2025-02-10 09:20:41 +01:00
29f7315f67
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-02-06 23:00:24 +01:00
738fa8accf
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-02-05 23:30:28 +01:00
172076eaad
feat(webnsupdate): parse IPv6 prefixes
All checks were successful
/ build (push) Successful in 2s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
This allows us to better support IPv6 from fritzbox updates in the
future.
2025-02-05 23:17:49 +01:00
b775f8e811
refactor(nsupdate): send all commands at once
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 6s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
This ensures `nsupdate` is only called once per IP update (even for both
IPv4 and IPv6 in a single call).
2025-02-05 22:47:13 +01:00
48c2e5be4d
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 6s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-02-04 23:00:40 +01:00
d56af9ecfe
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
2025-02-04 00:00:46 +01:00
72aa4f365e
fix(deps): update rust crate clap to v4.5.28
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.27 | 4.5.28 |
2025-02-03 23:50:29 +01:00
d8630aa8cb
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-02-01 23:00:21 +01:00
5655d7de67
fix(deps): update rust crate miette to v7.5.0
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
| datasource | package | from  | to    |
| ---------- | ------- | ----- | ----- |
| crate      | miette  | 7.4.0 | 7.5.0 |
2025-02-01 04:20:25 +01:00
d2f6c3cd66
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-01-31 22:00:20 +01:00
13c9c544a7
fix(webnsupdate): updating IPv6 in ipv4-only mode
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
Dumb logic error T-T.
2025-01-31 21:46:27 +01:00
70ed898f1d
fix(tests): add case for when query has empty string
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
This is produced by the FRITZ!Box DDNS client.
2025-01-31 21:25:41 +01:00
2f97008475
fix(webnsupdate): make IP none when query is empty
This happens when one of the IPs hasn't changed (but the other has) in
the FRITZ!Box DDNS client.
2025-01-31 21:25:41 +01:00
09bd450a46
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 1s
/ check (module-ipv4-only-test) (push) Successful in 1s
/ check (module-ipv4-test) (push) Successful in 1s
/ check (module-ipv6-only-test) (push) Successful in 1s
/ check (module-ipv6-test) (push) Successful in 1s
/ check (module-nginx-test) (push) Successful in 1s
/ check (nextest) (push) Successful in 1s
/ check (treefmt) (push) Successful in 0s
/ report-size (push) Successful in 1s
2025-01-29 23:00:44 +01:00
a6bb8bf817
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 2s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-only-test) (push) Successful in 7s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-01-29 00:00:30 +01:00
a39fa354e4
feat(webnsupdate): add support for fritzbox style updates
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-only-test) (push) Successful in 6s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-only-test) (push) Successful in 10s
/ check (module-ipv6-test) (push) Successful in 8s
/ check (module-nginx-test) (push) Successful in 10s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
Update now optionally accepts query params (e.g. `/update?ipv4=1.2.3.4`)
if present they will control how the update is made (only ipv4 and ipv6
are implemented right now).

Closes #80
2025-01-28 23:56:52 +01:00
26566fd612
fix(deps): update rust crate serde_json to v1.0.138
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 1s
| datasource | package    | from    | to      |
| ---------- | ---------- | ------- | ------- |
| crate      | serde_json | 1.0.137 | 1.0.138 |
2025-01-28 19:00:24 +01:00
09be5627c3
refactor(module): NixOS tests
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
Way easier to extend/maintain.
2025-01-28 00:31:32 +01:00
dff29cab77
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
2025-01-26 23:00:37 +01:00
c8407a8eb4
chore(release): prepare for 0.3.6
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 1s
/ check (module-ipv4-test) (push) Successful in 1s
/ check (module-ipv6-test) (push) Successful in 1s
/ check (module-nginx-test) (push) Successful in 1s
/ check (nextest) (push) Successful in 1s
/ check (treefmt) (push) Successful in 1s
/ report-size (push) Successful in 2s
Generate changelog and bump version.
2025-01-26 22:39:19 +01:00
77cb03576d
feat(flake): add tests for new allowedIPVersion option
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 1s
This ensures we don't accidentally update records.
2025-01-26 22:30:04 +01:00
a47dd0bfac
feat(module): add option for setting --ip-type
This makes it easy to set the server to ipv4-only mode.
2025-01-26 22:25:41 +01:00
34ce8a69f6
feat(webnsupdate): allow running in IPv4/6 only mode
This fixes issues when the IPv6/IPv4 is not working properly but updates
are still happening (like rn) T-T.
2025-01-26 22:25:41 +01:00
ea428d1aef
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-test) (push) Successful in 8s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 6s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-01-25 19:40:22 +01:00
98aa3c2a97
chore(deps): update rust crate insta to v1.42.1
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 3s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | insta   | 1.42.0 | 1.42.1 |
2025-01-25 19:30:20 +01:00
0fc17d9150
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 3s
/ report-size (push) Successful in 2s
2025-01-24 23:00:42 +01:00
e72e3777b8
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
2025-01-23 23:00:21 +01:00
2473e6edbc
chore(release): prepare for 0.3.5 (docs)
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 1s
/ check (module-ipv4-test) (push) Successful in 1s
/ check (module-ipv6-test) (push) Successful in 1s
/ check (module-nginx-test) (push) Successful in 1s
/ check (nextest) (push) Successful in 1s
/ check (treefmt) (push) Successful in 1s
/ report-size (push) Successful in 2s
Generate changelog
2025-01-23 22:49:09 +01:00
989ed2a080
chore(release): prepare for 0.3.5 (rust)
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-test) (push) Successful in 7s
/ check (module-ipv6-test) (push) Successful in 7s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 3s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
Bump version
2025-01-23 22:47:26 +01:00
2e8d20f89d
ci: parallelize checks
All checks were successful
/ build (push) Successful in 1s
/ check (clippy) (push) Successful in 2s
/ check (module-ipv4-test) (push) Successful in 6s
/ check (module-ipv6-test) (push) Successful in 6s
/ check (module-nginx-test) (push) Successful in 7s
/ check (nextest) (push) Successful in 2s
/ check (treefmt) (push) Successful in 2s
/ report-size (push) Successful in 2s
If I ever get more runners, this will be a great speedup.
2025-01-23 22:32:15 +01:00
ec27e31336
feat(tests): add nginx integration test
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 9s
/ report-size (push) Successful in 1s
This ensures we can handle IPv4 and IPv6 simultaneously
2025-01-23 22:24:30 +01:00
e5f7d94f77
feat: tune compilation for size
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 8s
/ report-size (push) Successful in 2s
Reduce size by setting `codegen-units = 1` in release mode.
2025-01-23 21:31:27 +01:00
a2735b46b5
feat(webnsupdate): add handling for multiple IPs
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 8s
/ report-size (push) Successful in 2s
Specifically, for when both and IPv6 and and IPv4 addr is provided. This
ensures we can forward both addrs to webnsupdate, instead of only
allowing IPv4.
2025-01-23 21:10:21 +01:00
542336867a
fix(module): test both IPv4 and IPv6
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 8s
/ report-size (push) Successful in 2s
This ensures both work
2025-01-23 21:06:26 +01:00
70162c83f6
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 6s
/ report-size (push) Successful in 1s
2025-01-21 23:00:21 +01:00
0fd9a87907
fix(deps): update rust crate axum to v0.8.2
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
| datasource | package | from  | to    |
| ---------- | ------- | ----- | ----- |
| crate      | axum    | 0.8.1 | 0.8.2 |
2025-01-21 14:20:22 +01:00
880d462e80
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 2s
/ check (push) Successful in 8s
/ report-size (push) Successful in 2s
2025-01-20 22:30:26 +01:00
40a9d600c9
fix(deps): update rust crate clap to v4.5.27
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.26 | 4.5.27 |
2025-01-20 22:20:18 +01:00
8bf62f3ce2
fix(flake): switch overlay to callPackage
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 1s
This ensures it can be built for any architecture (supported by Rust).
2025-01-10 23:42:40 +01:00
883f6e6ae7
fix(deps): update rust crate clap to v4.5.26
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.25 | 4.5.26 |
2025-01-09 19:30:24 +01:00
4863ebc6df
fix(deps): update rust crate clap to v4.5.25
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.24 | 4.5.25 |
2025-01-09 16:40:31 +01:00
36b4d55ea8
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 1s
2025-01-08 23:00:22 +01:00
faf1f637ab
fix(deps): update rust crate tokio to v1.43.0
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | tokio   | 1.42.0 | 1.43.0 |
2025-01-08 17:10:21 +01:00
9b2880c141
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 0s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
2025-01-07 18:30:25 +01:00
0685c2601a
fix(deps): update rust crate clap to v4.5.24
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | clap    | 4.5.23 | 4.5.24 |
2025-01-07 18:20:30 +01:00
5e16700652
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
2025-01-06 23:00:38 +01:00
ccc1ccba97
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
2025-01-05 12:20:24 +01:00
a515c5d8df
fix(renovaterc): invalid cron syntax
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
There was a small mistake in the config.
2025-01-05 12:17:09 +01:00
7fdf322c73
fix(renovate): branch creation before automerge
All checks were successful
/ build (push) Successful in 0s
/ check (push) Successful in 6s
/ report-size (push) Successful in 2s
Otherwise renovate might miss the merge window QAQ
2025-01-05 11:11:02 +01:00
5d6fd054ee
chore(deps): update rust crate insta to v1.42.0
All checks were successful
/ build (push) Successful in 0s
/ check (push) Successful in 6s
/ report-size (push) Successful in 2s
| datasource | package | from   | to     |
| ---------- | ------- | ------ | ------ |
| crate      | insta   | 1.41.1 | 1.42.0 |
2025-01-05 01:40:22 +01:00
2b953c4b75
fix(typos): typos caught more typos :3
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
Some typos where not a typo tho, just a test T-T. This fixes it.
2025-01-04 13:43:53 +01:00
c038b68ecb
chore(deps): lock file maintenance
Some checks failed
/ build (push) Successful in 1m4s
/ check (push) Failing after 37s
/ report-size (push) Successful in 7s
2025-01-04 13:20:20 +01:00
dcba690961
fix(ci): remove update workflow
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
Not needed as renovate fulfills this purpose.
2025-01-04 13:08:55 +01:00
99e887513d
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
2025-01-02 00:00:25 +01:00
4490dfac05
chore: update to axum 0.8
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
This requires updating axum-client-ip too as it depends on axum.
2025-01-01 19:32:15 +01:00
22ab037b6f
fix(renovate): switch automergeStrategy to auto
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
fast-forward triggers a rebase which is not allowed in this repo.
2025-01-01 10:44:48 +01:00
e4451beebf
chore(deps): lock file maintenance
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
2025-01-01 00:00:28 +01:00
6a1feb2612
fix(flake): switch to github ref
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
Switch from a flake ref to a github ref as renovate cannot resolve flake
refs, and they are slightly impure.

This shows some issues with the previous patch to the bind module which
we fix.
2024-12-29 18:19:32 +01:00
2ba6277778
feat(renovate): enable lockFileMaintenance
Some checks failed
/ build (push) Successful in 2s
/ check (push) Failing after 13s
/ report-size (push) Successful in 7s
This allows it to update the flake.lock and Cargo.lock files.
2024-12-29 18:07:36 +01:00
69fde96f67
refactor: setup renovate to manage dependencies
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 6s
/ report-size (push) Successful in 1s
This should make it easier to keep the repo up to date.
2024-12-28 10:08:11 +01:00
f6084449fa
Add renovate.json
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 6s
/ report-size (push) Successful in 1s
2024-12-28 01:07:06 +01:00
e9d5b87ecc
fix(main): add more logging and default to info
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
`leo` fails to run with v0.3.3 so we are adding a bit more logging to
debug the case.

As part of this, change default log level to `info`.
2024-12-26 17:21:27 +01:00
63a7505724
chore(release): prepare for v0.3.3
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 1s
2024-12-22 14:13:14 +01:00
502f7cbcdf
fix(ci): remove tea
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
`tea` doesn't like the forgejo GITHUB_TOKENs and doesn't work.
2024-12-22 13:40:12 +01:00
7bc103e861
chore: generate base changelog
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
2024-12-22 13:21:11 +01:00
2a52e66bc0
feat: add git-cliff to generate changelogs
This should make the project a bit more "official" c:
2024-12-22 13:12:52 +01:00
bd8badac23
fix(webnsupdate): reduce binary size
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 6s
/ report-size (push) Successful in 1s
There is no need to compile for speed of execution, this is not a
contested component.
2024-12-22 00:52:01 +01:00
forgejo-actions
6b4c0c4865 chore: cargo update
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
Locking 31 packages to latest compatible versions
    Updating bytes v1.8.0 -> v1.9.0
    Updating cc v1.2.1 -> v1.2.5
    Updating clap v4.5.21 -> v4.5.23
    Updating clap-verbosity-flag v3.0.0 -> v3.0.2
    Updating clap_builder v4.5.21 -> v4.5.23
    Updating clap_lex v0.7.3 -> v0.7.4
    Updating console v0.15.8 -> v0.15.10
    Updating encode_unicode v0.3.6 -> v1.0.0
    Updating errno v0.3.9 -> v0.3.10
    Removing hermit-abi v0.3.9
    Updating http v1.1.0 -> v1.2.0
    Updating hyper v1.5.1 -> v1.5.2
    Updating itoa v1.0.13 -> v1.0.14
    Updating libc v0.2.164 -> v0.2.169
    Updating miette v7.2.0 -> v7.4.0
    Updating miette-derive v7.2.0 -> v7.4.0
    Updating miniz_oxide v0.8.0 -> v0.8.2
    Updating mio v1.0.2 -> v1.0.3
    Updating rustix v0.38.41 -> v0.38.42
    Updating serde v1.0.215 -> v1.0.216
    Updating serde_derive v1.0.215 -> v1.0.216
    Removing smawk v0.3.2
    Updating socket2 v0.5.7 -> v0.5.8
    Updating supports-color v3.0.1 -> v3.0.2
    Updating supports-hyperlinks v3.0.0 -> v3.1.0
    Updating syn v2.0.89 -> v2.0.90
    Removing sync_wrapper v0.1.2
    Updating terminal_size v0.3.0 -> v0.4.1
    Updating tokio v1.41.1 -> v1.42.0
    Updating tower v0.5.1 -> v0.5.2
    Updating tracing v0.1.40 -> v0.1.41
    Updating tracing-attributes v0.1.27 -> v0.1.28
    Updating tracing-core v0.1.32 -> v0.1.33
    Updating tracing-subscriber v0.3.18 -> v0.3.19
    Removing windows-sys v0.48.0
    Removing windows-targets v0.48.5
    Removing windows_aarch64_gnullvm v0.48.5
    Removing windows_aarch64_msvc v0.48.5
    Removing windows_i686_gnu v0.48.5
    Removing windows_i686_msvc v0.48.5
    Removing windows_x86_64_gnu v0.48.5
    Removing windows_x86_64_gnullvm v0.48.5
    Removing windows_x86_64_msvc v0.48.5
note: pass `--verbose` to see 12 unchanged dependencies behind latest
2024-12-22 00:46:26 +01:00
9f40444846
chore(flake.lock): update inputs
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
Flake lock file updates:

• Updated input 'crane':
    'github:ipetkov/crane/3cb338ce81076ce5e461cf77f7824476addb0e1c' (2024-11-19)
  → 'github:ipetkov/crane/72e2d02dbac80c8c86bf6bf3e785536acf8ee926' (2024-12-21)
• Updated input 'flake-parts':
    'github:hercules-ci/flake-parts/506278e768c2a08bec68eb62932193e341f55c90' (2024-11-01)
  → 'github:hercules-ci/flake-parts/205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9' (2024-12-04)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/23e89b7da85c3640bbc2173fe04f4bd114342367' (2024-11-19)
  → 'github:NixOS/nixpkgs/d3c42f187194c26d9f0309a8ecc469d6c878ce33' (2024-12-17)
• Updated input 'treefmt-nix':
    'github:numtide/treefmt-nix/705df92694af7093dfbb27109ce16d828a79155f' (2024-11-22)
  → 'github:numtide/treefmt-nix/65712f5af67234dad91a5a4baee986a8b62dbf8f' (2024-12-20)
2024-12-22 00:43:41 +01:00
faf09c7de1
feat(ci): generate package size report
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
/ report-size (push) Successful in 2s
We should keep it lean c:
2024-12-22 00:41:52 +01:00
657a877168
chore: bump version to dev
All checks were successful
/ build (push) Successful in 2s
/ check (push) Successful in 7s
preparing for the next dev cycle
2024-11-23 21:26:47 +01:00
ce2692f66d
feat: release new version
All checks were successful
/ build (push) Successful in 1s
/ check (push) Successful in 7s
2024-11-23 21:16:46 +01:00
bea38bc445
fix(clippy): enable more lints and fix issues
All checks were successful
/ build (push) Successful in 3s
/ check (push) Successful in 7s
We also add some more metadata to the Cargo.toml manifest
2024-11-23 21:12:27 +01:00
846a0675d1
refactor: reorganize main.rs
All checks were successful
/ build (push) Successful in 2s
/ check (push) Successful in 8s
2024-11-23 21:01:58 +01:00
750cbbff93
feat: replace axum-auth with tower_http
All checks were successful
/ build (push) Successful in 3s
/ check (push) Successful in 7s
Slightly more involde in the auth code, but it makes the rest of the
application more straight forward.

Fixes #10
2024-11-23 20:39:06 +01:00
60aed649b1
feat: upgrade clap_verbosity_flag
All checks were successful
/ build (push) Successful in 2s
/ check (push) Successful in 7s
Adds tracing support which simplyfies code.
2024-11-23 13:10:48 +01:00
d98c4202f4
feat(ci): check depends on build
All checks were successful
/ build (push) Successful in 2s
/ check (push) Successful in 7s
Because it depends on build, if build runs afterward, nix uses the cache
and no valuable logs are produced.
2024-11-23 13:02:25 +01:00
68cf43c539
chore: update flake inputs
All checks were successful
/ check (push) Successful in 7s
/ build (push) Successful in 2s
• Updated input 'crane':
    'github:ipetkov/crane/498d9f122c413ee1154e8131ace5a35a80d8fa76' (2024-10-27)
  → 'github:ipetkov/crane/3cb338ce81076ce5e461cf77f7824476addb0e1c' (2024-11-19)
• Updated input 'flake-parts':
    'github:hercules-ci/flake-parts/3d04084d54bedc3d6b8b736c70ef449225c361b1' (2024-10-01)
  → 'github:hercules-ci/flake-parts/506278e768c2a08bec68eb62932193e341f55c90' (2024-11-01)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5633bcff0c6162b9e4b5f1264264611e950c8ec7' (2024-10-09)
  → 'github:NixOS/nixpkgs/23e89b7da85c3640bbc2173fe04f4bd114342367' (2024-11-19)
• Updated input 'treefmt-nix':
    'github:numtide/treefmt-nix/aac86347fb5063960eccb19493e0cadcdb4205ca' (2024-10-22)
  → 'github:numtide/treefmt-nix/705df92694af7093dfbb27109ce16d828a79155f' (2024-11-22)
2024-11-23 12:56:55 +01:00
forgejo-actions
82b1078a94 chore: cargo update
All checks were successful
/ check (push) Successful in 7s
/ build (push) Successful in 2s
Locking 25 packages to latest compatible versions
    Updating anstream v0.6.17 -> v0.6.18
    Updating anstyle v1.0.9 -> v1.0.10
    Updating axum v0.7.7 -> v0.7.9
    Updating cc v1.1.31 -> v1.2.1
    Updating clap v4.5.20 -> v4.5.21
    Updating clap-verbosity-flag v2.2.2 -> v2.2.3 (latest: v3.0.0)
    Updating clap_builder v4.5.20 -> v4.5.21
    Updating clap_lex v0.7.2 -> v0.7.3
    Updating hyper v1.5.0 -> v1.5.1
    Updating hyper-util v0.1.9 -> v0.1.10
    Updating insta v1.40.0 -> v1.41.1
    Updating itoa v1.0.11 -> v1.0.13
    Updating libc v0.2.161 -> v0.2.164
    Updating proc-macro2 v1.0.89 -> v1.0.92
    Updating regex-automata v0.4.8 -> v0.4.9
    Updating rustix v0.38.37 -> v0.38.41
    Updating serde v1.0.213 -> v1.0.215
    Updating serde_derive v1.0.213 -> v1.0.215
    Updating serde_json v1.0.132 -> v1.0.133
    Updating syn v2.0.85 -> v2.0.89
    Updating sync_wrapper v1.0.1 -> v1.0.2
    Updating thiserror v1.0.65 -> v1.0.69 (latest: v2.0.3)
    Updating thiserror-impl v1.0.65 -> v1.0.69 (latest: v2.0.3)
    Updating tokio v1.41.0 -> v1.41.1
    Updating unicode-ident v1.0.13 -> v1.0.14
2024-11-23 12:55:08 +01:00
5a9b09bc7e
chore: bump version
All checks were successful
/ check (push) Successful in 7s
/ build (push) Successful in 2s
New dev version
2024-10-28 22:44:51 +01:00
618670512e
fix: overlay was broken T-T
All checks were successful
/ check (push) Successful in 1m22s
/ build (push) Successful in 2s
2024-10-28 22:41:12 +01:00
e61913e5a8
chore: next dev version
All checks were successful
/ check (push) Successful in 6s
/ build (push) Successful in 2s
2024-10-28 22:11:56 +01:00
30 changed files with 2259 additions and 1171 deletions

14
.editorconfig Normal file
View file

@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
[*.{nix,toml,json}]
indent_style = space
indent_size = 2
[*.rs]
indent_style = space
indent_size = 4

View file

@ -1,14 +1,41 @@
on: [push]
jobs:
check:
check-renovaterc:
runs-on: nixos
steps:
- uses: https://git.salame.cl/actions/checkout@v4
- run: nix --version
- run: nix flake check --keep-going --verbose --print-build-logs
- uses: https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Validate renovaterc
run: |
nix --version
nix shell nixpkgs#renovate --command renovate-config-validator
build:
runs-on: nixos
steps:
- uses: https://git.salame.cl/actions/checkout@v4
- uses: https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Build Package
run: |
nix --version
nix build --print-build-logs .#
test:
needs: build # we use the built binaries in the checks
runs-on: nixos
steps:
- uses: https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Run tests
run: |
nix --version
nix-fast-build --max-jobs 2 --no-nom --skip-cached --no-link \
--flake ".#checks.$(nix eval --raw --impure --expr builtins.currentSystem)"
report-size:
runs-on: nixos
needs: build
steps:
- uses: https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- run: nix --version
- run: nix build --print-build-logs .#
- name: Generate size report
uses: "https://git.salame.cl/jalil/nix-flake-outputs-size@5c40a31e3e2ed0ea28f8ba68deca41d05fdf2e71" # main
with:
comment-on-pr: ${{ github.ref_name != 'main' }}
generate-artifact: ${{ github.ref_name == 'main' }}
do-comparison: true
job-name: report-size

View file

@ -1,62 +0,0 @@
on:
workflow_dispatch:
schedule:
# 03:42 on Saturdays
- cron: '42 3 * * 6'
env:
PR_TITLE: Weekly `cargo update` of dependencies
PR_MESSAGE: |
Automation to keep dependencies in `Cargo.lock` current.
The following is the output from `cargo update`:
COMMIT_MESSAGE: "chore: cargo update \n\n"
jobs:
update-cargo:
runs-on: nixos
env:
BRANCH_NAME: cargo-update
steps:
- uses: https://git.salame.cl/actions/checkout@v4
- run: nix --version
- run: nix run .#cargo-update
- name: craft PR body and commit message
run: |
set -xeuo pipefail
echo "${COMMIT_MESSAGE}" > commit.txt
cat cargo_update.log >> commit.txt
echo "${PR_MESSAGE}" > body.md
echo '```txt' >> body.md
cat cargo_update.log >> body.md
echo '```' >> body.md
- name: commit
run: |
set -xeuo pipefail
git config user.name forgejo-actions
git config user.email forgejo-actions@salame.cl
git switch --force-create "$BRANCH_NAME"
git add ./Cargo.lock
DIFF="$(git diff --staged)"
if [[ "$DIFF" == "" ]]; then
echo "Cargo.lock was not changed, bailing out and not making a PR"
exit 1
fi
git commit --no-verify --file=commit.txt
- name: push
run: |
set -xeuo pipefail
git push --no-verify --force --set-upstream origin "$BRANCH_NAME"
- name: open new pull request
env:
# We have to use a Personal Access Token (PAT) here.
# PRs opened from a workflow using the standard `GITHUB_TOKEN` in GitHub Actions
# do not automatically trigger more workflows:
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow
# GITHUB_TOKEN: ${{ secrets.DEPS_UPDATER_GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -xeuo pipefail
tea login add --token "$GITHUB_TOKEN"
tea pr create --title "${PR_TITLE}" --description "$(cat body.md)" --repo "$GITHUB_REPOSITORY"

44
.renovaterc.json Normal file
View file

@ -0,0 +1,44 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"assignees": [
"jalil"
],
"automerge": true,
"automergeStrategy": "auto",
"automergeType": "pr",
"commitBodyTable": true,
"dependencyDashboard": true,
"extends": [
"config:best-practices"
],
"prCreation": "immediate",
"cargo": {
"commitMessageTopic": "Rust crate {{depName}}",
"fileMatch": [
"(^|/)Cargo\\.toml$"
],
"versioning": "cargo",
"enabled": true
},
"nix": {
"fileMatch": [
"(^|/)flake\\.nix$"
],
"commitMessageTopic": "nixpkgs",
"commitMessageExtra": "to {{newValue}}",
"enabled": true
},
"lockFileMaintenance": {
"enabled": true,
"recreateWhen": "always",
"rebaseWhen": "behind-base-branch",
"branchTopic": "lock-file-maintenance",
"commitMessageAction": "Lock file maintenance",
"schedule": [
"* 22 * * *"
]
},
"automergeSchedule": [
"* 23 * * *"
]
}

139
CHANGELOG.md Normal file
View file

@ -0,0 +1,139 @@
# Changelog
All notable changes to this project will be documented in this file.
## [0.3.6] - 2025-01-26
### 🚀 Features
- *(webnsupdate)* Allow running in IPv4/6 only mode
- *(module)* Add option for setting --ip-type
- *(flake)* Add tests for new allowedIPVersion option
## [0.3.5] - 2025-01-23
### 🚀 Features
- *(renovate)* Enable lockFileMaintenance
- *(webnsupdate)* Add handling for multiple IPs
- Tune compilation for size
- *(tests)* Add nginx integration test
### 🐛 Bug Fixes
- *(flake)* Switch to github ref
- *(renovate)* Switch automergeStrategy to auto
- *(ci)* Remove update workflow
- *(typos)* Typos caught more typos :3
- *(renovate)* Branch creation before automerge
- *(renovaterc)* Invalid cron syntax
- *(deps)* Update rust crate clap to v4.5.24
- *(deps)* Update rust crate tokio to v1.43.0
- *(deps)* Update rust crate clap to v4.5.25
- *(deps)* Update rust crate clap to v4.5.26
- *(flake)* Switch overlay to callPackage
- *(deps)* Update rust crate clap to v4.5.27
- *(deps)* Update rust crate axum to v0.8.2
- *(module)* Test both IPv4 and IPv6
### 🚜 Refactor
- Setup renovate to manage dependencies
### ⚙️ Miscellaneous Tasks
- Update to axum 0.8
- Parallelize checks
## [0.3.4] - 2024-12-26
### 🐛 Bug Fixes
- *(main)* Add more logging and default to info
## [0.3.3] - 2024-12-22
### 🚀 Features
- *(ci)* Generate package size report
- Add git-cliff to generate changelogs
### 🐛 Bug Fixes
- *(webnsupdate)* Reduce binary size
- *(ci)* Remove tea
### ⚙️ Miscellaneous Tasks
- *(flake.lock)* Update inputs
- Cargo update
- Generate base changelog
## [0.3.2] - 2024-11-23
### 🚀 Features
- *(ci)* Check depends on build
- Upgrade clap_verbosity_flag
- Replace axum-auth with tower_http
- Release new version
### 🐛 Bug Fixes
- *(clippy)* Enable more lints and fix issues
### 🚜 Refactor
- Reorganize main.rs
### ⚙️ Miscellaneous Tasks
- Cargo update
- Update flake inputs
## [0.3.1] - 2024-10-28
### 🐛 Bug Fixes
- Overlay was broken T-T
### ⚙️ Miscellaneous Tasks
- Next dev version
## [0.3.0] - 2024-10-28
### 🚀 Features
- *(ci)* Auto-update rust deps
- Refactor and add ip saving
- Add -v verbosity flag
- Use treefmt-nix and split up flake.nix
- Add NixOS VM tests
- Switch to crane
### 🐛 Bug Fixes
- *(fmt)* Use nixfmt-rfc-style
- *(default.nix)* Small issues here and there
- *(ci)* Do not use a name when logging in
### 🚜 Refactor
- *(flake)* Use flake-parts
### ⚙️ Miscellaneous Tasks
- Updarte deps
- *(flake.lock)* Update inputs
- Cargo update
- Cargo update
- Cargo update
## [0.2.0] - 2024-06-02
### 💼 Other
- Init at version 0.1.0
<!-- generated by git-cliff -->

533
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,35 +1,48 @@
cargo-features = ["codegen-backend"]
[package]
description = "An HTTP server using HTTP basic auth to make secure calls to nsupdate"
name = "webnsupdate"
version = "0.3.0"
version = "0.3.6"
edition = "2021"
license-file = "LICENSE"
readme = "README.md"
keywords = ["dns", "dyndns", "dynamic-ip"]
categories = ["networking", "dns", "dyndns"]
repository = "https://github.com/jalil-salame/webnsupdate"
[lints.clippy]
cargo = { level = "warn", priority = -2 }
multiple_crate_versions = "allow"
pedantic = { level = "warn", priority = -1 }
[dependencies]
axum = "0.7"
axum-auth = { version = "0.7", default-features = false, features = [
"auth-basic",
] }
axum-client-ip = "0.6"
axum = "0.8"
axum-client-ip = "1.0"
base64 = "0.22"
clap = { version = "4", features = ["derive", "env"] }
clap-verbosity-flag = "2"
clap-verbosity-flag = { version = "3", default-features = false, features = [
"tracing",
] }
http = "1"
humantime = "2.2.0"
miette = { version = "7", features = ["fancy"] }
ring = { version = "0.17", features = ["std"] }
tokio = { version = "1", features = [
"macros",
"rt",
"process",
"io-util",
] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
tokio = { version = "1", features = ["macros", "rt", "process", "io-util"] }
tower-http = { version = "0.6", features = ["validate-request"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[dev-dependencies]
insta = "1"
insta = { version = "=1.42.2", features = ["json"] }
[profile.release]
opt-level = "s"
panic = "abort"
lto = true
strip = true
codegen-units = 1
[profile.dev]
debug = 0
codegen-backend = "cranelift"

85
cliff.toml Normal file
View file

@ -0,0 +1,85 @@
# git-cliff ~ default configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.
[changelog]
# template for the changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing s
trim = true
# postprocessors
postprocessors = [
# { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL
]
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# Replace issue numbers
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore: bump version", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = ".*", group = "<!-- 10 -->💼 Other" },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"

37
default.nix Normal file
View file

@ -0,0 +1,37 @@
{
pkgs ?
(builtins.getFlake (builtins.toString ./.)).inputs.nixpkgs.legacyPackages.${builtins.currentSystem},
lib ? pkgs.lib,
crane ? (builtins.getFlake (builtins.toString ./.)).inputs.crane,
pkgSrc ? ./.,
mold ? pkgs.mold,
}:
let
craneLib = crane.mkLib pkgs;
src = craneLib.cleanCargoSource pkgSrc;
commonArgs = {
inherit src;
strictDeps = true;
doCheck = false; # tests will be run in the `checks` derivation
NEXTEST_HIDE_PROGRESS_BAR = 1;
NEXTEST_FAILURE_OUTPUT = "immediate-final";
nativeBuildInputs = [ mold ];
meta = {
license = lib.licenses.mit;
homepage = "https://github.com/jalil-salame/webnsupdate";
mainProgram = "webnsupdate";
};
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
in
craneLib.buildPackage (
lib.mergeAttrsList [
commonArgs
{ inherit cargoArtifacts; }
]
)

View file

@ -3,11 +3,18 @@
imports = [
inputs.treefmt-nix.flakeModule
./package.nix
./overlay.nix
./module.nix
./tests.nix
];
flake.nixosModules =
let
webnsupdate = ../module.nix;
in
{
default = webnsupdate;
inherit webnsupdate;
};
perSystem =
{ pkgs, ... }:
{
@ -23,10 +30,11 @@
};
devShells.default = pkgs.mkShellNoCC {
packages = [
pkgs.cargo-insta
pkgs.cargo-udeps
pkgs.mold
packages = with pkgs; [
cargo-insta
cargo-udeps
mold
git-cliff
];
};
};

View file

@ -1,196 +0,0 @@
let
module =
{
lib,
pkgs,
config,
...
}:
let
cfg = config.services.webnsupdate;
inherit (lib)
mkOption
mkEnableOption
mkPackageOption
types
;
in
{
options.services.webnsupdate = mkOption {
description = "An HTTP server for nsupdate.";
default = { };
type = types.submodule {
options = {
enable = mkEnableOption "webnsupdate";
extraArgs = mkOption {
description = ''
Extra arguments to be passed to the webnsupdate server command.
'';
type = types.listOf types.str;
default = [ ];
example = [ "--ip-source" ];
};
package = mkPackageOption pkgs "webnsupdate" { };
bindIp = mkOption {
description = ''
IP address to bind to.
Setting it to anything other than localhost is very insecure as
`webnsupdate` only supports plain HTTP and should always be behind a
reverse proxy.
'';
type = types.str;
default = "localhost";
example = "0.0.0.0";
};
bindPort = mkOption {
description = "Port to bind to.";
type = types.port;
default = 5353;
};
passwordFile = mkOption {
description = ''
The file where the password is stored.
This file can be created by running `webnsupdate mkpasswd $USERNAME $PASSWORD`.
'';
type = types.path;
example = "/secrets/webnsupdate.pass";
};
keyFile = mkOption {
description = ''
The TSIG key that `nsupdate` should use.
This file will be passed to `nsupdate` through the `-k` option, so look
at `man 8 nsupdate` for information on the key's format.
'';
type = types.path;
example = "/secrets/webnsupdate.key";
};
ttl = mkOption {
description = "The TTL that should be set on the zone records created by `nsupdate`.";
type = types.ints.positive;
default = 60;
example = 3600;
};
records = mkOption {
description = ''
The fqdn of records that should be updated.
Empty lines will be ignored, but whitespace will not be.
'';
type = types.nullOr types.lines;
default = null;
example = ''
example.com.
example.org.
ci.example.org.
'';
};
recordsFile = mkOption {
description = ''
The fqdn of records that should be updated.
Empty lines will be ignored, but whitespace will not be.
'';
type = types.nullOr types.path;
default = null;
example = "/secrets/webnsupdate.records";
};
user = mkOption {
description = "The user to run as.";
type = types.str;
default = "named";
};
group = mkOption {
description = "The group to run as.";
type = types.str;
default = "named";
};
};
};
};
config =
let
recordsFile =
if cfg.recordsFile != null then cfg.recordsFile else pkgs.writeText "webnsrecords" cfg.records;
args = lib.strings.escapeShellArgs (
[
"--records"
recordsFile
"--key-file"
cfg.keyFile
"--password-file"
cfg.passwordFile
"--address"
cfg.bindIp
"--port"
(builtins.toString cfg.bindPort)
"--ttl"
(builtins.toString cfg.ttl)
"--data-dir=%S/webnsupdate"
]
++ cfg.extraArgs
);
cmd = "${lib.getExe cfg.package} ${args}";
in
lib.mkIf cfg.enable {
# warnings =
# lib.optional (!config.services.bind.enable) "`webnsupdate` is expected to be used alongside `bind`. This is an unsopported configuration.";
assertions = [
{
assertion =
(cfg.records != null || cfg.recordsFile != null)
&& !(cfg.records != null && cfg.recordsFile != null);
message = "Exactly one of `services.webnsupdate.records` and `services.webnsupdate.recordsFile` must be set.";
}
];
systemd.services.webnsupdate = {
description = "Web interface for nsupdate.";
wantedBy = [ "multi-user.target" ];
after = [
"network.target"
"bind.service"
];
preStart = "${cmd} verify";
path = [ pkgs.dig ];
startLimitIntervalSec = 60;
serviceConfig = {
ExecStart = [ cmd ];
Type = "exec";
Restart = "on-failure";
RestartSec = "10s";
# User and group
User = cfg.user;
Group = cfg.group;
# Runtime directory and mode
RuntimeDirectory = "webnsupdate";
RuntimeDirectoryMode = "0750";
# Cache directory and mode
CacheDirectory = "webnsupdate";
CacheDirectoryMode = "0750";
# Logs directory and mode
LogsDirectory = "webnsupdate";
LogsDirectoryMode = "0750";
# State directory and mode
StateDirectory = "webnsupdate";
StateDirectoryMode = "0750";
# New file permissions
UMask = "0027";
# Security
NoNewPrivileges = true;
ProtectHome = true;
};
};
};
};
in
{
flake.nixosModules = {
default = module;
webnsupdate = module;
};
}

View file

@ -1,5 +0,0 @@
{
flake = {
overlays.default = _final: prev: { webnsupdate = prev.callPackage ../default.nix { }; };
};
}

View file

@ -1,7 +1,14 @@
{ inputs, lib, ... }:
{ inputs, ... }:
{
flake.overlays.default = final: prev: {
webnsupdate = prev.callPackage ../default.nix {
inherit (inputs) crane;
pkgSrc = inputs.self;
};
};
perSystem =
{ pkgs, ... }:
{ pkgs, lib, ... }:
let
craneLib = inputs.crane.mkLib pkgs;
src = craneLib.cleanCargoSource inputs.self;
@ -24,46 +31,30 @@
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
webnsupdate = craneLib.buildPackage (
lib.mergeAttrsList [
commonArgs
{ inherit cargoArtifacts; }
]
);
withArtifacts = lib.mergeAttrsList [
commonArgs
{ inherit cargoArtifacts; }
];
webnsupdate = pkgs.callPackage ../default.nix {
inherit (inputs) crane;
pkgSrc = inputs.self;
};
in
{
checks = {
nextest = craneLib.cargoNextest withArtifacts;
clippy = craneLib.cargoClippy (
lib.mergeAttrsList [
commonArgs
{
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
}
]
);
nextest = craneLib.cargoNextest (
lib.mergeAttrsList [
commonArgs
{ inherit cargoArtifacts; }
withArtifacts
{ cargoClippyExtraArgs = "--all-targets -- --deny warnings"; }
]
);
};
packages = {
inherit webnsupdate;
inherit (pkgs) git-cliff;
default = webnsupdate;
cargo-update = pkgs.writeShellApplication {
name = "cargo-update-lockfile";
runtimeInputs = with pkgs; [
cargo
gnused
];
text = ''
CARGO_TERM_COLOR=never cargo update 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log
'';
};
};
};
}

View file

@ -6,138 +6,337 @@
checks =
let
testDomain = "webnstest.example";
dynamicZonesDir = "/var/lib/named/zones";
zoneFile = pkgs.writeText "${testDomain}.zoneinfo" ''
$ORIGIN .
$TTL 60 ; 1 minute
${testDomain} IN SOA ns1.${testDomain}. admin.${testDomain}. (
1 ; serial
21600 ; refresh (6 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
86400) ; negative caching TTL (1 day)
lastIPPath = "/var/lib/webnsupdate/last-ip.json";
IN NS ns1.${testDomain}.
zoneFile = pkgs.writeText "${testDomain}.zoneinfo" ''
$TTL 600 ; 10 minutes
$ORIGIN ${testDomain}.
${testDomain}. IN A 127.0.0.1
${testDomain}. IN AAAA ::1
ns1 IN A 127.0.0.1
ns1 IN AAAA ::1
nsupdate IN A 127.0.0.1
nsupdate IN AAAA ::1
@ IN SOA ns1.${testDomain}. admin.${testDomain}. (
1 ; serial
6h ; refresh
1h ; retry
1w ; expire
1d) ; negative caching TTL
IN NS ns1.${testDomain}.
@ IN A 127.0.0.1
ns1 IN A 127.0.0.1
nsupdate IN A 127.0.0.1
@ IN AAAA ::1
ns1 IN AAAA ::1
nsupdate IN AAAA ::1
'';
webnsupdate-machine = {
imports = [ self.nixosModules.webnsupdate ];
config = {
environment.systemPackages = [
pkgs.dig
pkgs.curl
];
services = {
webnsupdate = {
enable = true;
bindIp = "127.0.0.1";
keyFile = "/etc/bind/rndc.key";
# test:test (user:password)
passwordFile = pkgs.writeText "webnsupdate.pass" "FQoNmuU1BKfg8qsU96F6bK5ykp2b0SLe3ZpB3nbtfZA";
package = self'.packages.webnsupdate;
extraArgs = [
"-vvv" # debug messages
"--ip-source=ConnectInfo"
];
records = ''
test1.${testDomain}.
test2.${testDomain}.
test3.${testDomain}.
'';
};
bind = {
enable = true;
zones.${testDomain} = {
master = true;
file = "${dynamicZonesDir}/${testDomain}";
extraConfig = ''
allow-update { key rndc-key; };
'';
};
};
bindDynamicZone =
{ config, ... }:
let
bindCfg = config.services.bind;
bindData = bindCfg.directory;
dynamicZonesDir = "${bindData}/zones";
in
{
services.bind.zones.${testDomain} = {
master = true;
file = "${dynamicZonesDir}/${testDomain}";
extraConfig = ''
allow-update { key rndc-key; };
'';
};
systemd.services.bind.preStart = ''
# shellcheck disable=SC2211,SC1127
rm -f ${dynamicZonesDir}/* # reset dynamic zones
${pkgs.coreutils}/bin/mkdir -m 0755 -p ${dynamicZonesDir}
chown "named" ${dynamicZonesDir}
chown "named" /var/lib/named
# create a dynamic zones dir
mkdir -m 0755 -p ${dynamicZonesDir}
# copy dynamic zone's file to the dynamic zones dir
cp ${zoneFile} ${dynamicZonesDir}/${testDomain}
'';
};
webnsupdate-ipv4-machine =
{ lib, ... }:
{
imports = [
bindDynamicZone
self.nixosModules.webnsupdate
];
config = {
environment.systemPackages = [
pkgs.dig
pkgs.curl
];
services = {
bind.enable = true;
webnsupdate = {
enable = true;
package = self'.packages.webnsupdate;
extraArgs = [ "-vvv" ]; # debug messages
settings = {
address = lib.mkDefault "127.0.0.1:5353";
key_file = "/etc/bind/rndc.key";
password_file = pkgs.writeText "webnsupdate.pass" "FQoNmuU1BKfg8qsU96F6bK5ykp2b0SLe3ZpB3nbtfZA"; # test:test
ip_source = lib.mkDefault "ConnectInfo";
records = [
"test1.${testDomain}."
"test2.${testDomain}."
"test3.${testDomain}."
];
};
};
};
};
};
webnsupdate-ipv6-machine = {
imports = [
webnsupdate-ipv4-machine
];
config.services.webnsupdate.settings.address = "[::1]:5353";
};
webnsupdate-nginx-machine =
{ lib, config, ... }:
{
imports = [
webnsupdate-ipv4-machine
];
config.services = {
# Use default IP Source
webnsupdate.settings.ip_source = "RightmostXForwardedFor";
nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts.webnsupdate.locations."/".proxyPass =
"http://${config.services.webnsupdate.settings.address}";
};
};
};
webnsupdate-ipv4-only-machine = {
imports = [ webnsupdate-nginx-machine ];
config.services.webnsupdate.settings.ip_type = "Ipv4Only";
};
webnsupdate-ipv6-only-machine = {
imports = [ webnsupdate-nginx-machine ];
config.services.webnsupdate.settings.ip_type = "Ipv6Only";
};
# "A" for IPv4, "AAAA" for IPv6, "ANY" for any
testTemplate =
{
ipv4 ? false,
ipv6 ? false,
nginx ? false,
exclusive ? false,
}:
if exclusive && (ipv4 == ipv6) then
builtins.throw "exclusive means one of ipv4 or ipv6 must be set, but not both"
else
''
IPV4: bool = ${if ipv4 then "True" else "False"}
IPV6: bool = ${if ipv6 then "True" else "False"}
NGINX: bool = ${if nginx then "True" else "False"}
EXCLUSIVE: bool = ${if exclusive then "True" else "False"}
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
CURL: str = "curl --fail --no-progress-meter --show-error"
machine.start(allow_reboot=True)
machine.wait_for_unit("bind.service")
machine.wait_for_unit("webnsupdate.service")
STATIC_DOMAINS: list[str] = ["${testDomain}", "ns1.${testDomain}", "nsupdate.${testDomain}"]
DYNAMIC_DOMAINS: list[str] = ["test1.${testDomain}", "test2.${testDomain}", "test3.${testDomain}"]
def dig_cmd(domain: str, record: str, ip: str | None) -> tuple[str, str]:
match_ip = "" if ip is None else f"\\s\\+600\\s\\+IN\\s\\+{record}\\s\\+{ip}$"
return f"dig @localhost {record} {domain} +noall +answer", f"grep '^{domain}.{match_ip}'"
def curl_cmd(domain: str, identity: str, path: str, query: dict[str, str]) -> str:
from urllib.parse import urlencode
q= f"?{urlencode(query)}" if query else ""
return f"{CURL} -u {identity} -X GET 'http://{domain}{"" if NGINX else ":5353"}/{path}{q}'"
def domain_available(domain: str, record: str, ip: str | None=None):
dig, grep = dig_cmd(domain, record, ip)
rc, output = machine.execute(dig)
print(f"{dig}[{rc}]: {output}")
machine.succeed(f"{dig} | {grep}")
def domain_missing(domain: str, record: str, ip: str | None=None):
dig, grep = dig_cmd(domain, record, ip)
rc, output = machine.execute(dig)
print(f"{dig}[{rc}]: {output}")
machine.fail(f"{dig} | {grep}")
def update_records(domain: str="localhost", /, *, path: str="update", **kwargs):
machine.succeed(curl_cmd(domain, "test:test", path, kwargs))
machine.succeed("cat ${lastIPPath}")
def update_records_fail(domain: str="localhost", /, *, identity: str="test:test", path: str="update", **kwargs):
machine.fail(curl_cmd(domain, identity, path, kwargs))
machine.fail("cat ${lastIPPath}")
def invalid_update(domain: str="localhost"):
update_records_fail(domain, identity="bad_user:test")
update_records_fail(domain, identity="test:bad_pass")
# Tests
with subtest("static DNS records are available"):
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
for domain in STATIC_DOMAINS:
domain_available(domain, "A", "127.0.0.1") # IPv4
domain_available(domain, "AAAA", "::1") # IPv6
with subtest("dynamic DNS records are missing"):
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
for domain in DYNAMIC_DOMAINS:
domain_missing(domain, "A") # IPv4
domain_missing(domain, "AAAA") # IPv6
with subtest("invalid auth fails to update records"):
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
invalid_update()
for domain in DYNAMIC_DOMAINS:
domain_missing(domain, "A") # IPv4
domain_missing(domain, "AAAA") # IPv6
if EXCLUSIVE:
with subtest("exclusive IP version fails to update with invalid version"):
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
if IPV6:
update_records_fail("127.0.0.1")
if IPV4:
update_records_fail("[::1]")
with subtest("valid auth updates records"):
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
if IPV4:
update_records("127.0.0.1")
if IPV6:
update_records("[::1]")
for domain in DYNAMIC_DOMAINS:
if IPV4:
domain_available(domain, "A", "127.0.0.1")
elif IPV6 and EXCLUSIVE:
domain_missing(domain, "A")
if IPV6:
domain_available(domain, "AAAA", "::1")
elif IPV4 and EXCLUSIVE:
domain_missing(domain, "AAAA")
with subtest("valid auth fritzbox compatible updates records"):
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
if IPV4 and IPV6:
update_records("127.0.0.1", domain="test", ipv4="1.2.3.4", ipv6="::1234")
elif IPV4:
update_records("127.0.0.1", ipv4="1.2.3.4", ipv6="")
elif IPV6:
update_records("[::1]", ipv4="", ipv6="::1234")
for domain in DYNAMIC_DOMAINS:
if IPV4:
domain_available(domain, "A", "1.2.3.4")
elif IPV6 and EXCLUSIVE:
domain_missing(domain, "A")
if IPV6:
domain_available(domain, "AAAA", "::1234")
elif IPV4 and EXCLUSIVE:
domain_missing(domain, "AAAA")
with subtest("valid auth replaces records"):
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
if IPV4:
update_records("127.0.0.1")
if IPV6:
update_records("[::1]")
for domain in DYNAMIC_DOMAINS:
if IPV4:
domain_available(domain, "A", "127.0.0.1")
elif IPV6 and EXCLUSIVE:
domain_missing(domain, "A")
if IPV6:
domain_available(domain, "AAAA", "::1")
elif IPV4 and EXCLUSIVE:
domain_missing(domain, "AAAA")
machine.reboot()
machine.succeed("cat ${lastIPPath}")
machine.wait_for_unit("webnsupdate.service")
machine.succeed("cat ${lastIPPath}")
with subtest("static DNS records are available after reboot"):
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
for domain in STATIC_DOMAINS:
domain_available(domain, "A", "127.0.0.1") # IPv4
domain_available(domain, "AAAA", "::1") # IPv6
with subtest("dynamic DNS records are available after reboot"):
print(f"{IPV4=} {IPV6=} {EXCLUSIVE=}")
for domain in DYNAMIC_DOMAINS:
if IPV4:
domain_available(domain, "A", "127.0.0.1")
elif IPV6 and EXCLUSIVE:
domain_missing(domain, "A")
if IPV6:
domain_available(domain, "AAAA", "::1")
elif IPV4 and EXCLUSIVE:
domain_missing(domain, "AAAA")
'';
in
{
module-test = pkgs.testers.runNixOSTest {
name = "webnsupdate-module";
nodes.machine = webnsupdate-machine;
testScript = ''
machine.start(allow_reboot=True)
machine.wait_for_unit("webnsupdate.service")
# ensure base DNS records area available
with subtest("query base DNS records"):
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
# ensure webnsupdate managed records are missing
with subtest("query webnsupdate DNS records (fail)"):
machine.fail("dig @127.0.0.1 test1.${testDomain} | grep ^test1.${testDomain}")
machine.fail("dig @127.0.0.1 test2.${testDomain} | grep ^test2.${testDomain}")
machine.fail("dig @127.0.0.1 test3.${testDomain} | grep ^test3.${testDomain}")
with subtest("update webnsupdate DNS records (invalid auth)"):
machine.fail("curl --fail --silent -u test1:test1 -X GET http://localhost:5353/update")
machine.fail("cat /var/lib/webnsupdate/last-ip") # no last-ip set yet
# ensure webnsupdate managed records are missing
with subtest("query webnsupdate DNS records (fail)"):
machine.fail("dig @127.0.0.1 test1.${testDomain} | grep ^test1.${testDomain}")
machine.fail("dig @127.0.0.1 test2.${testDomain} | grep ^test2.${testDomain}")
machine.fail("dig @127.0.0.1 test3.${testDomain} | grep ^test3.${testDomain}")
with subtest("update webnsupdate DNS records (valid auth)"):
machine.succeed("curl --fail --silent -u test:test -X GET http://localhost:5353/update")
machine.succeed("cat /var/lib/webnsupdate/last-ip")
# ensure webnsupdate managed records are available
with subtest("query webnsupdate DNS records (succeed)"):
machine.succeed("dig @127.0.0.1 test1.${testDomain} | grep ^test1.${testDomain}")
machine.succeed("dig @127.0.0.1 test2.${testDomain} | grep ^test2.${testDomain}")
machine.succeed("dig @127.0.0.1 test3.${testDomain} | grep ^test3.${testDomain}")
machine.reboot()
machine.succeed("cat /var/lib/webnsupdate/last-ip")
machine.wait_for_unit("webnsupdate.service")
machine.succeed("cat /var/lib/webnsupdate/last-ip")
# ensure base DNS records area available after a reboot
with subtest("query base DNS records"):
machine.succeed("dig @127.0.0.1 ${testDomain} | grep ^${testDomain}")
machine.succeed("dig @127.0.0.1 ns1.${testDomain} | grep ^ns1.${testDomain}")
machine.succeed("dig @127.0.0.1 nsupdate.${testDomain} | grep ^nsupdate.${testDomain}")
# ensure webnsupdate managed records are available after a reboot
with subtest("query webnsupdate DNS records (succeed)"):
machine.succeed("dig @127.0.0.1 test1.${testDomain} | grep ^test1.${testDomain}")
machine.succeed("dig @127.0.0.1 test2.${testDomain} | grep ^test2.${testDomain}")
machine.succeed("dig @127.0.0.1 test3.${testDomain} | grep ^test3.${testDomain}")
'';
module-ipv4-test = pkgs.testers.nixosTest {
name = "webnsupdate-ipv4-module";
nodes.machine = webnsupdate-ipv4-machine;
testScript = testTemplate { ipv4 = true; };
};
module-ipv6-test = pkgs.testers.nixosTest {
name = "webnsupdate-ipv6-module";
nodes.machine = webnsupdate-ipv6-machine;
testScript = testTemplate { ipv6 = true; };
};
module-nginx-test = pkgs.testers.nixosTest {
name = "webnsupdate-nginx-module";
nodes.machine = webnsupdate-nginx-machine;
testScript = testTemplate {
ipv4 = true;
ipv6 = true;
nginx = true;
};
};
module-ipv4-only-test = pkgs.testers.nixosTest {
name = "webnsupdate-ipv4-only-module";
nodes.machine = webnsupdate-ipv4-only-machine;
testScript = testTemplate {
ipv4 = true;
nginx = true;
exclusive = true;
};
};
module-ipv6-only-test = pkgs.testers.nixosTest {
name = "webnsupdate-ipv6-only-module";
nodes.machine = webnsupdate-ipv6-only-machine;
testScript = testTemplate {
ipv6 = true;
nginx = true;
exclusive = true;
};
};
};
};

29
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": {
"crane": {
"locked": {
"lastModified": 1730060262,
"narHash": "sha256-RMgSVkZ9H03sxC+Vh4jxtLTCzSjPq18UWpiM0gq6shQ=",
"lastModified": 1743700120,
"narHash": "sha256-8BjG/P0xnuCyVOXlYRwdI1B8nVtyYLf3oDwPSimqREY=",
"owner": "ipetkov",
"repo": "crane",
"rev": "498d9f122c413ee1154e8131ace5a35a80d8fa76",
"rev": "e316f19ee058e6db50075115783be57ac549c389",
"type": "github"
},
"original": {
@ -22,11 +22,11 @@
]
},
"locked": {
"lastModified": 1727826117,
"narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=",
"lastModified": 1743550720,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1",
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github"
},
"original": {
@ -37,17 +37,18 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1728492678,
"narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=",
"lastModified": 1743583204,
"narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7",
"rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434",
"type": "github"
},
"original": {
"id": "nixpkgs",
"owner": "NixOS",
"ref": "nixos-unstable",
"type": "indirect"
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
@ -81,11 +82,11 @@
]
},
"locked": {
"lastModified": 1729613947,
"narHash": "sha256-XGOvuIPW1XRfPgHtGYXd5MAmJzZtOuwlfKDgxX5KT3s=",
"lastModified": 1743677901,
"narHash": "sha256-eWZln+k+L/VHO69tUTzEmgeDWNQNKIpSUa9nqQgBrSE=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "aac86347fb5063960eccb19493e0cadcdb4205ca",
"rev": "57dabe2a6255bd6165b2437ff6c2d1f6ee78421a",
"type": "github"
},
"original": {

View file

@ -6,7 +6,7 @@
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
nixpkgs.url = "nixpkgs/nixos-unstable";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default";
treefmt-nix = {
url = "github:numtide/treefmt-nix";

2
justfile Normal file
View file

@ -0,0 +1,2 @@
changelog version:
git cliff --unreleased --prepend=CHANGELOG.md --tag='{{ version }}'

162
module.nix Normal file
View file

@ -0,0 +1,162 @@
{ lib, pkgs, ... }@args:
let
cfg = args.config.services.webnsupdate;
inherit (lib)
mkOption
mkEnableOption
mkPackageOption
types
;
format = pkgs.formats.json { };
in
{
options.services.webnsupdate = mkOption {
description = "An HTTP server for nsupdate.";
default = { };
type = types.submodule {
options = {
enable = mkEnableOption "webnsupdate";
extraArgs = mkOption {
description = ''
Extra arguments to be passed to the webnsupdate server command.
'';
type = types.listOf types.str;
default = [ ];
example = [ "--ip-source" ];
};
package = mkPackageOption pkgs "webnsupdate" { };
settings = mkOption {
description = "The webnsupdate JSON configuration";
default = { };
type = types.submodule {
freeformType = format.type;
options = {
address = mkOption {
description = ''
IP address and port to bind to.
Setting it to anything other than localhost is very
insecure as `webnsupdate` only supports plain HTTP and
should always be behind a reverse proxy.
'';
type = types.str;
default = "127.0.0.1:5353";
example = "[::1]:5353";
};
ip_type = mkOption {
description = ''The allowed IP versions to accept updates from.'';
type = types.enum [
"Both"
"Ipv4Only"
"Ipv6Only"
];
default = "Both";
example = "Ipv4Only";
};
password_file = mkOption {
description = ''
The file where the password is stored.
This file can be created by running `webnsupdate mkpasswd $USERNAME $PASSWORD`.
'';
type = types.path;
example = "/secrets/webnsupdate.pass";
};
key_file = mkOption {
description = ''
The TSIG key that `nsupdate` should use.
This file will be passed to `nsupdate` through the `-k` option, so look
at `man 8 nsupdate` for information on the key's format.
'';
type = types.path;
example = "/secrets/webnsupdate.key";
};
ttl = mkOption {
description = "The TTL that should be set on the zone records created by `nsupdate`.";
default = "10m";
example = "60s";
type = types.str;
};
records = mkOption {
description = ''
The fqdn of records that should be updated.
Empty lines will be ignored, but whitespace will not be.
'';
type = types.listOf types.str;
default = [ ];
example = [
"example.com."
"example.org."
"ci.example.org."
];
};
};
};
};
user = mkOption {
description = "The user to run as.";
type = types.str;
default = "named";
};
group = mkOption {
description = "The group to run as.";
type = types.str;
default = "named";
};
};
};
};
config =
let
configFile = format.generate "webnsupdate.json" cfg.settings;
args = lib.strings.escapeShellArgs ([ "--config=${configFile}" ] ++ cfg.extraArgs);
cmd = "${lib.getExe cfg.package} ${args}";
in
lib.mkIf cfg.enable {
# FIXME: re-enable once I stop using the patched version of bind
# warnings =
# lib.optional (!config.services.bind.enable) "`webnsupdate` is expected to be used alongside `bind`. This is an unsupported configuration.";
systemd.services.webnsupdate = {
description = "Web interface for nsupdate.";
wantedBy = [ "multi-user.target" ];
after = [
"network.target"
"bind.service"
];
preStart = "${lib.getExe cfg.package} verify ${configFile}";
path = [ pkgs.dig ];
startLimitIntervalSec = 60;
environment.DATA_DIR = "%S/webnsupdate";
serviceConfig = {
ExecStart = [ cmd ];
Type = "exec";
Restart = "on-failure";
RestartSec = "10s";
# User and group
User = cfg.user;
Group = cfg.group;
# Runtime directory and mode
RuntimeDirectory = "webnsupdate";
RuntimeDirectoryMode = "0750";
# Cache directory and mode
CacheDirectory = "webnsupdate";
CacheDirectoryMode = "0750";
# Logs directory and mode
LogsDirectory = "webnsupdate";
LogsDirectoryMode = "0750";
# State directory and mode
StateDirectory = "webnsupdate";
StateDirectoryMode = "0750";
# New file permissions
UMask = "0027";
# Security
NoNewPrivileges = true;
ProtectHome = true;
};
};
};
}

104
src/auth.rs Normal file
View file

@ -0,0 +1,104 @@
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use tower_http::validate_request::ValidateRequestHeaderLayer;
use tracing::{trace, warn};
use crate::password;
pub fn layer<'a, ResBody>(
user_pass_hash: &'a [u8],
salt: &'a str,
) -> ValidateRequestHeaderLayer<Basic<'a, ResBody>> {
ValidateRequestHeaderLayer::custom(Basic::new(user_pass_hash, salt))
}
#[derive(Copy)]
pub struct Basic<'a, ResBody> {
pass: &'a [u8],
salt: &'a str,
_ty: std::marker::PhantomData<fn() -> ResBody>,
}
impl<ResBody> std::fmt::Debug for Basic<'_, ResBody> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BasicAuth")
.field("pass", &self.pass)
.field("salt", &self.salt)
.field("_ty", &self._ty)
.finish()
}
}
impl<ResBody> Clone for Basic<'_, ResBody> {
fn clone(&self) -> Self {
Self {
pass: self.pass,
salt: self.salt,
_ty: std::marker::PhantomData,
}
}
}
impl<'a, ResBody> Basic<'a, ResBody> {
pub fn new(pass: &'a [u8], salt: &'a str) -> Self {
Self {
pass,
salt,
_ty: std::marker::PhantomData,
}
}
fn check_headers(&self, headers: &http::HeaderMap<http::HeaderValue>) -> bool {
let Some(auth) = headers.get(http::header::AUTHORIZATION) else {
return false;
};
// Poor man's split once: https://doc.rust-lang.org/std/primitive.slice.html#method.split_once
let Some(index) = auth.as_bytes().iter().position(|&c| c == b' ') else {
return false;
};
let user_pass = &auth.as_bytes()[index + 1..];
match base64::engine::general_purpose::URL_SAFE.decode(user_pass) {
Ok(user_pass) => {
let hashed = password::hash_basic_auth(&user_pass, self.salt);
if hashed.as_ref() == self.pass {
return true;
}
warn!("rejected update");
trace!(
"mismatched hashes:\nprovided: {}\nstored: {}",
URL_SAFE_NO_PAD.encode(hashed.as_ref()),
URL_SAFE_NO_PAD.encode(self.pass),
);
false
}
Err(err) => {
warn!("received invalid base64 when decoding Basic header: {err}");
false
}
}
}
}
impl<B, ResBody> tower_http::validate_request::ValidateRequest<B> for Basic<'_, ResBody>
where
ResBody: Default,
{
type ResponseBody = ResBody;
fn validate(
&mut self,
request: &mut http::Request<B>,
) -> std::result::Result<(), http::Response<Self::ResponseBody>> {
if self.check_headers(request.headers()) {
return Ok(());
}
let mut res = http::Response::new(ResBody::default());
*res.status_mut() = http::status::StatusCode::UNAUTHORIZED;
res.headers_mut()
.insert(http::header::WWW_AUTHENTICATE, "Basic".parse().unwrap());
Err(res)
}
}

253
src/config.rs Normal file
View file

@ -0,0 +1,253 @@
use std::{
fs::File,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::PathBuf,
};
use axum_client_ip::ClientIpSource;
use miette::{Context, IntoDiagnostic};
#[derive(Debug, Default, Clone, Copy, serde::Deserialize, serde::Serialize)]
pub enum IpType {
#[default]
Both,
Ipv4Only,
Ipv6Only,
}
impl IpType {
pub fn valid_for_type(self, ip: IpAddr) -> bool {
match self {
IpType::Both => true,
IpType::Ipv4Only => ip.is_ipv4(),
IpType::Ipv6Only => ip.is_ipv6(),
}
}
}
impl std::fmt::Display for IpType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IpType::Both => f.write_str("both"),
IpType::Ipv4Only => f.write_str("ipv4-only"),
IpType::Ipv6Only => f.write_str("ipv6-only"),
}
}
}
impl std::str::FromStr for IpType {
type Err = miette::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"both" => Ok(Self::Both),
"ipv4-only" => Ok(Self::Ipv4Only),
"ipv6-only" => Ok(Self::Ipv6Only),
_ => miette::bail!("expected one of 'ipv4-only', 'ipv6-only' or 'both', got '{s}'"),
}
}
}
/// Webserver settings
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Server {
/// Ip address and port of the server
#[serde(default = "default_address")]
pub address: SocketAddr,
}
/// Password settings
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Password {
/// File containing password to match against
///
/// Should be of the format `username:password` and contain a single password
#[serde(default, skip_serializing_if = "Option::is_none")]
pub password_file: Option<PathBuf>,
/// Salt to get more unique hashed passwords and prevent table based attacks
#[serde(default = "default_salt")]
pub salt: Box<str>,
}
/// Records settings
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Records {
/// Time To Live (in seconds) to set on the DNS records
#[serde(
default = "default_ttl",
serialize_with = "humantime_ser",
deserialize_with = "humantime_de"
)]
pub ttl: humantime::Duration,
/// List of domain names for which to update the IP when an update is requested
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[allow(clippy::struct_field_names)]
pub records: Vec<Box<str>>,
/// If provided, when an IPv6 prefix is provided with an update, this will be used to derive
/// the full IPv6 address of the client
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client_id: Option<Ipv6Addr>,
/// If a client id is provided the ipv6 update will be ignored (only the prefix will be used).
/// This domain will point to the ipv6 address instead of the address derived from the client
/// id (usually this is the router).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub router_domain: Option<Box<str>>,
/// Set client IP source
///
/// see: <https://docs.rs/axum-client-ip/latest/axum_client_ip/enum.ClientIpSource.html>
#[serde(default = "default_ip_source")]
pub ip_source: ClientIpSource,
/// Set which IPs to allow updating (ipv4, ipv6 or both)
#[serde(default = "default_ip_type")]
pub ip_type: IpType,
/// Keyfile `nsupdate` should use
///
/// If specified, then `webnsupdate` must have read access to the file
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_file: Option<PathBuf>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Config {
/// Server Configuration
#[serde(flatten)]
pub server: Server,
/// Password Configuration
#[serde(flatten)]
pub password: Password,
/// Records Configuration
#[serde(flatten)]
pub records: Records,
/// The config schema (used for lsp completions)
#[serde(default, rename = "$schema", skip_serializing)]
pub _schema: serde::de::IgnoredAny,
}
impl Config {
/// Load the configuration without verifying it
pub fn load(path: &std::path::Path) -> miette::Result<Self> {
serde_json::from_reader::<File, Self>(
File::open(path)
.into_diagnostic()
.wrap_err_with(|| format!("failed open {}", path.display()))?,
)
.into_diagnostic()
.wrap_err_with(|| format!("failed to load configuration from {}", path.display()))
}
/// Ensure only a verified configuration is returned
pub fn verified(self) -> miette::Result<Self> {
self.verify()?;
Ok(self)
}
/// Verify the configuration
pub fn verify(&self) -> Result<(), Invalid> {
let mut invalid_records: Vec<miette::Error> = self
.records
.records
.iter()
.filter_map(|record| crate::records::validate_record_str(record).err())
.collect();
invalid_records.extend(
self.records
.router_domain
.as_ref()
.and_then(|domain| crate::records::validate_record_str(domain).err()),
);
let err = Invalid { invalid_records };
if err.invalid_records.is_empty() {
Ok(())
} else {
Err(err)
}
}
}
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
#[error("the configuration was invalid")]
pub struct Invalid {
#[related]
pub invalid_records: Vec<miette::Error>,
}
// --- Default Values (sadly serde doesn't have a way to specify a constant as a default value) ---
fn default_ttl() -> humantime::Duration {
super::DEFAULT_TTL.into()
}
fn default_salt() -> Box<str> {
super::DEFAULT_SALT.into()
}
fn default_address() -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5353)
}
fn default_ip_source() -> ClientIpSource {
ClientIpSource::RightmostXForwardedFor
}
fn default_ip_type() -> IpType {
IpType::Both
}
fn humantime_de<'de, D>(de: D) -> Result<humantime::Duration, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = humantime::Duration;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a duration (e.g. 5s)")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.parse().map_err(E::custom)
}
}
de.deserialize_str(Visitor)
}
fn humantime_ser<S>(duration: &humantime::Duration, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ser.serialize_str(&duration.to_string())
}
#[test]
fn default_values_config_snapshot() {
let config: Config = serde_json::from_str("{}").unwrap();
insta::assert_json_snapshot!(config, @r#"
{
"address": "127.0.0.1:5353",
"salt": "UpdateMyDNS",
"ttl": {
"secs": 60,
"nanos": 0
},
"ip_source": "RightmostXForwardedFor",
"ip_type": "Both"
}
"#);
}

View file

@ -1,24 +1,28 @@
use std::{
ffi::OsStr,
io::ErrorKind,
net::{IpAddr, SocketAddr},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::{Path, PathBuf},
process::{ExitStatus, Stdio},
time::Duration,
};
use axum::{extract::State, routing::get, Json, Router};
use axum_auth::AuthBasic;
use axum_client_ip::{SecureClientIp, SecureClientIpSource};
use axum::{
extract::{Query, State},
routing::get,
Router,
};
use axum_client_ip::ClientIp;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use clap::{Parser, Subcommand};
use clap_verbosity_flag::Verbosity;
use config::Config;
use http::StatusCode;
use miette::{bail, ensure, Context, IntoDiagnostic, Result};
use tokio::io::AsyncWriteExt;
use tracing::{debug, error, info, level_filters::LevelFilter, trace, warn};
use tracing::{debug, error, info};
use tracing_subscriber::EnvFilter;
mod auth;
mod config;
mod nsupdate;
mod password;
mod records;
@ -28,77 +32,54 @@ const DEFAULT_SALT: &str = "UpdateMyDNS";
#[derive(Debug, Parser)]
struct Opts {
#[command(flatten)]
verbosity: Verbosity,
/// Ip address of the server
#[arg(long, default_value = "127.0.0.1")]
address: IpAddr,
/// Port of the server
#[arg(long, default_value_t = 5353)]
port: u16,
/// File containing password to match against
///
/// Should be of the format `username:password` and contain a single password
#[arg(long)]
password_file: Option<PathBuf>,
/// Salt to get more unique hashed passwords and prevent table based attacks
#[arg(long, default_value = DEFAULT_SALT)]
salt: String,
/// Time To Live (in seconds) to set on the DNS records
#[arg(long, default_value_t = DEFAULT_TTL.as_secs())]
ttl: u64,
verbosity: Verbosity<clap_verbosity_flag::InfoLevel>,
/// Data directory
#[arg(long, default_value = ".")]
#[arg(long, env, default_value = ".")]
data_dir: PathBuf,
/// File containing the records that should be updated when an update request is made
///
/// There should be one record per line:
///
/// ```text
/// example.com.
/// mail.example.com.
/// ```
#[arg(long)]
records: PathBuf,
/// Keyfile `nsupdate` should use
///
/// If specified, then `webnsupdate` must have read access to the file
#[arg(long)]
key_file: Option<PathBuf>,
/// Allow not setting a password
#[arg(long)]
insecure: bool,
/// Set client IP source
///
/// see: https://docs.rs/axum-client-ip/latest/axum_client_ip/enum.SecureClientIpSource.html
#[clap(long, default_value = "RightmostXForwardedFor")]
ip_source: SecureClientIpSource,
#[clap(flatten)]
config_or_command: ConfigOrCommand,
}
#[derive(clap::Args, Debug)]
#[group(multiple = false)]
struct ConfigOrCommand {
/// Path to the configuration file
#[arg(long, short)]
config: Option<PathBuf>,
#[clap(subcommand)]
subcommand: Option<Cmd>,
}
impl ConfigOrCommand {
pub fn take(&mut self) -> (Option<PathBuf>, Option<Cmd>) {
(self.config.take(), self.subcommand.take())
}
}
#[derive(Debug, Subcommand)]
enum Cmd {
Mkpasswd(password::Mkpasswd),
/// Verify the records file
Verify,
/// Verify the configuration file
Verify {
/// Path to the configuration file
config: PathBuf,
},
}
impl Cmd {
pub fn process(self, args: &Opts) -> Result<()> {
match self {
Cmd::Mkpasswd(mkpasswd) => mkpasswd.process(args),
Cmd::Verify => records::load(&args.records).map(drop),
Cmd::Verify { config } => config::Config::load(&config) // load config
.and_then(Config::verified) // verify config
.map(drop), // ignore config data
}
}
}
@ -108,23 +89,123 @@ struct AppState<'a> {
/// TTL set on the Zonefile
ttl: Duration,
/// Salt added to the password
salt: &'a str,
/// The IN A/AAAA records that should have their IPs updated
records: &'a [&'a str],
/// The TSIG key file
key_file: Option<&'a Path>,
/// The password hash
password_hash: Option<&'a [u8]>,
/// The file where the last IP is stored
ip_file: &'a Path,
/// Last recorded IPs
last_ips: std::sync::Arc<tokio::sync::Mutex<SavedIPs>>,
/// The IP type for which to allow updates
ip_type: config::IpType,
}
fn load_ip(path: &Path) -> Result<Option<IpAddr>> {
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
struct SavedIPs {
#[serde(skip_serializing_if = "Option::is_none")]
ipv4: Option<Ipv4Addr>,
#[serde(skip_serializing_if = "Option::is_none")]
ipv6: Option<Ipv6Addr>,
}
impl SavedIPs {
fn update(&mut self, ip: IpAddr) {
match ip {
IpAddr::V4(ipv4_addr) => self.ipv4 = Some(ipv4_addr),
IpAddr::V6(ipv6_addr) => self.ipv6 = Some(ipv6_addr),
}
}
fn ips(&self) -> impl Iterator<Item = IpAddr> {
self.ipv4
.map(IpAddr::V4)
.into_iter()
.chain(self.ipv6.map(IpAddr::V6))
}
fn from_str(data: &str) -> miette::Result<Self> {
match data.parse::<IpAddr>() {
// Old format
Ok(IpAddr::V4(ipv4)) => Ok(Self {
ipv4: Some(ipv4),
ipv6: None,
}),
Ok(IpAddr::V6(ipv6)) => Ok(Self {
ipv4: None,
ipv6: Some(ipv6),
}),
Err(_) => serde_json::from_str(data).into_diagnostic(),
}
}
}
impl AppState<'static> {
fn from_args(args: &Opts, config: &config::Config) -> miette::Result<Self> {
let Opts {
verbosity: _,
data_dir,
insecure,
config_or_command: _,
} = args;
let config::Records {
ttl,
records,
client_id: _,
router_domain: _,
ip_source: _,
ip_type,
key_file,
} = &config.records;
// Use last registered IP address if available
let ip_file = Box::leak(data_dir.join("last-ip.json").into_boxed_path());
// Leak DNS records
let records: &[&str] = &*Vec::leak(
records
.iter()
.map(|record| &*Box::leak(record.clone()))
.collect(),
);
let state = AppState {
ttl: **ttl,
records,
// Load keyfile
key_file: key_file
.as_deref()
.map(|path| -> miette::Result<_> {
std::fs::File::open(path)
.into_diagnostic()
.wrap_err_with(|| {
format!("{} is not readable by the current user", path.display())
})?;
Ok(&*Box::leak(path.into()))
})
.transpose()?,
ip_file,
ip_type: *ip_type,
last_ips: std::sync::Arc::new(tokio::sync::Mutex::new(
load_ip(ip_file)?.unwrap_or_default(),
)),
};
ensure!(
state.key_file.is_some() || *insecure,
"a key file must be used"
);
Ok(state)
}
}
fn load_ip(path: &Path) -> Result<Option<SavedIPs>> {
debug!("loading last IP from {}", path.display());
let data = match std::fs::read_to_string(path) {
Ok(ip) => ip,
@ -138,120 +219,117 @@ fn load_ip(path: &Path) -> Result<Option<IpAddr>> {
}
};
Ok(Some(
data.parse()
.into_diagnostic()
.wrap_err("failed to parse last ip address")?,
))
SavedIPs::from_str(&data)
.wrap_err_with(|| format!("failed to load last ip address from {}", path.display()))
.map(Some)
}
#[derive(Clone, Copy, Debug)]
struct Ipv6Prefix {
prefix: Ipv6Addr,
length: u32,
}
impl std::fmt::Display for Ipv6Prefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { prefix, length } = self;
write!(f, "{prefix}/{length}")
}
}
impl std::str::FromStr for Ipv6Prefix {
type Err = miette::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let (addr, len) = s.split_once('/').wrap_err("missing `/` in ipv6 prefix")?;
Ok(Self {
prefix: addr
.parse()
.into_diagnostic()
.wrap_err("invalid ipv6 address for ipv6 prefix")?,
length: len
.parse()
.into_diagnostic()
.wrap_err("invalid length for ipv6 prefix")?,
})
}
}
#[tracing::instrument(err)]
fn main() -> Result<()> {
// set panic hook to pretty print with miette's formatter
miette::set_panic_hook();
// parse cli arguments
let mut args = Opts::parse();
debug!("{args:?}");
// configure logger
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.without_time()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(
if args.verbosity.is_present() {
match args.verbosity.log_level_filter() {
clap_verbosity_flag::LevelFilter::Off => LevelFilter::OFF,
clap_verbosity_flag::LevelFilter::Error => LevelFilter::ERROR,
clap_verbosity_flag::LevelFilter::Warn => LevelFilter::WARN,
clap_verbosity_flag::LevelFilter::Info => LevelFilter::INFO,
clap_verbosity_flag::LevelFilter::Debug => LevelFilter::DEBUG,
clap_verbosity_flag::LevelFilter::Trace => LevelFilter::TRACE,
}
} else {
LevelFilter::WARN
}
.into(),
)
.with_default_directive(args.verbosity.tracing_level_filter().into())
.from_env_lossy(),
)
.finish();
tracing::subscriber::set_global_default(subscriber)
.into_diagnostic()
.wrap_err("setting global tracing subscriber")?;
.wrap_err("failed to set global tracing subscriber")?;
// process subcommand
if let Some(cmd) = args.subcommand.take() {
return cmd.process(&args);
}
debug!("{args:?}");
let config = match args.config_or_command.take() {
// process subcommand
(None, Some(cmd)) => return cmd.process(&args),
(Some(path), None) => {
let config = config::Config::load(&path)?;
if let Err(err) = config.verify() {
error!("failed to verify configuration: {err}");
}
config
}
(None, None) | (Some(_), Some(_)) => unreachable!(
"bad state, one of config or subcommand should be available (clap should enforce this)"
),
};
// Initialize state
let state = AppState::from_args(&args, &config)?;
let Opts {
verbosity: _,
address: ip,
port,
password_file,
data_dir,
key_file,
data_dir: _,
insecure,
subcommand: _,
records,
salt,
ttl,
ip_source,
config_or_command: _,
} = args;
info!("checking environment");
// Set state
let ttl = Duration::from_secs(ttl);
// Load password hash
let password_hash = config
.password
.password_file
.map(|path| -> miette::Result<_> {
let path = path.as_path();
let pass = std::fs::read_to_string(path).into_diagnostic()?;
// Use last registered IP address if available
let ip_file = data_dir.join("last-ip");
let pass: Box<[u8]> = URL_SAFE_NO_PAD
.decode(pass.trim().as_bytes())
.into_diagnostic()
.wrap_err_with(|| format!("failed to decode password from {}", path.display()))?
.into();
let state = AppState {
ttl,
salt: salt.leak(),
// Load DNS records
records: records::load_no_verify(&records)?,
// Load keyfile
key_file: key_file
.map(|key_file| -> miette::Result<_> {
let path = key_file.as_path();
std::fs::File::open(path)
.into_diagnostic()
.wrap_err_with(|| {
format!("{} is not readable by the current user", path.display())
})?;
Ok(&*Box::leak(key_file.into_boxed_path()))
})
.transpose()?,
// Load password hash
password_hash: password_file
.map(|path| -> miette::Result<_> {
let pass = std::fs::read_to_string(path.as_path()).into_diagnostic()?;
let pass: Box<[u8]> = URL_SAFE_NO_PAD
.decode(pass.trim().as_bytes())
.into_diagnostic()
.wrap_err_with(|| format!("failed to decode password from {}", path.display()))?
.into();
Ok(&*Box::leak(pass))
})
.transpose()?,
ip_file: Box::leak(ip_file.into_boxed_path()),
};
Ok(pass)
})
.transpose()
.wrap_err("failed to load password hash")?;
ensure!(
state.password_hash.is_some() || insecure,
password_hash.is_some() || insecure,
"a password must be used"
);
ensure!(
state.key_file.is_some() || insecure,
"a key file must be used"
);
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
@ -259,9 +337,17 @@ fn main() -> Result<()> {
.wrap_err("failed to start the tokio runtime")?;
rt.block_on(async {
// Load previous IP and update DNS record to point to it (if available)
match load_ip(state.ip_file) {
Ok(Some(ip)) => match nsupdate(ip, ttl, state.key_file, state.records).await {
// Update DNS record with previous IPs (if available)
let ips = state.last_ips.lock().await.clone();
let mut actions = ips
.ips()
.filter(|ip| config.records.ip_type.valid_for_type(*ip))
.flat_map(|ip| nsupdate::Action::from_records(ip, state.ttl, state.records))
.peekable();
if actions.peek().is_some() {
match nsupdate::nsupdate(state.key_file, actions).await {
Ok(status) => {
if !status.success() {
error!("nsupdate failed: code {status}");
@ -274,25 +360,31 @@ fn main() -> Result<()> {
.into_diagnostic()
.wrap_err("failed to update records with previous IP");
}
},
Ok(None) => {
info!("No previous IP address set");
}
Err(err) => {
error!("Failed to load last ip address: {err}")
}
};
}
// Create services
let app = Router::new().route("/update", get(update_records));
// if a password is provided, validate it
let app = if let Some(pass) = password_hash {
app.layer(auth::layer(
Box::leak(pass),
Box::leak(config.password.salt),
))
} else {
app
}
.layer(config.records.ip_source.into_extension())
.with_state(state);
let config::Server { address } = config.server;
// Start services
let app = Router::new()
.route("/update", get(update_records))
.layer(ip_source.into_extension())
.with_state(state);
info!("starting listener on {ip}:{port}");
let listener = tokio::net::TcpListener::bind(SocketAddr::new(ip, port))
info!("starting listener on {address}");
let listener = tokio::net::TcpListener::bind(address)
.await
.into_diagnostic()?;
info!("listening on {ip}:{port}");
info!("listening on {address}");
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
@ -300,157 +392,318 @@ fn main() -> Result<()> {
.await
.into_diagnostic()
})
.wrap_err("failed to run main loop")
}
#[tracing::instrument(skip(state, pass), level = "trace", ret(level = "info"))]
/// Serde deserialization decorator to map empty Strings to None,
///
/// Adapted from: <https://github.com/tokio-rs/axum/blob/main/examples/query-params-with-empty-strings/src/main.rs>
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: std::str::FromStr,
T::Err: std::fmt::Display,
{
use serde::Deserialize;
let opt = Option::<std::borrow::Cow<'de, str>>::deserialize(de)?;
match opt.as_deref() {
None | Some("") => Ok(None),
Some(s) => s.parse::<T>().map_err(serde::de::Error::custom).map(Some),
}
}
#[derive(Debug, serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct FritzBoxUpdateParams {
/// The domain that should be updated
#[allow(unused)]
#[serde(default, deserialize_with = "empty_string_as_none")]
domain: Option<String>,
/// IPv4 address for the domain
#[serde(default, deserialize_with = "empty_string_as_none")]
ipv4: Option<Ipv4Addr>,
/// IPv6 address for the domain
#[serde(default, deserialize_with = "empty_string_as_none")]
ipv6: Option<Ipv6Addr>,
/// IPv6 prefix for the home network
#[allow(unused)]
#[serde(default, deserialize_with = "empty_string_as_none")]
ipv6prefix: Option<Ipv6Prefix>,
/// Whether the networks uses both IPv4 and IPv6
#[allow(unused)]
#[serde(default, deserialize_with = "empty_string_as_none")]
dualstack: Option<String>,
}
impl FritzBoxUpdateParams {
fn has_data(&self) -> bool {
let Self {
domain,
ipv4,
ipv6,
ipv6prefix,
dualstack,
} = self;
domain.is_some()
| ipv4.is_some()
| ipv6.is_some()
| ipv6prefix.is_some()
| dualstack.is_some()
}
}
#[tracing::instrument(skip(state), level = "trace", ret(level = "info"))]
async fn update_records(
State(state): State<AppState<'static>>,
AuthBasic((username, pass)): AuthBasic,
SecureClientIp(ip): SecureClientIp,
ClientIp(ip): ClientIp,
Query(update_params): Query<FritzBoxUpdateParams>,
) -> axum::response::Result<&'static str> {
debug!("received update request from {ip}");
let Some(pass) = pass else {
return Err((StatusCode::UNAUTHORIZED, Json::from("no password provided")).into());
};
info!("accepted update from {ip}");
if let Some(stored_pass) = state.password_hash {
let password = pass.trim().to_string();
let pass_hash = password::hash_identity(&username, &password, state.salt);
if pass_hash.as_ref() != stored_pass {
warn!("rejected update");
trace!(
"mismatched hashes:\n{}\n{}",
URL_SAFE_NO_PAD.encode(pass_hash.as_ref()),
URL_SAFE_NO_PAD.encode(stored_pass),
if !update_params.has_data() {
if !state.ip_type.valid_for_type(ip) {
tracing::warn!(
"rejecting update from {ip} as we are running a {} filter",
state.ip_type
);
return Err((StatusCode::UNAUTHORIZED, "invalid identity").into());
return Err((
StatusCode::CONFLICT,
format!("running in {} mode", state.ip_type),
)
.into());
}
return trigger_update(ip, &state).await;
}
// FIXME: mark suspicious updates (where IP doesn't match the update_ip) and reject them based
// on policy
let FritzBoxUpdateParams {
domain: _,
ipv4,
ipv6,
ipv6prefix: _,
dualstack: _,
} = update_params;
if ipv4.is_none() && ipv6.is_none() {
return Err((
StatusCode::BAD_REQUEST,
"failed to provide an IP for the update",
)
.into());
}
if let Some(ip) = ipv4 {
let ip = IpAddr::V4(ip);
if state.ip_type.valid_for_type(ip) {
_ = trigger_update(ip, &state).await?;
} else {
tracing::warn!("requested update of IPv4 but we are {}", state.ip_type);
}
}
info!("accepted update");
match nsupdate(ip, state.ttl, state.key_file, state.records).await {
if let Some(ip) = ipv6 {
let ip = IpAddr::V6(ip);
if state.ip_type.valid_for_type(ip) {
_ = trigger_update(ip, &state).await?;
} else {
tracing::warn!("requested update of IPv6 but we are {}", state.ip_type);
}
}
Ok("Successfully updated IP of records!\n")
}
#[tracing::instrument(skip(state), level = "trace", ret(level = "info"))]
async fn trigger_update(
ip: IpAddr,
state: &AppState<'static>,
) -> axum::response::Result<&'static str> {
let actions = nsupdate::Action::from_records(ip, state.ttl, state.records);
if actions.len() == 0 {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Nothing to do (e.g. we are ipv4-only but an ipv6 update was requested)",
)
.into());
}
match nsupdate::nsupdate(state.key_file, actions).await {
Ok(status) if status.success() => {
let ips = {
// Update state
let mut ips = state.last_ips.lock().await;
ips.update(ip);
ips.clone()
};
let ip_file = state.ip_file;
tokio::task::spawn_blocking(move || {
info!("updating last ip to {ip}");
if let Err(err) = std::fs::write(state.ip_file, format!("{ip}")) {
info!("updating last ips to {ips:?}");
let data = serde_json::to_vec(&ips).expect("invalid serialization impl");
if let Err(err) = std::fs::write(ip_file, data) {
error!("Failed to update last IP: {err}");
}
info!("updated last ip to {ip}");
info!("updated last ips to {ips:?}");
});
Ok("successful update")
Ok("Successfully updated IP of records!\n")
}
Ok(status) => {
error!("nsupdate failed with code {status}");
Err((
StatusCode::INTERNAL_SERVER_ERROR,
"nsupdate failed, check server logs",
"nsupdate failed, check server logs\n",
)
.into())
}
Err(error) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to update records: {error}"),
format!("failed to update records: {error}\n"),
)
.into()),
}
}
#[tracing::instrument(level = "trace", ret(level = "warn"))]
async fn nsupdate(
ip: IpAddr,
ttl: Duration,
key_file: Option<&Path>,
records: &[&str],
) -> std::io::Result<ExitStatus> {
let mut cmd = tokio::process::Command::new("nsupdate");
if let Some(key_file) = key_file {
cmd.args([OsStr::new("-k"), key_file.as_os_str()]);
}
debug!("spawning new process");
let mut child = cmd
.stdin(Stdio::piped())
.spawn()
.inspect_err(|err| warn!("failed to spawn child: {err}"))?;
let mut stdin = child.stdin.take().expect("stdin not present");
debug!("sending update request");
stdin
.write_all(update_ns_records(ip, ttl, records).as_bytes())
.await
.inspect_err(|err| warn!("failed to write to the stdin of nsupdate: {err}"))?;
debug!("closing stdin");
stdin
.shutdown()
.await
.inspect_err(|err| warn!("failed to close stdin to nsupdate: {err}"))?;
debug!("waiting for nsupdate to exit");
child
.wait()
.await
.inspect_err(|err| warn!("failed to wait for child: {err}"))
}
fn update_ns_records(ip: IpAddr, ttl: Duration, records: &[&str]) -> String {
use std::fmt::Write;
let ttl_s: u64 = ttl.as_secs();
let rec_type = match ip {
IpAddr::V4(_) => "A",
IpAddr::V6(_) => "AAAA",
};
let mut cmds = String::from("server 127.0.0.1\n");
for &record in records {
writeln!(cmds, "update delete {record} {ttl_s} IN {rec_type}").unwrap();
writeln!(cmds, "update add {record} {ttl_s} IN {rec_type} {ip}").unwrap();
}
writeln!(cmds, "send\nquit").unwrap();
cmds
}
#[cfg(test)]
mod test {
use insta::assert_snapshot;
mod parse_query_params {
use axum::extract::Query;
use crate::{update_ns_records, DEFAULT_TTL};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::FritzBoxUpdateParams;
#[test]
#[allow(non_snake_case)]
fn expected_update_string_A() {
assert_snapshot!(update_ns_records(
IpAddr::V4(Ipv4Addr::LOCALHOST),
DEFAULT_TTL,
&["example.com.", "example.org.", "example.net."],
), @r###"
server 127.0.0.1
update delete example.com. 60 IN A
update add example.com. 60 IN A 127.0.0.1
update delete example.org. 60 IN A
update add example.org. 60 IN A 127.0.0.1
update delete example.net. 60 IN A
update add example.net. 60 IN A 127.0.0.1
send
quit
"###);
fn no_params() {
let uri = http::Uri::builder()
.path_and_query("/update")
.build()
.unwrap();
let query: Query<FritzBoxUpdateParams> = Query::try_from_uri(&uri).unwrap();
insta::assert_debug_snapshot!(query, @r#"
Query(
FritzBoxUpdateParams {
domain: None,
ipv4: None,
ipv6: None,
ipv6prefix: None,
dualstack: None,
},
)
"#);
}
#[test]
#[allow(non_snake_case)]
fn expected_update_string_AAAA() {
assert_snapshot!(update_ns_records(
IpAddr::V6(Ipv6Addr::LOCALHOST),
DEFAULT_TTL,
&["example.com.", "example.org.", "example.net."],
), @r###"
server 127.0.0.1
update delete example.com. 60 IN AAAA
update add example.com. 60 IN AAAA ::1
update delete example.org. 60 IN AAAA
update add example.org. 60 IN AAAA ::1
update delete example.net. 60 IN AAAA
update add example.net. 60 IN AAAA ::1
send
quit
"###);
fn ipv4() {
let uri = http::Uri::builder()
.path_and_query("/update?ipv4=1.2.3.4")
.build()
.unwrap();
let query: Query<FritzBoxUpdateParams> = Query::try_from_uri(&uri).unwrap();
insta::assert_debug_snapshot!(query, @r#"
Query(
FritzBoxUpdateParams {
domain: None,
ipv4: Some(
1.2.3.4,
),
ipv6: None,
ipv6prefix: None,
dualstack: None,
},
)
"#);
}
#[test]
fn ipv6() {
let uri = http::Uri::builder()
.path_and_query("/update?ipv6=%3A%3A1234")
.build()
.unwrap();
let query: Query<FritzBoxUpdateParams> = Query::try_from_uri(&uri).unwrap();
insta::assert_debug_snapshot!(query, @r#"
Query(
FritzBoxUpdateParams {
domain: None,
ipv4: None,
ipv6: Some(
::1234,
),
ipv6prefix: None,
dualstack: None,
},
)
"#);
}
#[test]
fn ipv4_and_ipv6() {
let uri = http::Uri::builder()
.path_and_query("/update?ipv4=1.2.3.4&ipv6=%3A%3A1234")
.build()
.unwrap();
let query: Query<FritzBoxUpdateParams> = Query::try_from_uri(&uri).unwrap();
insta::assert_debug_snapshot!(query, @r#"
Query(
FritzBoxUpdateParams {
domain: None,
ipv4: Some(
1.2.3.4,
),
ipv6: Some(
::1234,
),
ipv6prefix: None,
dualstack: None,
},
)
"#);
}
#[test]
fn ipv4_and_empty_ipv6() {
let uri = http::Uri::builder()
.path_and_query("/update?ipv4=1.2.3.4&ipv6=")
.build()
.unwrap();
let query: Query<FritzBoxUpdateParams> = Query::try_from_uri(&uri).unwrap();
insta::assert_debug_snapshot!(query, @r#"
Query(
FritzBoxUpdateParams {
domain: None,
ipv4: Some(
1.2.3.4,
),
ipv6: None,
ipv6prefix: None,
dualstack: None,
},
)
"#);
}
#[test]
fn empty_ipv4_and_ipv6() {
let uri = http::Uri::builder()
.path_and_query("/update?ipv4=&ipv6=%3A%3A1234")
.build()
.unwrap();
let query: Query<FritzBoxUpdateParams> = Query::try_from_uri(&uri).unwrap();
insta::assert_debug_snapshot!(query, @r#"
Query(
FritzBoxUpdateParams {
domain: None,
ipv4: None,
ipv6: Some(
::1234,
),
ipv6prefix: None,
dualstack: None,
},
)
"#);
}
}

156
src/nsupdate.rs Normal file
View file

@ -0,0 +1,156 @@
use std::{
ffi::OsStr,
net::IpAddr,
path::Path,
process::{ExitStatus, Stdio},
time::Duration,
};
use tokio::io::AsyncWriteExt;
use tracing::{debug, warn};
pub enum Action<'a> {
// Reassign a domain to a different IP
Reassign {
domain: &'a str,
to: IpAddr,
ttl: Duration,
},
}
impl<'a> Action<'a> {
/// Create a set of [`Action`]s reassigning the domains in `records` to the specified
/// [`IpAddr`]
pub fn from_records(
to: IpAddr,
ttl: Duration,
records: &'a [&'a str],
) -> impl IntoIterator<Item = Self> + std::iter::ExactSizeIterator + 'a {
records
.iter()
.map(move |&domain| Action::Reassign { domain, to, ttl })
}
}
impl std::fmt::Display for Action<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Action::Reassign { domain, to, ttl } => {
let ttl = ttl.as_secs();
let kind = match to {
IpAddr::V4(_) => "A",
IpAddr::V6(_) => "AAAA",
};
// Delete previous record of type `kind`
writeln!(f, "update delete {domain} {ttl} IN {kind}")?;
// Add record with new IP
writeln!(f, "update add {domain} {ttl} IN {kind} {to}")
}
}
}
}
#[tracing::instrument(level = "trace", skip(actions), ret(level = "warn"))]
pub async fn nsupdate(
key_file: Option<&Path>,
actions: impl IntoIterator<Item = Action<'_>>,
) -> std::io::Result<ExitStatus> {
let mut cmd = tokio::process::Command::new("nsupdate");
if let Some(key_file) = key_file {
cmd.args([OsStr::new("-k"), key_file.as_os_str()]);
}
debug!("spawning new process");
let mut child = cmd
.stdin(Stdio::piped())
.spawn()
.inspect_err(|err| warn!("failed to spawn child: {err}"))?;
let mut stdin = child.stdin.take().expect("stdin not present");
debug!("sending update request");
let mut buf = Vec::new();
update_ns_records(&mut buf, actions).unwrap();
stdin
.write_all(&buf)
.await
.inspect_err(|err| warn!("failed to write to the stdin of nsupdate: {err}"))?;
debug!("closing stdin");
stdin
.shutdown()
.await
.inspect_err(|err| warn!("failed to close stdin to nsupdate: {err}"))?;
debug!("waiting for nsupdate to exit");
child
.wait()
.await
.inspect_err(|err| warn!("failed to wait for child: {err}"))
}
fn update_ns_records<'a>(
mut buf: impl std::io::Write,
actions: impl IntoIterator<Item = Action<'a>>,
) -> std::io::Result<()> {
writeln!(buf, "server 127.0.0.1")?;
for action in actions {
write!(buf, "{action}")?;
}
writeln!(buf, "send")?;
writeln!(buf, "quit")
}
#[cfg(test)]
mod test {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use insta::assert_snapshot;
use super::{update_ns_records, Action};
use crate::DEFAULT_TTL;
#[test]
#[allow(non_snake_case)]
fn expected_update_string_A() {
let mut buf = Vec::new();
let actions = Action::from_records(
IpAddr::V4(Ipv4Addr::LOCALHOST),
DEFAULT_TTL,
&["example.com.", "example.org.", "example.net."],
);
update_ns_records(&mut buf, actions).unwrap();
assert_snapshot!(String::from_utf8(buf).unwrap(), @r###"
server 127.0.0.1
update delete example.com. 60 IN A
update add example.com. 60 IN A 127.0.0.1
update delete example.org. 60 IN A
update add example.org. 60 IN A 127.0.0.1
update delete example.net. 60 IN A
update add example.net. 60 IN A 127.0.0.1
send
quit
"###);
}
#[test]
#[allow(non_snake_case)]
fn expected_update_string_AAAA() {
let mut buf = Vec::new();
let actions = Action::from_records(
IpAddr::V6(Ipv6Addr::LOCALHOST),
DEFAULT_TTL,
&["example.com.", "example.org.", "example.net."],
);
update_ns_records(&mut buf, actions).unwrap();
assert_snapshot!(String::from_utf8(buf).unwrap(), @r###"
server 127.0.0.1
update delete example.com. 60 IN AAAA
update add example.com. 60 IN AAAA ::1
update delete example.org. 60 IN AAAA
update add example.org. 60 IN AAAA ::1
update delete example.net. 60 IN AAAA
update add example.net. 60 IN AAAA ::1
send
quit
"###);
}
}

View file

@ -4,7 +4,7 @@
//! records
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use std::path::PathBuf;
use base64::prelude::*;
use miette::{Context, IntoDiagnostic, Result};
@ -20,28 +20,48 @@ pub struct Mkpasswd {
/// The password
password: String,
/// An application specific value
#[arg(long, default_value = crate::DEFAULT_SALT)]
salt: String,
/// The file to write the password to
password_file: Option<PathBuf>,
}
impl Mkpasswd {
pub fn process(self, args: &crate::Opts) -> Result<()> {
mkpasswd(self, args.password_file.as_deref(), &args.salt)
pub fn process(self, _args: &crate::Opts) -> Result<()> {
mkpasswd(self)
}
}
pub fn hash_basic_auth(user_pass: &[u8], salt: &str) -> Digest {
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
context.update(user_pass);
context.update(salt.as_bytes());
context.finish()
}
pub fn hash_identity(username: &str, password: &str, salt: &str) -> Digest {
let mut data = Vec::with_capacity(username.len() + password.len() + salt.len() + 1);
write!(data, "{username}:{password}{salt}").unwrap();
ring::digest::digest(&ring::digest::SHA256, &data)
let mut context = ring::digest::Context::new(&ring::digest::SHA256);
context.update(username.as_bytes());
context.update(b":");
context.update(password.as_bytes());
context.update(salt.as_bytes());
context.finish()
}
pub fn mkpasswd(
Mkpasswd { username, password }: Mkpasswd,
password_file: Option<&Path>,
salt: &str,
Mkpasswd {
username,
password,
salt,
password_file,
}: Mkpasswd,
) -> miette::Result<()> {
let hash = hash_identity(&username, &password, salt);
let hash = hash_identity(&username, &password, &salt);
let encoded = BASE64_URL_SAFE_NO_PAD.encode(hash.as_ref());
let Some(path) = password_file else {
let Some(path) = password_file.as_deref() else {
println!("{encoded}");
return Ok(());
};

View file

@ -1,52 +1,9 @@
//! Deal with the DNS records
use std::path::Path;
use miette::{ensure, miette, LabeledSpan, Result};
use miette::{ensure, miette, Context, IntoDiagnostic, LabeledSpan, NamedSource, Result};
/// Loads and verifies the records from a file
pub fn load(path: &Path) -> Result<()> {
let records = std::fs::read_to_string(path)
.into_diagnostic()
.wrap_err_with(|| format!("failed to read records from {}", path.display()))?;
verify(&records, path)?;
Ok(())
}
/// Load records without verifying them
pub fn load_no_verify(path: &Path) -> Result<&'static [&'static str]> {
let records = std::fs::read_to_string(path)
.into_diagnostic()
.wrap_err_with(|| format!("failed to read records from {}", path.display()))?;
if let Err(err) = verify(&records, path) {
tracing::error!("Failed to verify records: {err}");
}
// leak memory: we only do this here and it prevents a bunch of allocations
let records: &str = records.leak();
let records: Box<[&str]> = records.lines().collect();
Ok(Box::leak(records))
}
/// Verifies that a list of records is valid
pub fn verify(data: &str, path: &Path) -> Result<()> {
let mut offset = 0usize;
for line in data.lines() {
validate_line(offset, line).map_err(|err| {
err.with_source_code(NamedSource::new(
path.display().to_string(),
data.to_string(),
))
})?;
offset += line.len() + 1;
}
Ok(())
pub fn validate_record_str(record: &str) -> Result<()> {
validate_line(0, record).map_err(|err| err.with_source_code(String::from(record)))
}
fn validate_line(offset: usize, line: &str) -> Result<()> {
@ -156,7 +113,7 @@ fn validate_octet(offset: usize, octet: u8) -> Result<()> {
#[cfg(test)]
mod test {
use crate::records::verify;
use crate::records::validate_record_str;
macro_rules! assert_miette_snapshot {
($diag:expr) => {{
@ -180,104 +137,51 @@ mod test {
#[test]
fn valid_records() -> miette::Result<()> {
verify(
"\
example.com.\n\
example.org.\n\
example.net.\n\
subdomain.example.com.\n\
",
std::path::Path::new("test_records_valid"),
)
for record in [
"example.com.",
"example.org.",
"example.net.",
"subdomain.example.com.",
] {
validate_record_str(record)?;
}
Ok(())
}
#[test]
fn hostname_too_long() {
let err = verify(
"\
example.com.\n\
example.org.\n\
example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.net.\n\
subdomain.example.com.\n\
",
std::path::Path::new("test_records_invalid"),
)
.unwrap_err();
let err = validate_record_str("example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.net.").unwrap_err();
assert_miette_snapshot!(err);
}
#[test]
fn not_fqd() {
let err = verify(
"\
example.com.\n\
example.org.\n\
example.net\n\
subdomain.example.com.\n\
",
std::path::Path::new("test_records_invalid"),
)
.unwrap_err();
let err = validate_record_str("example.net").unwrap_err();
assert_miette_snapshot!(err);
}
#[test]
fn empty_label() {
let err = verify(
"\
example.com.\n\
name..example.org.\n\
example.net.\n\
subdomain.example.com.\n\
",
std::path::Path::new("test_records_invalid"),
)
.unwrap_err();
let err = validate_record_str("name..example.org.").unwrap_err();
assert_miette_snapshot!(err);
}
#[test]
fn label_too_long() {
let err = verify(
"\
example.com.\n\
name.an-entremely-long-label-that-should-not-exist-because-it-goes-against-the-spec.example.org.\n\
example.net.\n\
subdomain.example.com.\n\
",
std::path::Path::new("test_records_invalid"),
)
.unwrap_err();
let err = validate_record_str("name.an-entremely-long-label-that-should-not-exist-because-it-goes-against-the-spec.example.org.").unwrap_err();
assert_miette_snapshot!(err);
}
#[test]
fn invalid_ascii() {
let err = verify(
"\
example.com.\n\
name.this-is-not-aßcii.example.org.\n\
example.net.\n\
subdomain.example.com.\n\
",
std::path::Path::new("test_records_invalid"),
)
.unwrap_err();
let err = validate_record_str("name.this-is-not-ascii-ß.example.org.").unwrap_err();
assert_miette_snapshot!(err);
}
#[test]
fn invalid_octet() {
let err = verify(
"\
example.com.\n\
name.this-character:-is-not-allowed.example.org.\n\
example.net.\n\
subdomain.example.com.\n\
",
std::path::Path::new("test_records_invalid"),
)
.unwrap_err();
let err =
validate_record_str("name.this-character:-is-not-allowed.example.org.").unwrap_err();
assert_miette_snapshot!(err);
}
}

View file

@ -6,11 +6,9 @@ expression: out
]8;;https://en.wikipedia.org/wiki/Fully_qualified_domain_name\(link)]8;;\
× empty label
╭─[test_records_invalid:2:6]
1 │ example.com.
2 │ name..example.org.
╭────
1 │ name..example.org.
· ▲
· ╰── label
3 │ example.net.
╰────
help: each label should have at least one character

View file

@ -6,11 +6,9 @@ expression: out
]8;;https://en.wikipedia.org/wiki/Fully_qualified_domain_name\(link)]8;;\
× hostname too long (260 octets)
╭─[test_records_invalid:3:1]
2 │ example.org.
3 │ example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.net.
╭────
1 │ example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.example.net.
· ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
· ╰── this line
4 │ subdomain.example.com.
╰────
help: fully qualified domain names can be at most 255 characters long

View file

@ -6,11 +6,9 @@ expression: out
]8;;https://en.wikipedia.org/wiki/Hostname#Syntax\(link)]8;;\
× invalid octet: '\xc3'
╭─[test_records_invalid:2:19]
1 │ example.com.
2 │ name.this-is-not-aßcii.example.org.
· ┬
· ╰── octet
3 │ example.net.
╭────
1 │ name.this-is-not-ascii-ß.example.org.
· ┬
· ╰── octet
╰────
help: we only accept ascii characters

View file

@ -6,11 +6,9 @@ expression: out
]8;;https://en.wikipedia.org/wiki/Hostname#Syntax\(link)]8;;\
× invalid octet: ':'
╭─[test_records_invalid:2:20]
1 │ example.com.
2 │ name.this-character:-is-not-allowed.example.org.
╭────
1 │ name.this-character:-is-not-allowed.example.org.
· ┬
· ╰── octet
3 │ example.net.
╰────
help: hostnames are only allowed to contain characters in [a-zA-Z0-9_-]

View file

@ -6,11 +6,9 @@ expression: out
]8;;https://en.wikipedia.org/wiki/Fully_qualified_domain_name\(link)]8;;\
× label too long (78 octets)
╭─[test_records_invalid:2:6]
1 │ example.com.
2 │ name.an-entremely-long-label-that-should-not-exist-because-it-goes-against-the-spec.example.org.
╭────
1 │ name.an-entremely-long-label-that-should-not-exist-because-it-goes-against-the-spec.example.org.
· ───────────────────────────────────────┬──────────────────────────────────────
· ╰── label
3 │ example.net.
╰────
help: labels should be at most 63 octets

View file

@ -6,11 +6,9 @@ expression: out
]8;;https://en.wikipedia.org/wiki/Fully_qualified_domain_name\(link)]8;;\
× not a fully qualified domain name
╭─[test_records_invalid:3:11]
2 │ example.org.
3 │ example.net
╭────
1 │ example.net
· ┬
· ╰── last character
4 │ subdomain.example.com.
╰────
help: hostname should be a fully qualified domain name (end with a '.')