Compare commits

...

136 commits
v0.3.3 ... 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
27 changed files with 1733 additions and 885 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,26 +1,39 @@
on: [push]
jobs:
check-renovaterc:
runs-on: nixos
steps:
- 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
- run: nix --version
- run: nix build --print-build-logs .#
check:
- 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@v4
- run: nix --version
- run: nix flake check --keep-going --verbose --print-build-logs
- 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@v4
- uses: https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- run: nix --version
- name: Generate size report
uses: https://git.salame.cl/jalil/nix-flake-outputs-size@main
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' }}

View file

@ -1,71 +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(deps): cargo update"
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
printf '\n\n' >> 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.GITHUB_TOKEN }}
run: |
set -euo pipefail
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H 'Content-Type: application/json' \
-d "$(
echo '{}' |
jq --arg body "$(cat body.md)" \
--arg title "$COMMIT_MESSAGE" \
--arg head "$BRANCH_NAME" \
'{"body": $body, "title": $title, "head": $head, "base": "main"}'
)" \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls"

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 * * *"
]
}

View file

@ -2,6 +2,55 @@
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

298
Cargo.lock generated
View file

@ -67,34 +67,24 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
version = "3.0.6"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "async-trait"
version = "0.1.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "axum"
version = "0.7.9"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288"
dependencies = [
"async-trait",
"axum-core",
"bytes",
"form_urlencoded",
"futures-util",
"http",
"http-body",
@ -122,9 +112,9 @@ dependencies = [
[[package]]
name = "axum-client-ip"
version = "0.6.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eefda7e2b27e1bda4d6fa8a06b50803b8793769045918bc37ad062d48a6efac"
checksum = "b9329923fe6c30624095e63cb6c25796b32ffbf5d1da8c3a95d1054c301db92a"
dependencies = [
"axum",
"forwarded-header-value",
@ -133,13 +123,12 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.4.5"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"futures-core",
"http",
"http-body",
"http-body-util",
@ -184,21 +173,21 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.6.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bytes"
version = "1.9.0"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.5"
version = "1.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
dependencies = [
"shlex",
]
@ -211,9 +200,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.23"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [
"clap_builder",
"clap_derive",
@ -231,9 +220,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.23"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [
"anstream",
"anstyle",
@ -243,9 +232,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.18"
version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [
"heck",
"proc-macro2",
@ -267,9 +256,9 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "console"
version = "0.15.10"
version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
dependencies = [
"encode_unicode",
"libc",
@ -315,7 +304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
dependencies = [
"nonempty",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -376,9 +365,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "http"
version = "1.2.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
@ -397,12 +386,12 @@ dependencies = [
[[package]]
name = "http-body-util"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-util",
"futures-core",
"http",
"http-body",
"pin-project-lite",
@ -410,9 +399,9 @@ dependencies = [
[[package]]
name = "httparse"
version = "1.9.5"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
@ -421,10 +410,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.5.2"
name = "humantime"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
[[package]]
name = "hyper"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
dependencies = [
"bytes",
"futures-channel",
@ -441,9 +436,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
dependencies = [
"bytes",
"futures-util",
@ -457,13 +452,15 @@ dependencies = [
[[package]]
name = "insta"
version = "1.41.1"
version = "1.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8"
checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084"
dependencies = [
"console",
"lazy_static",
"linked-hash-map",
"once_cell",
"pin-project",
"serde",
"similar",
]
@ -481,9 +478,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.14"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "lazy_static"
@ -493,9 +490,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.169"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "linked-hash-map"
@ -505,15 +502,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]]
name = "log"
version = "0.4.22"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "matchers"
@ -526,9 +523,9 @@ dependencies = [
[[package]]
name = "matchit"
version = "0.7.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "memchr"
@ -538,9 +535,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miette"
version = "7.4.0"
version = "7.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64"
checksum = "1a955165f87b37fd1862df2a59547ac542c77ef6d17c666f619d1ad22dd89484"
dependencies = [
"backtrace",
"backtrace-ext",
@ -552,15 +549,15 @@ dependencies = [
"supports-unicode",
"terminal_size",
"textwrap",
"thiserror",
"unicode-width",
"thiserror 1.0.69",
"unicode-width 0.1.14",
]
[[package]]
name = "miette-derive"
version = "7.4.0"
version = "7.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67"
checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147"
dependencies = [
"proc-macro2",
"quote",
@ -575,9 +572,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
version = "0.8.2"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
dependencies = [
"adler2",
]
@ -611,18 +608,18 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.5"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.20.2"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "overload"
@ -632,9 +629,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "4.1.0"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564"
[[package]]
name = "percent-encoding"
@ -643,10 +640,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
version = "0.2.15"
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
@ -656,18 +673,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.92"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
@ -718,15 +735,14 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ring"
version = "0.17.8"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
@ -739,9 +755,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
version = "0.38.42"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags",
"errno",
@ -752,30 +768,30 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.18"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
version = "1.0.18"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.216"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.216"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
@ -784,9 +800,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.133"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
@ -796,9 +812,9 @@ dependencies = [
[[package]]
name = "serde_path_to_error"
version = "0.1.16"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
dependencies = [
"itoa",
"serde",
@ -842,32 +858,26 @@ dependencies = [
[[package]]
name = "similar"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "smallvec"
version = "1.13.2"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "socket2"
version = "0.5.8"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "strsim"
version = "0.11.1"
@ -897,9 +907,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]]
name = "syn"
version = "2.0.90"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
@ -914,9 +924,9 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "terminal_size"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
dependencies = [
"rustix",
"windows-sys 0.59.0",
@ -924,12 +934,12 @@ dependencies = [
[[package]]
name = "textwrap"
version = "0.16.1"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
dependencies = [
"unicode-linebreak",
"unicode-width",
"unicode-width 0.2.0",
]
[[package]]
@ -938,7 +948,16 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
@ -952,6 +971,17 @@ dependencies = [
"syn",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.8"
@ -964,9 +994,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.42.0"
version = "1.44.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
dependencies = [
"backtrace",
"bytes",
@ -981,9 +1011,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
@ -1097,9 +1127,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.14"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-linebreak"
@ -1113,6 +1143,12 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "untrusted"
version = "0.9.0"
@ -1127,9 +1163,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "valuable"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "wasi"
@ -1139,7 +1175,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "webnsupdate"
version = "0.3.3"
version = "0.3.6"
dependencies = [
"axum",
"axum-client-ip",
@ -1147,9 +1183,13 @@ dependencies = [
"clap",
"clap-verbosity-flag",
"http",
"humantime",
"insta",
"miette",
"ring",
"serde",
"serde_json",
"thiserror 2.0.12",
"tokio",
"tower-http",
"tracing",

View file

@ -1,9 +1,7 @@
cargo-features = ["codegen-backend"]
[package]
description = "An HTTP server using HTTP basic auth to make secure calls to nsupdate"
name = "webnsupdate"
version = "0.3.3"
version = "0.3.6"
edition = "2021"
license-file = "LICENSE"
readme = "README.md"
@ -17,30 +15,34 @@ multiple_crate_versions = "allow"
pedantic = { level = "warn", priority = -1 }
[dependencies]
axum = "0.7"
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 = { version = "3", default-features = false, features = [
"tracing",
] }
http = "1"
humantime = "2.2.0"
miette = { version = "7", features = ["fancy"] }
ring = { version = "0.17", features = ["std"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
tokio = { version = "1", features = ["macros", "rt", "process", "io-util"] }
tower-http = { version = "0.6.2", features = ["validate-request"] }
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"

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,10 +3,18 @@
imports = [
inputs.treefmt-nix.flakeModule
./package.nix
./module.nix
./tests.nix
];
flake.nixosModules =
let
webnsupdate = ../module.nix;
in
{
default = webnsupdate;
inherit webnsupdate;
};
perSystem =
{ pkgs, ... }:
{

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,13 +1,11 @@
{ withSystem, inputs, ... }:
{ inputs, ... }:
{
flake.overlays.default =
final: prev:
withSystem prev.stdenv.hostPlatform.system (
{ self', ... }:
{
inherit (self'.packages) webnsupdate;
}
);
flake.overlays.default = final: prev: {
webnsupdate = prev.callPackage ../default.nix {
inherit (inputs) crane;
pkgSrc = inputs.self;
};
};
perSystem =
{ pkgs, lib, ... }:
@ -33,29 +31,22 @@
};
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"; }
]
);
};
@ -64,16 +55,6 @@
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": 1734808813,
"narHash": "sha256-3aH/0Y6ajIlfy7j52FGZ+s4icVX0oHhqBzRdlOeztqg=",
"lastModified": 1743700120,
"narHash": "sha256-8BjG/P0xnuCyVOXlYRwdI1B8nVtyYLf3oDwPSimqREY=",
"owner": "ipetkov",
"repo": "crane",
"rev": "72e2d02dbac80c8c86bf6bf3e785536acf8ee926",
"rev": "e316f19ee058e6db50075115783be57ac549c389",
"type": "github"
},
"original": {
@ -22,11 +22,11 @@
]
},
"locked": {
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"lastModified": 1743550720,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github"
},
"original": {
@ -37,17 +37,18 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1734424634,
"narHash": "sha256-cHar1vqHOOyC7f1+tVycPoWTfKIaqkoe1Q6TnKzuti4=",
"lastModified": 1743583204,
"narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d3c42f187194c26d9f0309a8ecc469d6c878ce33",
"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": 1734704479,
"narHash": "sha256-MMi74+WckoyEWBRcg/oaGRvXC9BVVxDZNRMpL+72wBI=",
"lastModified": 1743677901,
"narHash": "sha256-eWZln+k+L/VHO69tUTzEmgeDWNQNKIpSUa9nqQgBrSE=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "65712f5af67234dad91a5a4baee986a8b62dbf8f",
"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;
};
};
};
}

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,21 +1,27 @@
use std::{
io::ErrorKind,
net::{IpAddr, SocketAddr},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::{Path, PathBuf},
time::Duration,
};
use axum::{extract::State, routing::get, Router};
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 tracing::{debug, error, info};
use tracing_subscriber::EnvFilter;
mod auth;
mod config;
mod nsupdate;
mod password;
mod records;
@ -26,77 +32,54 @@ const DEFAULT_SALT: &str = "UpdateMyDNS";
#[derive(Debug, Parser)]
struct Opts {
#[command(flatten)]
verbosity: Verbosity<clap_verbosity_flag::WarnLevel>,
/// 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
}
}
}
@ -114,35 +97,86 @@ struct AppState<'a> {
/// 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,
}
#[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) -> miette::Result<Self> {
fn from_args(args: &Opts, config: &config::Config) -> miette::Result<Self> {
let Opts {
verbosity: _,
address: _,
port: _,
password_file: _,
data_dir,
key_file,
insecure,
subcommand: _,
records,
salt: _,
ttl,
ip_source: _,
config_or_command: _,
} = args;
// Set state
let ttl = Duration::from_secs(*ttl);
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 = data_dir.join("last-ip");
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,
// Load DNS records
records: records::load_no_verify(records)?,
ttl: **ttl,
records,
// Load keyfile
key_file: key_file
.as_deref()
@ -155,7 +189,11 @@ impl AppState<'static> {
Ok(&*Box::leak(path.into()))
})
.transpose()?,
ip_file: Box::leak(ip_file.into_boxed_path()),
ip_file,
ip_type: *ip_type,
last_ips: std::sync::Arc::new(tokio::sync::Mutex::new(
load_ip(ip_file)?.unwrap_or_default(),
)),
};
ensure!(
@ -167,7 +205,7 @@ impl AppState<'static> {
}
}
fn load_ip(path: &Path) -> Result<Option<IpAddr>> {
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,
@ -181,13 +219,43 @@ 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();
@ -204,39 +272,44 @@ fn main() -> Result<()> {
.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")?;
debug!("{args:?}");
// process subcommand
if let Some(cmd) = args.subcommand.take() {
return cmd.process(&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)?;
let state = AppState::from_args(&args, &config)?;
let Opts {
verbosity: _,
address: ip,
port,
password_file,
data_dir: _,
key_file: _,
insecure,
subcommand: _,
records: _,
salt,
ttl: _,
ip_source,
config_or_command: _,
} = args;
info!("checking environment");
// Load password hash
let password_hash = password_file
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()?;
@ -249,7 +322,8 @@ fn main() -> Result<()> {
Ok(pass)
})
.transpose()?;
.transpose()
.wrap_err("failed to load password hash")?;
ensure!(
password_hash.is_some() || insecure,
@ -263,46 +337,54 @@ 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::nsupdate(ip, state.ttl, state.key_file, state.records).await {
Ok(status) => {
if !status.success() {
error!("nsupdate failed: code {status}");
bail!("nsupdate returned with code {status}");
}
}
Err(err) => {
error!("Failed to update records with previous IP: {err}");
return Err(err)
.into_diagnostic()
.wrap_err("failed to update records with previous IP");
// 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}");
bail!("nsupdate returned with code {status}");
}
}
Err(err) => {
error!("Failed to update records with previous IP: {err}");
return Err(err)
.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), String::leak(salt)))
app.layer(auth::layer(
Box::leak(pass),
Box::leak(config.password.salt),
))
} else {
app
}
.layer(ip_source.into_extension())
.layer(config.records.ip_source.into_extension())
.with_state(state);
let config::Server { address } = config.server;
// Start services
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>(),
@ -310,37 +392,318 @@ fn main() -> Result<()> {
.await
.into_diagnostic()
})
.wrap_err("failed to run main loop")
}
/// 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>>,
SecureClientIp(ip): SecureClientIp,
ClientIp(ip): ClientIp,
Query(update_params): Query<FritzBoxUpdateParams>,
) -> axum::response::Result<&'static str> {
info!("accepted update from {ip}");
match nsupdate::nsupdate(ip, state.ttl, state.key_file, state.records).await {
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::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);
}
}
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()),
}
}
#[cfg(test)]
mod parse_query_params {
use axum::extract::Query;
use super::FritzBoxUpdateParams;
#[test]
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]
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,
},
)
"#);
}
}

View file

@ -9,12 +9,51 @@ use std::{
use tokio::io::AsyncWriteExt;
use tracing::{debug, warn};
#[tracing::instrument(level = "trace", ret(level = "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(
ip: IpAddr,
ttl: Duration,
key_file: Option<&Path>,
records: &[&str],
actions: impl IntoIterator<Item = Action<'_>>,
) -> std::io::Result<ExitStatus> {
let mut cmd = tokio::process::Command::new("nsupdate");
if let Some(key_file) = key_file {
@ -27,10 +66,13 @@ pub async fn nsupdate(
.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(update_ns_records(ip, ttl, records).as_bytes())
.write_all(&buf)
.await
.inspect_err(|err| warn!("failed to write to the stdin of nsupdate: {err}"))?;
debug!("closing stdin");
stdin
.shutdown()
@ -43,21 +85,16 @@ pub async fn nsupdate(
.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();
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!(cmds, "send\nquit").unwrap();
cmds
writeln!(buf, "send")?;
writeln!(buf, "quit")
}
#[cfg(test)]
@ -66,17 +103,21 @@ mod test {
use insta::assert_snapshot;
use super::update_ns_records;
use super::{update_ns_records, Action};
use crate::DEFAULT_TTL;
#[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###"
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
@ -92,11 +133,15 @@ mod test {
#[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###"
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

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,11 +20,18 @@ 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)
}
}
@ -45,13 +52,16 @@ pub fn hash_identity(username: &str, password: &str, salt: &str) -> Digest {
}
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 '.')