@@ -13,8 +13,22 @@ Resolverele interne sunt: |
||
| 13 | 13 |
- jumper: `192.168.2.100` |
| 14 | 14 |
- as01: `192.168.2.2` |
| 15 | 15 |
|
| 16 |
-Sync-ul ramane explicit: |
|
| 16 |
+## 2026-06-10 - Prompt Resolver Publishing |
|
| 17 |
+ |
|
| 18 |
+Incident: un vhost nou, `git.madagascar.xdev.ro`, exista in SQLite si in |
|
| 19 |
+manifestul generat, dar nu fusese aplicat pe resolvere. Pe macOS, o incercare |
|
| 20 |
+de rezolvare esuata poate ramane cache-uita agresiv, deci publicarea DNS trebuie |
|
| 21 |
+sa urmeze rapid dupa editarea registry-ului. |
|
| 22 |
+ |
|
| 23 |
+Decizie: |
|
| 24 |
+ |
|
| 25 |
+- modificarile DNS facute prin aplicatie regenereaza `config/local-hosts.tsv` |
|
| 26 |
+- aplicatia atinge `var/dns-publish.trigger` |
|
| 27 |
+- `host-manager-dns-publish.path` porneste `host-manager-dns-publish.service` |
|
| 28 |
+- serviciul oneshot ruleaza sync-ul privilegiat existent: |
|
| 17 | 29 |
|
| 18 | 30 |
```bash |
| 19 | 31 |
./scripts/sync_local_hosts.sh --apply --verify |
| 20 | 32 |
``` |
| 33 |
+ |
|
| 34 |
+Sync-ul manual ramane disponibil pentru interventii operationale si audit. |
|
@@ -114,8 +114,8 @@ Secretul nu se comite în repo. Dacă avem nevoie de integrare cu un manager de |
||
| 114 | 114 |
|
| 115 | 115 |
1. Hosturile se editează în aplicație; store-ul runtime este `var/host-manager.sqlite`. |
| 116 | 116 |
2. Operatorii autentificați pot descărca `/download/hosts.yaml`, `/download/local-hosts.tsv` sau `/download/monitoring.json`. |
| 117 |
-3. Pentru DNS local, butonul `Write local-hosts.tsv` regenerează `config/local-hosts.tsv` din SQLite. |
|
| 118 |
-4. Sincronizarea efectivă către jumper și as01 rămâne: |
|
| 117 |
+3. Pentru DNS local, schimbările făcute în aplicație regenerează `config/local-hosts.tsv` din SQLite. |
|
| 118 |
+4. Aplicația atinge `var/dns-publish.trigger`, iar pe jumper `host-manager-dns-publish.path` pornește prompt: |
|
| 119 | 119 |
|
| 120 | 120 |
```bash |
| 121 | 121 |
./scripts/sync_local_hosts.sh --apply --verify |
@@ -140,12 +140,13 @@ Confirmarea unui WO: |
||
| 140 | 140 |
- elimină numele declarate din registry-ul SQLite |
| 141 | 141 |
- marchează WO-ul ca `confirmed` |
| 142 | 142 |
- regenerează `config/local-hosts.tsv` |
| 143 |
-- nu rulează automat sync-ul către resolvere |
|
| 143 |
+- declanșează publicarea către resolverele locale prin `host-manager-dns-publish.path` |
|
| 144 | 144 |
|
| 145 |
-După confirmare, operatorul verifică exportul și rulează explicit: |
|
| 145 |
+După confirmare, operatorul poate verifica manual resolverele cu: |
|
| 146 | 146 |
|
| 147 | 147 |
```bash |
| 148 |
-./scripts/sync_local_hosts.sh --apply --verify |
|
| 148 |
+dig @192.168.2.100 nume.madagascar.xdev.ro +short |
|
| 149 |
+dig @192.168.2.2 nume.madagascar.xdev.ro +short |
|
| 149 | 150 |
``` |
| 150 | 151 |
|
| 151 | 152 |
Primul WO curent este pentru retragerea numelor locale `pmx.*`/`pbs.*` create istoric pentru vhosturi nginx cu certificate Let's Encrypt. Odată cu CA-ul local, aceste nume nu mai trebuie să existe ca vhosturi separate pentru interfețele Proxmox/PBS, dar rămân publicate până când checklist-ul operațional este complet și WO-ul este confirmat. |
@@ -126,7 +126,8 @@ Aplică și verifică: |
||
| 126 | 126 |
# 1. Adaugă hostul în Madagascar Local Authority. |
| 127 | 127 |
# Aplicația păstrează registry-ul în var/host-manager.sqlite. |
| 128 | 128 |
|
| 129 |
-# 2. Aplică pe ambele resolvere |
|
| 129 |
+# 2. Publicarea către resolvere este declanșată automat pe jumper. |
|
| 130 |
+# Pentru intervenție manuală sau re-verificare: |
|
| 130 | 131 |
./scripts/sync_local_hosts.sh --apply --verify |
| 131 | 132 |
|
| 132 | 133 |
# 3. Verificare manuală, dacă e nevoie |
@@ -42,6 +42,8 @@ sudo dnf install nginx |
||
| 42 | 42 |
/etc/xdev/host-manager.env |
| 43 | 43 |
/etc/systemd/system/host-manager.service |
| 44 | 44 |
/etc/systemd/system/host-manager-mdns.service |
| 45 |
+/etc/systemd/system/host-manager-dns-publish.path |
|
| 46 |
+/etc/systemd/system/host-manager-dns-publish.service |
|
| 45 | 47 |
/etc/nginx/conf.d/madagascar.xdev.ro.conf |
| 46 | 48 |
``` |
| 47 | 49 |
|
@@ -55,6 +57,8 @@ sudo install -d -o host-manager -g host-manager /usr/local/xdev-host-manager |
||
| 55 | 57 |
sudo install -d -m 0750 /etc/xdev |
| 56 | 58 |
sudo install -m 0644 deploy/jumper/host-manager.service /etc/systemd/system/host-manager.service |
| 57 | 59 |
sudo install -m 0644 deploy/jumper/host-manager-mdns.service /etc/systemd/system/host-manager-mdns.service |
| 60 |
+sudo install -m 0644 deploy/jumper/host-manager-dns-publish.path /etc/systemd/system/host-manager-dns-publish.path |
|
| 61 |
+sudo install -m 0644 deploy/jumper/host-manager-dns-publish.service /etc/systemd/system/host-manager-dns-publish.service |
|
| 58 | 62 |
sudo install -m 0644 deploy/jumper/nginx-host-manager.conf /etc/nginx/conf.d/madagascar.xdev.ro.conf |
| 59 | 63 |
``` |
| 60 | 64 |
|
@@ -73,6 +77,7 @@ Validare: |
||
| 73 | 77 |
sudo systemctl daemon-reload |
| 74 | 78 |
sudo systemctl enable --now host-manager |
| 75 | 79 |
sudo systemctl enable --now host-manager-mdns |
| 80 |
+sudo systemctl enable --now host-manager-dns-publish.path |
|
| 76 | 81 |
sudo nginx -t |
| 77 | 82 |
sudo systemctl reload nginx |
| 78 | 83 |
curl -fsS http://127.0.0.1:8088/healthz |
@@ -97,6 +102,18 @@ madagascar.xdev.ro -> jumper.madagascar.xdev.ro |
||
| 97 | 102 |
|
| 98 | 103 |
Nu se adaugă wildcard local. Doar acest nume exact trebuie publicat. |
| 99 | 104 |
|
| 105 |
+Schimbările DNS făcute prin aplicație regenerează `config/local-hosts.tsv` și |
|
| 106 |
+ating `var/dns-publish.trigger`. Pe jumper, |
|
| 107 |
+`host-manager-dns-publish.path` pornește imediat |
|
| 108 |
+`host-manager-dns-publish.service`, care rulează: |
|
| 109 |
+ |
|
| 110 |
+```bash |
|
| 111 |
+/usr/local/xdev-host-manager/scripts/sync_local_hosts.sh --apply --verify |
|
| 112 |
+``` |
|
| 113 |
+ |
|
| 114 |
+Serviciul oneshot rulează ca root prin systemd, deoarece publicarea atinge |
|
| 115 |
+`/etc/hosts`, `dnscrypt-proxy`, `systemd-resolved` și DNS-ul static de pe as01. |
|
| 116 |
+ |
|
| 100 | 117 |
## Runtime store |
| 101 | 118 |
|
| 102 | 119 |
`var/host-manager.sqlite` este sursa de adevăr pentru registry și Work Orders. La prima pornire, aplicația seed-uiește documentele lipsă din `config/hosts.yaml` și `config/work-orders.yaml`; ulterior push-urile de cod nu trebuie să înlocuiască baza runtime. |
@@ -0,0 +1,10 @@ |
||
| 1 |
+[Unit] |
|
| 2 |
+Description=Watch Madagascar Local Authority DNS publish trigger |
|
| 3 |
+After=host-manager.service |
|
| 4 |
+ |
|
| 5 |
+[Path] |
|
| 6 |
+PathChanged=/usr/local/xdev-host-manager/var/dns-publish.trigger |
|
| 7 |
+Unit=host-manager-dns-publish.service |
|
| 8 |
+ |
|
| 9 |
+[Install] |
|
| 10 |
+WantedBy=multi-user.target |
|
@@ -0,0 +1,9 @@ |
||
| 1 |
+[Unit] |
|
| 2 |
+Description=Publish Madagascar local DNS records to resolvers |
|
| 3 |
+After=network-online.target host-manager.service |
|
| 4 |
+Wants=network-online.target |
|
| 5 |
+ |
|
| 6 |
+[Service] |
|
| 7 |
+Type=oneshot |
|
| 8 |
+WorkingDirectory=/usr/local/xdev-host-manager |
|
| 9 |
+ExecStart=/usr/local/xdev-host-manager/scripts/sync_local_hosts.sh --apply --verify |
|
@@ -6,6 +6,7 @@ HOST_MANAGER_PORT=8088 |
||
| 6 | 6 |
HOST_MANAGER_DB=/usr/local/xdev-host-manager/var/host-manager.sqlite |
| 7 | 7 |
HOST_MANAGER_DATA=/usr/local/xdev-host-manager/config/hosts.yaml |
| 8 | 8 |
HOST_MANAGER_LOCAL_HOSTS_TSV=/usr/local/xdev-host-manager/config/local-hosts.tsv |
| 9 |
+HOST_MANAGER_DNS_PUBLISH_TRIGGER=/usr/local/xdev-host-manager/var/dns-publish.trigger |
|
| 9 | 10 |
|
| 10 | 11 |
# Base32 TOTP secret. Required for write access. |
| 11 | 12 |
HOST_MANAGER_TOTP_SECRET=CHANGE_ME_BASE32 |
@@ -118,6 +118,9 @@ printf 'revision=%s\nbranch=%s\ndirty=%s\ndeployed_at=%s\n' \ |
||
| 118 | 118 |
ssh "$TARGET_HOST" "cd '$TARGET_DIR' && perl -c scripts/host_manager.pl >/dev/null && perl -c scripts/mdns_host_seed.pl >/dev/null" |
| 119 | 119 |
|
| 120 | 120 |
if [[ "$RESTART" -eq 1 ]]; then |
| 121 |
+ ssh "$TARGET_HOST" "sudo -n install -m 0644 '$TARGET_DIR/deploy/jumper/host-manager-dns-publish.path' /etc/systemd/system/host-manager-dns-publish.path" |
|
| 122 |
+ ssh "$TARGET_HOST" "sudo -n install -m 0644 '$TARGET_DIR/deploy/jumper/host-manager-dns-publish.service' /etc/systemd/system/host-manager-dns-publish.service" |
|
| 123 |
+ ssh "$TARGET_HOST" "sudo -n systemctl daemon-reload && sudo -n systemctl enable --now host-manager-dns-publish.path >/dev/null" |
|
| 121 | 124 |
ssh "$TARGET_HOST" "sudo -n systemctl restart host-manager" |
| 122 | 125 |
fi |
| 123 | 126 |
|
@@ -24,6 +24,7 @@ my %opt = ( |
||
| 24 | 24 |
db => $ENV{HOST_MANAGER_DB} || "$project_dir/var/host-manager.sqlite",
|
| 25 | 25 |
data => $ENV{HOST_MANAGER_DATA} || "$project_dir/config/hosts.yaml",
|
| 26 | 26 |
local_hosts_tsv => $ENV{HOST_MANAGER_LOCAL_HOSTS_TSV} || "$project_dir/config/local-hosts.tsv",
|
| 27 |
+ dns_publish_trigger => $ENV{HOST_MANAGER_DNS_PUBLISH_TRIGGER} || "$project_dir/var/dns-publish.trigger",
|
|
| 27 | 28 |
work_orders => $ENV{HOST_MANAGER_WORK_ORDERS} || "$project_dir/config/work-orders.yaml",
|
| 28 | 29 |
); |
| 29 | 30 |
my $print_local_hosts_tsv = 0; |
@@ -95,6 +96,8 @@ Environment: |
||
| 95 | 96 |
HOST_MANAGER_DB Defaults to var/host-manager.sqlite. |
| 96 | 97 |
HOST_MANAGER_DATA Defaults to config/hosts.yaml. |
| 97 | 98 |
HOST_MANAGER_LOCAL_HOSTS_TSV Defaults to config/local-hosts.tsv. |
| 99 |
+ HOST_MANAGER_DNS_PUBLISH_TRIGGER |
|
| 100 |
+ Defaults to var/dns-publish.trigger. |
|
| 98 | 101 |
HOST_MANAGER_WORK_ORDERS Defaults to config/work-orders.yaml. |
| 99 | 102 |
--print-local-hosts-tsv Print the runtime DNS manifest and exit. |
| 100 | 103 |
|
@@ -257,10 +260,8 @@ sub handle_client {
|
||
| 257 | 260 |
} |
| 258 | 261 |
if ($path eq '/api/render/local-hosts-tsv') {
|
| 259 | 262 |
my $registry = load_registry(); |
| 260 |
- my $content = render_local_hosts_tsv($registry); |
|
| 261 |
- backup_file($opt{local_hosts_tsv});
|
|
| 262 |
- write_file($opt{local_hosts_tsv}, $content);
|
|
| 263 |
- return send_json($client, 200, { ok => json_bool(1), file => $opt{local_hosts_tsv} });
|
|
| 263 |
+ my $publish = publish_dns_change($registry, 'manual-render'); |
|
| 264 |
+ return send_json($client, 200, { ok => json_bool(1), file => $opt{local_hosts_tsv}, dns_publish => $publish });
|
|
| 264 | 265 |
} |
| 265 | 266 |
} |
| 266 | 267 |
|
@@ -283,6 +284,7 @@ sub save_registry {
|
||
| 283 | 284 |
$registry->{updated_at} = iso_now();
|
| 284 | 285 |
normalize_registry_policy($registry); |
| 285 | 286 |
save_registry_to_db($registry); |
| 287 |
+ return publish_dns_change($registry, 'registry-save'); |
|
| 286 | 288 |
} |
| 287 | 289 |
|
| 288 | 290 |
sub load_work_orders {
|
@@ -488,16 +490,15 @@ sub confirm_work_order {
|
||
| 488 | 490 |
$work_order->{confirmed_at} = iso_now();
|
| 489 | 491 |
$work_order->{result} = scalar(@$results) . ' action(s) applied';
|
| 490 | 492 |
|
| 491 |
- save_registry($registry); |
|
| 493 |
+ my $publish = save_registry($registry); |
|
| 492 | 494 |
save_work_orders($orders); |
| 493 |
- backup_file($opt{local_hosts_tsv});
|
|
| 494 |
- write_file($opt{local_hosts_tsv}, render_local_hosts_tsv($registry));
|
|
| 495 | 495 |
|
| 496 | 496 |
return send_json($client, 200, {
|
| 497 | 497 |
ok => json_bool(1), |
| 498 | 498 |
work_order => $work_order, |
| 499 | 499 |
results => $results, |
| 500 | 500 |
local_hosts_tsv => $opt{local_hosts_tsv},
|
| 501 |
+ dns_publish => $publish, |
|
| 501 | 502 |
}); |
| 502 | 503 |
} |
| 503 | 504 |
|
@@ -835,7 +836,8 @@ sub reassign_vhost {
|
||
| 835 | 836 |
my $err = $@ || 'vhost_reassign_failed'; |
| 836 | 837 |
return send_json($client, 409, { error => 'vhost_reassign_failed', detail => clean_scalar($err) });
|
| 837 | 838 |
} |
| 838 |
- return send_json($client, 200, { ok => json_bool(1), vhost_fqdn => $vhost, host_fqdn => $target_fqdn, previous_host_fqdn => $current_fqdn });
|
|
| 839 |
+ my $publish = publish_dns_change(load_registry(), 'vhost-reassign'); |
|
| 840 |
+ return send_json($client, 200, { ok => json_bool(1), vhost_fqdn => $vhost, host_fqdn => $target_fqdn, previous_host_fqdn => $current_fqdn, dns_publish => $publish });
|
|
| 839 | 841 |
} |
| 840 | 842 |
|
| 841 | 843 |
sub upsert_vhost {
|
@@ -873,7 +875,8 @@ sub upsert_vhost {
|
||
| 873 | 875 |
my $err = $@ || 'vhost_upsert_failed'; |
| 874 | 876 |
return send_json($client, 409, { error => 'vhost_upsert_failed', detail => clean_scalar($err) });
|
| 875 | 877 |
} |
| 876 |
- return send_json($client, 200, { ok => json_bool(1), vhost_fqdn => $vhost, host_fqdn => $target_fqdn, previous_host_fqdn => $current_fqdn || '' });
|
|
| 878 |
+ my $publish = publish_dns_change(load_registry(), 'vhost-upsert'); |
|
| 879 |
+ return send_json($client, 200, { ok => json_bool(1), vhost_fqdn => $vhost, host_fqdn => $target_fqdn, previous_host_fqdn => $current_fqdn || '', dns_publish => $publish });
|
|
| 877 | 880 |
} |
| 878 | 881 |
|
| 879 | 882 |
sub delete_vhost {
|
@@ -911,7 +914,8 @@ sub delete_vhost {
|
||
| 911 | 914 |
my $err = $@ || 'vhost_delete_failed'; |
| 912 | 915 |
return send_json($client, 409, { error => 'vhost_delete_failed', detail => clean_scalar($err) });
|
| 913 | 916 |
} |
| 914 |
- return send_json($client, 200, { ok => json_bool(1), vhost_fqdn => $vhost, previous_host_fqdn => $current_fqdn });
|
|
| 917 |
+ my $publish = publish_dns_change(load_registry(), 'vhost-delete'); |
|
| 918 |
+ return send_json($client, 200, { ok => json_bool(1), vhost_fqdn => $vhost, previous_host_fqdn => $current_fqdn, dns_publish => $publish });
|
|
| 915 | 919 |
} |
| 916 | 920 |
|
| 917 | 921 |
sub set_host_certificate {
|
@@ -2163,6 +2167,33 @@ sub backup_file {
|
||
| 2163 | 2167 |
write_file("$backup_dir/$name.$stamp.bak", read_file($path));
|
| 2164 | 2168 |
} |
| 2165 | 2169 |
|
| 2170 |
+sub publish_dns_change {
|
|
| 2171 |
+ my ($registry, $reason) = @_; |
|
| 2172 |
+ $reason = clean_scalar($reason || 'registry-change'); |
|
| 2173 |
+ |
|
| 2174 |
+ backup_file($opt{local_hosts_tsv});
|
|
| 2175 |
+ write_file($opt{local_hosts_tsv}, render_local_hosts_tsv($registry));
|
|
| 2176 |
+ |
|
| 2177 |
+ my $trigger = $opt{dns_publish_trigger} || '';
|
|
| 2178 |
+ return {
|
|
| 2179 |
+ queued => json_bool(0), |
|
| 2180 |
+ file => $opt{local_hosts_tsv},
|
|
| 2181 |
+ reason => $reason, |
|
| 2182 |
+ } unless length $trigger; |
|
| 2183 |
+ |
|
| 2184 |
+ ensure_parent_dir($trigger); |
|
| 2185 |
+ open my $fh, '>>', $trigger or die "Cannot write DNS publish trigger $trigger: $!"; |
|
| 2186 |
+ print {$fh} iso_now() . "\t$reason\n";
|
|
| 2187 |
+ close $fh or die "Cannot close DNS publish trigger $trigger: $!"; |
|
| 2188 |
+ |
|
| 2189 |
+ return {
|
|
| 2190 |
+ queued => json_bool(1), |
|
| 2191 |
+ file => $opt{local_hosts_tsv},
|
|
| 2192 |
+ trigger => $trigger, |
|
| 2193 |
+ reason => $reason, |
|
| 2194 |
+ }; |
|
| 2195 |
+} |
|
| 2196 |
+ |
|
| 2166 | 2197 |
my $db_handle; |
| 2167 | 2198 |
my $db_seeded = 0; |
| 2168 | 2199 |
|