underpost 2.99.4 → 2.99.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.env.development +0 -3
  2. package/.env.production +1 -3
  3. package/.env.test +0 -3
  4. package/README.md +3 -3
  5. package/baremetal/commission-workflows.json +93 -4
  6. package/bin/deploy.js +56 -45
  7. package/cli.md +45 -28
  8. package/examples/static-page/README.md +101 -357
  9. package/examples/static-page/ssr-components/CustomPage.js +1 -13
  10. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +40 -0
  11. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +40 -0
  12. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  13. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  14. package/package.json +3 -4
  15. package/scripts/disk-devices.sh +13 -0
  16. package/scripts/maas-setup.sh +13 -9
  17. package/scripts/rocky-kickstart.sh +294 -0
  18. package/src/cli/baremetal.js +657 -263
  19. package/src/cli/cloud-init.js +120 -120
  20. package/src/cli/env.js +4 -1
  21. package/src/cli/image.js +4 -37
  22. package/src/cli/index.js +56 -11
  23. package/src/cli/kickstart.js +149 -0
  24. package/src/cli/repository.js +3 -1
  25. package/src/cli/run.js +56 -10
  26. package/src/cli/secrets.js +0 -34
  27. package/src/cli/static.js +23 -23
  28. package/src/client/components/core/Docs.js +22 -3
  29. package/src/index.js +30 -5
  30. package/src/server/backup.js +11 -4
  31. package/src/server/client-build-docs.js +1 -1
  32. package/src/server/conf.js +0 -22
  33. package/src/server/cron.js +339 -130
  34. package/src/server/dns.js +10 -0
  35. package/src/server/logger.js +22 -27
  36. package/src/server/tls.js +14 -14
  37. package/examples/static-page/QUICK-REFERENCE.md +0 -481
  38. package/examples/static-page/STATIC-GENERATOR-GUIDE.md +0 -757
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+ # disk-devices.sh
3
+ # List block devices with detailed info, including by-id names
4
+ # Usage: sudo ./disk-devices.sh
5
+ set -euo pipefail
6
+
7
+ sudo bash -c 'printf "NODE\tTYPE\tSIZE\tFSTYPE\tLABEL\tUUID\tMOUNTPOINT\tBY-ID\n"; \
8
+ lsblk -pnl -o NAME,TYPE,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINT | while read -r DEV TYPE SIZE FS LBL UUID MNT; do \
9
+ node=$(readlink -f "$DEV"); \
10
+ byid=$(find /dev/disk/by-id/ -maxdepth 1 -lname "*${node##*/}" ! -name "nvme-eui*" ! -name "dm-uuid*" 2>/dev/null | head -n 1); \
11
+ [ -z "$byid" ] && byid=$(find /dev/disk/by-id/ -maxdepth 1 -lname "*${node##*/}" 2>/dev/null | head -n 1); \
12
+ printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$node" "$TYPE" "$SIZE" "$FS" "$LBL" "$UUID" "$MNT" "$(basename "$byid" 2>/dev/null)"; \
13
+ done' | column -s $'\t' -t
@@ -93,12 +93,16 @@ echo "MAAS setup script completed with new configurations."
93
93
 
94
94
 
95
95
 
96
- echo "Configuring DHCP for fabric-1 (untagged VLAN)..."
96
+ echo "Configuring DHCP for the subnet's fabric (untagged VLAN)..."
97
97
 
98
- # Get the FABRIC_ID for "fabric-1"
98
+ # Derive FABRIC_ID and VLAN_VID from the subnet that matches our CIDR
99
99
  SUBNET_CIDR="192.168.1.0/24"
100
- SUBNET_ID=$(maas "$MAAS_ADMIN_USERNAME" subnets read | jq -r '.[] | select(.cidr == "'"$SUBNET_CIDR"'") | .id')
101
- FABRIC_ID=$(maas "$MAAS_ADMIN_USERNAME" fabrics read | jq -r '.[] | select(.name == "fabric-1") | .id')
100
+ SUBNET_JSON=$(maas "$MAAS_ADMIN_USERNAME" subnets read | jq -r '.[] | select(.cidr == "'"$SUBNET_CIDR"'")')
101
+ SUBNET_ID=$(echo "$SUBNET_JSON" | jq -r '.id')
102
+ FABRIC_ID=$(echo "$SUBNET_JSON" | jq -r '.vlan.fabric_id')
103
+ VLAN_VID=$(echo "$SUBNET_JSON" | jq -r '.vlan.vid')
104
+ FABRIC_NAME=$(echo "$SUBNET_JSON" | jq -r '.vlan.fabric')
105
+ echo "Detected subnet $SUBNET_CIDR on $FABRIC_NAME (fabric ID: $FABRIC_ID, VLAN VID: $VLAN_VID)"
102
106
  RACK_CONTROLLER_ID=$(maas "$MAAS_ADMIN_USERNAME" rack-controllers read | jq -r '[.[] | select(.ip_addresses[] == "'"$IP_ADDRESS"'") | .system_id] | .[0]')
103
107
 
104
108
  if [ -z "$RACK_CONTROLLER_ID" ] || [ "$RACK_CONTROLLER_ID" == "null" ]; then
@@ -114,8 +118,8 @@ fi
114
118
  START_IP="192.168.1.191"
115
119
  END_IP="192.168.1.254"
116
120
 
117
- if [ -z "$FABRIC_ID" ]; then
118
- echo "Error: Could not find FABRIC_ID for 'fabric-1'. Please ensure 'fabric-1' exists in MAAS."
121
+ if [ -z "$FABRIC_ID" ] || [ "$FABRIC_ID" == "null" ]; then
122
+ echo "Error: Could not find FABRIC_ID for subnet '$SUBNET_CIDR'. Please ensure the subnet exists in MAAS."
119
123
  exit 1
120
124
  fi
121
125
 
@@ -123,9 +127,9 @@ fi
123
127
  echo "Creating dynamic IP range from $START_IP to $END_IP..."
124
128
  maas "$MAAS_ADMIN_USERNAME" ipranges create type=dynamic start_ip="$START_IP" end_ip="$END_IP" || echo "Dynamic IP range likely already exists or conflicts. Proceeding..."
125
129
 
126
- # Enable DHCP on the untagged VLAN (VLAN tag 0)
127
- echo "Enabling DHCP on VLAN 0 for fabric-1 (ID: $FABRIC_ID)..."
128
- maas "$MAAS_ADMIN_USERNAME" vlan update "$FABRIC_ID" 0 dhcp_on=true primary_rack="$RACK_CONTROLLER_ID"
130
+ # Enable DHCP on the VLAN associated with the subnet
131
+ echo "Enabling DHCP on VLAN $VLAN_VID for $FABRIC_NAME (fabric ID: $FABRIC_ID)..."
132
+ maas "$MAAS_ADMIN_USERNAME" vlan update "$FABRIC_ID" "$VLAN_VID" dhcp_on=true primary_rack="$RACK_CONTROLLER_ID"
129
133
 
130
134
  echo "Setting gateway IP for subnet $SUBNET_CIDR (ID: $SUBNET_ID) to $IP_ADDRESS..."
131
135
  maas "$MAAS_ADMIN_USERNAME" subnet update $SUBNET_ID gateway_ip=$IP_ADDRESS
@@ -0,0 +1,294 @@
1
+ #!/bin/bash
2
+ # Rocky Linux 9 - Anaconda %pre Ephemeral Commissioning Script
3
+ # Variables ROOT_PASS, AUTHORIZED_KEYS, ADMIN_USER must be set before this script runs.
4
+
5
+ set +e
6
+
7
+ # 1. Set root password
8
+ if [ -n "$ROOT_PASS" ]; then
9
+ echo "root:$ROOT_PASS" | chpasswd 2>/dev/null || \
10
+ echo "$ROOT_PASS" | passwd --stdin root 2>/dev/null || {
11
+ HASH=$(python3 -c "import crypt; print(crypt.crypt('$ROOT_PASS', crypt.mksalt(crypt.METHOD_SHA512)))" 2>/dev/null || \
12
+ openssl passwd -6 "$ROOT_PASS" 2>/dev/null)
13
+ [ -n "$HASH" ] && sed -i "s|^root:[^:]*:|root:$HASH:|" /etc/shadow 2>/dev/null
14
+ }
15
+ fi
16
+
17
+ # 2. SSH authorized_keys for root
18
+ if [ -n "$AUTHORIZED_KEYS" ]; then
19
+ mkdir -p /root/.ssh && chmod 700 /root/.ssh
20
+ echo "$AUTHORIZED_KEYS" > /root/.ssh/authorized_keys
21
+ chmod 600 /root/.ssh/authorized_keys
22
+ fi
23
+
24
+ # 3. Create admin user
25
+ if command -v useradd >/dev/null 2>&1; then
26
+ useradd -m -G wheel "$ADMIN_USER" 2>/dev/null || true
27
+ else
28
+ NEXT_UID=$(awk -F: 'BEGIN{max=999} $3>max && $3<60000{max=$3} END{print max+1}' /etc/passwd 2>/dev/null || echo 1001)
29
+ if ! grep -q "^$ADMIN_USER:" /etc/passwd 2>/dev/null; then
30
+ echo "$ADMIN_USER:x:$NEXT_UID:$NEXT_UID:$ADMIN_USER:/home/$ADMIN_USER:/bin/bash" >> /etc/passwd
31
+ echo "$ADMIN_USER:x:$NEXT_UID:" >> /etc/group 2>/dev/null
32
+ echo "$ADMIN_USER:!:19000:0:99999:7:::" >> /etc/shadow 2>/dev/null
33
+ mkdir -p /home/$ADMIN_USER
34
+ fi
35
+ fi
36
+
37
+ if [ -n "$ROOT_PASS" ]; then
38
+ echo "$ADMIN_USER:$ROOT_PASS" | chpasswd 2>/dev/null || \
39
+ echo "$ROOT_PASS" | passwd --stdin "$ADMIN_USER" 2>/dev/null || {
40
+ HASH=$(python3 -c "import crypt; print(crypt.crypt('$ROOT_PASS', crypt.mksalt(crypt.METHOD_SHA512)))" 2>/dev/null || \
41
+ openssl passwd -6 "$ROOT_PASS" 2>/dev/null)
42
+ [ -n "$HASH" ] && sed -i "s|^$ADMIN_USER:[^:]*:|$ADMIN_USER:$HASH:|" /etc/shadow 2>/dev/null
43
+ }
44
+ fi
45
+
46
+ if [ -n "$AUTHORIZED_KEYS" ]; then
47
+ mkdir -p /home/$ADMIN_USER/.ssh && chmod 700 /home/$ADMIN_USER/.ssh
48
+ echo "$AUTHORIZED_KEYS" > /home/$ADMIN_USER/.ssh/authorized_keys
49
+ chmod 600 /home/$ADMIN_USER/.ssh/authorized_keys
50
+ chown -R $ADMIN_USER:$ADMIN_USER /home/$ADMIN_USER/.ssh 2>/dev/null || true
51
+ fi
52
+
53
+ if [ -d /etc/sudoers.d ]; then
54
+ echo "$ADMIN_USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$ADMIN_USER
55
+ chmod 0440 /etc/sudoers.d/$ADMIN_USER
56
+ elif [ -f /etc/sudoers ]; then
57
+ echo "$ADMIN_USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
58
+ fi
59
+
60
+ # 4. Configure sshd
61
+ if [ -f /etc/ssh/sshd_config ]; then
62
+ sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
63
+ sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
64
+ sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
65
+ sed -i 's/^#*ChallengeResponseAuthentication.*/ChallengeResponseAuthentication yes/' /etc/ssh/sshd_config
66
+ sed -i 's/^#*UsePAM.*/UsePAM yes/' /etc/ssh/sshd_config
67
+ sed -i 's/^#*KbdInteractiveAuthentication.*/KbdInteractiveAuthentication yes/' /etc/ssh/sshd_config
68
+ grep -q "^PasswordAuthentication" /etc/ssh/sshd_config || echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config
69
+ grep -q "^PermitRootLogin" /etc/ssh/sshd_config || echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
70
+ grep -q "^PubkeyAuthentication" /etc/ssh/sshd_config || echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config
71
+ else
72
+ mkdir -p /etc/ssh
73
+ cat > /etc/ssh/sshd_config << 'SSHEOF'
74
+ Port 22
75
+ PermitRootLogin yes
76
+ PubkeyAuthentication yes
77
+ AuthorizedKeysFile .ssh/authorized_keys
78
+ PasswordAuthentication yes
79
+ ChallengeResponseAuthentication yes
80
+ KbdInteractiveAuthentication yes
81
+ UsePAM yes
82
+ Subsystem sftp /usr/libexec/openssh/sftp-server
83
+ SSHEOF
84
+ fi
85
+
86
+ if [ -d /etc/ssh/sshd_config.d ]; then
87
+ cat > /etc/ssh/sshd_config.d/00-underpost.conf << 'SSHCONF'
88
+ PermitRootLogin yes
89
+ PasswordAuthentication yes
90
+ PubkeyAuthentication yes
91
+ KbdInteractiveAuthentication yes
92
+ SSHCONF
93
+ fi
94
+
95
+ if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then
96
+ ssh-keygen -A 2>/dev/null || {
97
+ ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N "" 2>/dev/null
98
+ ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N "" 2>/dev/null
99
+ ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" 2>/dev/null
100
+ }
101
+ fi
102
+
103
+ # 5. Initialize rpmdb and dnf repos
104
+ mkdir -p /var/lib/rpm /var/cache/dnf /var/log/dnf /etc/yum.repos.d /etc/pki/rpm-gpg /etc/dnf/vars
105
+ echo "9" > /etc/dnf/vars/releasever
106
+
107
+ if ! grep -q "^VERSION_ID=" /etc/os-release 2>/dev/null; then
108
+ cat > /etc/os-release << 'OSREL'
109
+ NAME="Rocky Linux"
110
+ VERSION="9"
111
+ ID="rocky"
112
+ ID_LIKE="rhel centos fedora"
113
+ VERSION_ID="9"
114
+ PLATFORM_ID="platform:el9"
115
+ PRETTY_NAME="Rocky Linux 9 (Ephemeral Anaconda)"
116
+ ANSI_COLOR="0;32"
117
+ HOME_URL="https://rockylinux.org/"
118
+ OSREL
119
+ fi
120
+
121
+ [ ! -f /etc/system-release ] && echo "Rocky Linux release 9 (Ephemeral)" > /etc/system-release
122
+
123
+ rpm --initdb 2>/dev/null || rpmdb --initdb 2>/dev/null || {
124
+ if command -v python3 >/dev/null 2>&1; then
125
+ python3 -c "
126
+ import sqlite3, os
127
+ db_path = '/var/lib/rpm/rpmdb.sqlite'
128
+ if not os.path.exists(db_path):
129
+ conn = sqlite3.connect(db_path)
130
+ conn.execute('CREATE TABLE IF NOT EXISTS Packages (key BLOB NOT NULL, data BLOB NOT NULL)')
131
+ conn.execute('CREATE TABLE IF NOT EXISTS Name (key BLOB NOT NULL, data BLOB NOT NULL)')
132
+ conn.execute('CREATE TABLE IF NOT EXISTS Basenames (key BLOB NOT NULL, data BLOB NOT NULL)')
133
+ conn.execute('CREATE TABLE IF NOT EXISTS Installtid (key BLOB NOT NULL, data BLOB NOT NULL)')
134
+ conn.execute('CREATE TABLE IF NOT EXISTS Providename (key BLOB NOT NULL, data BLOB NOT NULL)')
135
+ conn.execute('CREATE TABLE IF NOT EXISTS Requirename (key BLOB NOT NULL, data BLOB NOT NULL)')
136
+ conn.execute('CREATE TABLE IF NOT EXISTS Dirnames (key BLOB NOT NULL, data BLOB NOT NULL)')
137
+ conn.execute('CREATE TABLE IF NOT EXISTS Sha1header (key BLOB NOT NULL, data BLOB NOT NULL)')
138
+ conn.execute('CREATE TABLE IF NOT EXISTS Sigmd5 (key BLOB NOT NULL, data BLOB NOT NULL)')
139
+ conn.commit()
140
+ conn.close()
141
+ " 2>/dev/null
142
+ fi
143
+ }
144
+ chmod -R 755 /var/lib/rpm 2>/dev/null
145
+
146
+ REPO_ARCH=$(uname -m)
147
+
148
+ rpm --import https://dl.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-9 2>/dev/null || \
149
+ curl -fsSL https://dl.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-9 -o /etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9 2>/dev/null || true
150
+
151
+ cat > /etc/dnf/dnf.conf << 'DNFCONF'
152
+ [main]
153
+ gpgcheck=1
154
+ installonly_limit=3
155
+ clean_requirements_on_remove=True
156
+ best=True
157
+ skip_if_unavailable=True
158
+ install_weak_deps=False
159
+ tsflags=nodocs
160
+ DNFCONF
161
+
162
+ cat > /etc/yum.repos.d/rocky-baseos.repo << REPOEOF
163
+ [baseos]
164
+ name=Rocky Linux 9 - BaseOS
165
+ baseurl=http://dl.rockylinux.org/pub/rocky/9/BaseOS/$REPO_ARCH/os/
166
+ gpgcheck=1
167
+ enabled=1
168
+ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
169
+ https://dl.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-9
170
+ skip_if_unavailable=1
171
+ REPOEOF
172
+
173
+ cat > /etc/yum.repos.d/rocky-appstream.repo << REPOEOF2
174
+ [appstream]
175
+ name=Rocky Linux 9 - AppStream
176
+ baseurl=http://dl.rockylinux.org/pub/rocky/9/AppStream/$REPO_ARCH/os/
177
+ gpgcheck=1
178
+ enabled=1
179
+ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
180
+ https://dl.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-9
181
+ skip_if_unavailable=1
182
+ REPOEOF2
183
+
184
+ cat > /etc/yum.repos.d/rocky-extras.repo << REPOEOF3
185
+ [extras]
186
+ name=Rocky Linux 9 - Extras
187
+ baseurl=http://dl.rockylinux.org/pub/rocky/9/extras/$REPO_ARCH/os/
188
+ gpgcheck=1
189
+ enabled=1
190
+ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
191
+ https://dl.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-9
192
+ skip_if_unavailable=1
193
+ REPOEOF3
194
+
195
+ cat > /etc/yum.repos.d/rocky-crb.repo << REPOEOF4
196
+ [crb]
197
+ name=Rocky Linux 9 - CRB
198
+ baseurl=http://dl.rockylinux.org/pub/rocky/9/CRB/$REPO_ARCH/os/
199
+ gpgcheck=1
200
+ enabled=0
201
+ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
202
+ https://dl.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-9
203
+ skip_if_unavailable=1
204
+ REPOEOF4
205
+
206
+ cat > /etc/yum.repos.d/epel.repo << REPOEOF5
207
+ [epel]
208
+ name=Extra Packages for Enterprise Linux 9
209
+ metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-9&arch=$REPO_ARCH
210
+ gpgcheck=1
211
+ enabled=1
212
+ gpgkey=https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-9
213
+ skip_if_unavailable=1
214
+ REPOEOF5
215
+
216
+ rpm --import https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-9 2>/dev/null || \
217
+ curl -fsSL https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-9 -o /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-9 2>/dev/null || true
218
+
219
+ dnf makecache --releasever=9 --quiet 2>/dev/null || dnf makecache --releasever=9 2>/dev/null || true
220
+
221
+ # Install sudo
222
+ if ! command -v sudo >/dev/null 2>&1; then
223
+ dnf install -y --releasever=9 --nogpgcheck sudo 2>/dev/null || \
224
+ dnf install -y --releasever=9 --nogpgcheck --disableplugin='*' sudo 2>/dev/null || true
225
+ fi
226
+
227
+ if command -v sudo >/dev/null 2>&1; then
228
+ mkdir -p /etc/sudoers.d
229
+ echo "$ADMIN_USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$ADMIN_USER
230
+ chmod 0440 /etc/sudoers.d/$ADMIN_USER
231
+ fi
232
+
233
+ cat > /home/$ADMIN_USER/.bash_profile 2>/dev/null << PROFILEEOF
234
+ if ! command -v sudo >/dev/null 2>&1; then
235
+ echo ""
236
+ echo "NOTE: sudo is not available. Use 'su -' to switch to root."
237
+ echo ""
238
+ fi
239
+ PROFILEEOF
240
+ chown $ADMIN_USER:$ADMIN_USER /home/$ADMIN_USER/.bash_profile 2>/dev/null || true
241
+
242
+ # Restart sshd
243
+ if command -v systemctl >/dev/null 2>&1; then
244
+ systemctl restart sshd 2>/dev/null || systemctl restart sshd.service 2>/dev/null
245
+ fi
246
+ SSHD_PID=$(cat /var/run/sshd.pid 2>/dev/null || pidof sshd 2>/dev/null | awk '{print $1}')
247
+ [ -n "$SSHD_PID" ] && kill -HUP "$SSHD_PID" 2>/dev/null
248
+ if ! pidof sshd >/dev/null 2>&1; then
249
+ /usr/sbin/sshd 2>/dev/null || sshd 2>/dev/null
250
+ fi
251
+
252
+ # 6. Status report
253
+ DETECTED_IP=$(ip -4 addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d/ -f1 | head -1)
254
+
255
+ echo ""
256
+ echo "=============================================="
257
+ echo "Underpost: Ephemeral SSHD Setup Complete"
258
+ echo "=============================================="
259
+ echo "Root login: root / (password set: $([ -n "$ROOT_PASS" ] && echo 'yes' || echo 'no'))"
260
+ echo "Admin user: $ADMIN_USER / (password set: $([ -n "$ROOT_PASS" ] && echo 'yes' || echo 'no'))"
261
+ echo "SSH keys: $([ -n "$AUTHORIZED_KEYS" ] && echo 'configured' || echo 'NOT configured')"
262
+ echo "sshd status: $(pidof sshd >/dev/null 2>&1 && echo "running (pid $(pidof sshd))" || echo 'NOT running')"
263
+ echo "sudo: $(command -v sudo >/dev/null 2>&1 && echo 'installed' || echo 'NOT available (use su -)')"
264
+ echo "IP address: $DETECTED_IP"
265
+ echo "=============================================="
266
+
267
+ # Physical console display (printf with \r\n for proper line breaks on serial/physical consoles)
268
+ {
269
+ printf "\r\n"
270
+ printf "██╗░░░██╗███╗░░██╗██████╗░███████╗██████╗░██████╗░░█████╗░░██████╗████████╗\r\n"
271
+ printf "██║░░░██║████╗░██║██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝\r\n"
272
+ printf "██║░░░██║██╔██╗██║██║░░██║█████╗░░██████╔╝██████╔╝██║░░██║╚█████╗░░░░██║░░░\r\n"
273
+ printf "██║░░░██║██║╚████║██║░░██║██╔══╝░░██╔══██╗██╔═══╝░██║░░██║░╚═══██╗░░░██║░░░\r\n"
274
+ printf "╚██████╔╝██║░╚███║██████╔╝███████╗██║░░██║██║░░░░░╚█████╔╝██████╔╝░░░██║░░░\r\n"
275
+ printf "░╚═════╝░╚═╝░░╚══╝╚═════╝░╚══════╝╚═╝░░╚═╝╚═╝░░░░░░╚════╝░╚═════╝░░░░╚═╝░░░\r\n"
276
+ printf "==============================================\r\n"
277
+ printf " Underpost Ephemeral Commissioning Active\r\n"
278
+ printf "==============================================\r\n"
279
+ printf " SSH as: root@%s\r\n" "$DETECTED_IP"
280
+ printf " or: %s@%s\r\n" "$ADMIN_USER" "$DETECTED_IP"
281
+ printf " Key: %s\r\n" "$([ -n "$AUTHORIZED_KEYS" ] && echo 'pubkey auth enabled' || echo 'password only')"
282
+ printf " dnf: ready (BaseOS, AppStream, Extras, EPEL)\r\n"
283
+ printf "==============================================\r\n"
284
+ printf "\r\n"
285
+ } > /dev/console 2>/dev/null || true
286
+
287
+ # 7. Infinite wait loop - keep Anaconda live environment active
288
+ while true; do
289
+ sleep 60
290
+ if ! pidof sshd >/dev/null 2>&1; then
291
+ echo "$(date): sshd not running, restarting..." >> /tmp/ks-pre.log
292
+ /usr/sbin/sshd 2>/dev/null || sshd 2>/dev/null
293
+ fi
294
+ done