-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathbuild-iso.sh
More file actions
executable file
·1110 lines (953 loc) · 40.6 KB
/
Copy pathbuild-iso.sh
File metadata and controls
executable file
·1110 lines (953 loc) · 40.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
#
# TensorAgent OS — Master Build Script
#
# Builds the entire OS from an Ubuntu 24.04 LTS (Noble) base:
# 1. Creates rootfs via debootstrap
# 2. Installs system dependencies (Qt6, Node.js, PAM, Wayland)
# 3. Integrates OpenWhale AI platform + WhaleOS shell
# 4. Configures systemd services and boot
# 5. Generates bootable ISO (via xorriso)
#
# Supports:
# x86_64 (native or cross-compile)
# aarch64 (cross-compile via qemu-user-static)
#
# Requirements:
# - Linux x86_64 host (Ubuntu 22.04+ / Debian 12+)
# - 8GB+ RAM, 20GB+ disk
# - Build tools: debootstrap, xorriso, mtools, qemu-user-static (for ARM)
#
# Usage: ./scripts/build-iso.sh [--arch=x86_64|aarch64] [--clean] [--skip-chromium]
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
AINUX_ROOT="$(dirname "$SCRIPT_DIR")"
BUILD_DIR="${AINUX_ROOT}/build"
ROOTFS_DIR="${BUILD_DIR}/rootfs"
ISO_DIR="${BUILD_DIR}/iso"
ARCH="x86_64"
CLEAN=false
SKIP_CHROMIUM=false
# Parse arguments
for arg in "$@"; do
case $arg in
--arch=*) ARCH="${arg#*=}" ;;
--clean) CLEAN=true ;;
--skip-chromium) SKIP_CHROMIUM=true ;;
--help)
echo "Usage: $0 [--arch=x86_64|aarch64] [--clean] [--skip-chromium]"
echo " --arch=ARCH Target architecture (default: x86_64)"
echo " --clean Remove build directory and start fresh"
echo " --skip-chromium Use Ubuntu's packaged Chromium instead of building"
exit 0
;;
esac
done
# Resolve Ubuntu/Debian arch name
case "$ARCH" in
x86_64) DEB_ARCH="amd64" ; KERNEL_ARCH="amd64" ;;
aarch64) DEB_ARCH="arm64" ; KERNEL_ARCH="arm64" ;;
*) echo "ERROR: Unsupported arch: $ARCH"; exit 1 ;;
esac
echo ""
echo " 🐋 ═══════════════════════════════════════════════════════"
echo " 🐋 TensorAgent OS Build System"
echo " 🐋 Target: ${ARCH} (Ubuntu 24.04 Noble)"
echo " 🐋 ═══════════════════════════════════════════════════════"
echo ""
# ─── Prerequisites Check ────────────────────────────────────────
echo "[1/8] Checking prerequisites..."
REQUIRED_CMDS="debootstrap xorriso mtools mksquashfs"
MISSING=""
for cmd in $REQUIRED_CMDS; do
if ! command -v "$cmd" &> /dev/null; then
MISSING="$MISSING $cmd"
fi
done
if [ -n "$MISSING" ]; then
echo "Missing tools:$MISSING"
echo "Install: sudo apt install debootstrap xorriso mtools squashfs-tools"
exit 1
fi
# Cross-compilation check for aarch64 (only needed when host is not ARM64)
HOST_ARCH=$(uname -m)
if [ "$ARCH" = "aarch64" ] && [ "$HOST_ARCH" != "aarch64" ] && ! command -v qemu-aarch64-static &> /dev/null; then
echo "ERROR: qemu-user-static needed for aarch64 cross-builds on $HOST_ARCH"
echo "Install: sudo apt install qemu-user-static binfmt-support"
exit 1
fi
echo " ✓ Prerequisites OK"
# ─── Clean (optional) ───────────────────────────────────────────
if [ "$CLEAN" = true ]; then
echo "[*] Cleaning build directory..."
sudo rm -rf "$BUILD_DIR"
fi
mkdir -p "$BUILD_DIR" "$ISO_DIR"
# Resolve debootstrap mirror
if [ "$DEB_ARCH" = "arm64" ]; then
DEBOOTSTRAP_MIRROR="http://ports.ubuntu.com/ubuntu-ports"
else
DEBOOTSTRAP_MIRROR="http://archive.ubuntu.com/ubuntu"
fi
echo "[2/8] Creating Ubuntu Noble rootfs (${DEB_ARCH})..."
if [ ! -d "$ROOTFS_DIR" ] || [ "$CLEAN" = true ]; then
sudo debootstrap --arch="$DEB_ARCH" \
--include=systemd,systemd-sysv,dbus,sudo,bash,curl,wget,git,openssh-server \
noble "$ROOTFS_DIR" "$DEBOOTSTRAP_MIRROR"
echo " ✓ Base rootfs created"
else
echo " ✓ Rootfs already exists, skipping debootstrap"
fi
# ─── Configure Rootfs ──────────────────────────────────────────
echo "[3/8] Configuring rootfs..."
# Mount pseudo-filesystems for chroot
sudo mount --bind /dev "${ROOTFS_DIR}/dev" 2>/dev/null || true
sudo mount --bind /proc "${ROOTFS_DIR}/proc" 2>/dev/null || true
sudo mount --bind /sys "${ROOTFS_DIR}/sys" 2>/dev/null || true
# Set hostname
echo "tensoragent" | sudo tee "${ROOTFS_DIR}/etc/hostname" > /dev/null
# Add hostname to /etc/hosts so sudo doesn't complain
sudo bash -c "grep -q tensoragent '${ROOTFS_DIR}/etc/hosts' || echo '127.0.1.1 tensoragent' >> '${ROOTFS_DIR}/etc/hosts'"
# Configure apt sources — use correct mirror per architecture
if [ "$DEB_ARCH" = "arm64" ]; then
UBUNTU_MIRROR="http://ports.ubuntu.com/ubuntu-ports"
else
UBUNTU_MIRROR="http://archive.ubuntu.com/ubuntu"
fi
sudo tee "${ROOTFS_DIR}/etc/apt/sources.list" > /dev/null << SOURCES
deb ${UBUNTU_MIRROR} noble main restricted universe multiverse
deb ${UBUNTU_MIRROR} noble-updates main restricted universe multiverse
deb ${UBUNTU_MIRROR} noble-security main restricted universe multiverse
SOURCES
echo " ✓ Base configuration applied"
# ─── Install System Dependencies ───────────────────────────────
echo "[4/8] Installing system dependencies in chroot..."
# Disable -e: chroot packages trigger systemd/dbus scripts that fail harmlessly
set +e
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
# Chroot environments cannot run systemd/dbus — package post-install
# scripts will fail harmlessly. Prevent these from aborting the build.
set +e
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
# Core system + build tools
apt-get install -y -qq \
build-essential pkg-config g++ \
linux-image-generic grub-efi \
systemd-boot linux-firmware \
2>/dev/null || apt-get install -y -qq build-essential pkg-config g++ linux-image-generic grub-efi
# Graphics & Wayland
apt-get install -y -qq \
mesa-utils libgl1-mesa-dri libglx-mesa0 libegl-mesa0 \
wayland-protocols libwayland-dev weston cage \
xwayland xdg-utils
# Qt6 (for WhaleOS shell)
apt-get install -y -qq \
qt6-base-dev qt6-declarative-dev \
qt6-wayland-dev qt6-wayland \
qml6-module-qtquick qml6-module-qtquick-controls \
qml6-module-qtquick-window qml6-module-qtquick-templates \
qml6-module-qtquick-layouts qml6-module-qtwayland-compositor \
qml6-module-qtqml-workerscript qml6-module-qtqml \
libqt6waylandcompositor6 \
2>/dev/null || echo " ⚠ Some Qt6 packages unavailable, will build from source"
# PAM (for secure authentication)
apt-get install -y -qq libpam0g-dev 2>/dev/null || apt-get install -y -qq libpam-dev 2>/dev/null || echo " ⚠ PAM dev package not found, may need manual install"
# Node.js 22.x
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y -qq nodejs
# Python & AI tools
apt-get install -y -qq python3 python3-pip python3-venv sqlite3
# ── Enterprise Security Packages ─────────────────────────────
echo " → Installing enterprise security packages..."
apt-get install -y -qq \
apparmor apparmor-utils apparmor-profiles \
auditd audispd-plugins \
ufw \
fail2ban \
aide \
rkhunter \
libpam-pwquality \
2>/dev/null || echo " ⚠ Some security packages unavailable"
# SSSD for enterprise identity (AD/LDAP) — installed but not enabled
apt-get install -y -qq \
sssd sssd-tools sssd-ldap sssd-ad \
realmd adcli krb5-user libnss-sss libpam-sss \
2>/dev/null || echo " ⚠ SSSD packages unavailable (install manually for AD/LDAP)"
# Multimedia & utilities
apt-get install -y -qq \
ffmpeg mousepad galculator \
htop tmux ripgrep jq tree \
fonts-dejavu fonts-noto fontconfig \
pipewire pipewire-alsa wireplumber \
xsel wl-clipboard
# Office suite & PDF viewer
apt-get install -y -qq \
libreoffice-writer libreoffice-calc libreoffice-impress \
libreoffice-gtk3 \
evince \
2>/dev/null || echo " [SKIP] Some office/PDF packages unavailable"
# Chromium — preinstalled web browser
# snap does not work in chroot/CI environments, use apt packages
apt-get install -y -qq chromium-browser 2>/dev/null \
|| apt-get install -y -qq chromium 2>/dev/null \
|| echo " ⚠ Chromium not available for this architecture, install manually after first boot"
# Preinstalled native apps — all apps shown in NativeAppsLauncher
apt-get install -y -qq \
mousepad \
galculator \
evince \
libreoffice-writer \
libreoffice-calc \
libreoffice-impress \
2>/dev/null || echo " ⚠ Some native apps not available"
# Verify native app binaries
echo " Verifying native app binaries..."
# Ensure 'chromium' command exists — Ubuntu installs as 'chromium-browser'
if which chromium-browser 2>/dev/null && ! which chromium 2>/dev/null; then
ln -sf "$(which chromium-browser)" /usr/local/bin/chromium
echo " ✓ Created symlink: chromium → chromium-browser"
fi
for bin in chromium chromium-browser mousepad galculator evince libreoffice; do
if which "$bin" 2>/dev/null; then
echo " ✓ $bin found: $(which $bin)"
fi
done
# VMware guest tools (for VMware Fusion/Workstation support)
apt-get install -y -qq open-vm-tools open-vm-tools-desktop 2>/dev/null || true
# Network management (WiFi + wired)
apt-get install -y -qq network-manager wpasupplicant wireless-tools iw
# CRITICAL: NetworkManager ignores interfaces in /etc/network/interfaces
# Strip it to loopback-only so NM manages everything (ethernet, wifi)
cat > /etc/network/interfaces << NETIF
# This file is intentionally minimal.
# Network interfaces are managed by NetworkManager.
auto lo
iface lo inet loopback
NETIF
# Tell NetworkManager to manage all devices (even if in /etc/network/interfaces)
mkdir -p /etc/NetworkManager/conf.d
cat > /etc/NetworkManager/conf.d/10-manage-all.conf << NMCONF
[main]
plugins=ifupdown,keyfile
[ifupdown]
managed=true
[device]
wifi.scan-rand-mac-address=no
NMCONF
systemctl enable NetworkManager 2>/dev/null || true
# Disable ifupdown networking service so it does not conflict
systemctl disable networking 2>/dev/null || true
# ── systemd-networkd: early-boot wired DHCP ──────────────────────────
# NetworkManager handles WiFi and user-session interfaces, but it starts
# late. systemd-networkd provides DHCP on first boot before NM is ready,
# fixing the "no connectivity" issue in UTM/QEMU without any manual steps.
systemctl enable systemd-networkd 2>/dev/null || true
systemctl enable systemd-resolved 2>/dev/null || true
# Point /etc/resolv.conf at systemd-resolved stub resolver.
# This is a symlink — NM will not overwrite it, it manages DNS separately.
rm -f /etc/resolv.conf
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
# Tell NM to use systemd-resolved for DNS (avoids /etc/resolv.conf fights)
cat > /etc/NetworkManager/conf.d/20-dns-resolved.conf << NMRES
[main]
dns=systemd-resolved
NMRES
# Flatpak (dynamic package management) — optional, skip if unavailable
apt-get install -y -qq flatpak 2>/dev/null || echo " [SKIP] flatpak not available in repos"
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo 2>/dev/null || true
# Clean apt cache
apt-get clean
rm -rf /var/lib/apt/lists/*
'
set -e # Re-enable strict error handling
echo " ✓ Dependencies installed"
# ── Patch Qt 6.4.2 Wayland Compositor binary (ARM64 ONLY) ─────────
# Qt 6.4's libQt6WaylandCompositor has stub implementations for critical
# Wayland protocol functions that print "Not implemented" and cause
# Chrome/Chromium to crash on right-click (context menu popups).
# We patch these stubs to ARM64 'ret' (immediate return, no-op).
# This must happen OUTSIDE the chroot using the rootfs path from the host.
# NOTE: The offsets and instruction are ARM64-specific — skip on x86.
if [ "$ARCH" = "aarch64" ]; then
QT_SO=$(find "${ROOTFS_DIR}/usr/lib" -name "libQt6WaylandCompositor.so.6.4.2" -type f 2>/dev/null | head -1)
if [ -n "$QT_SO" ] && [ -f "$QT_SO" ]; then
echo " Patching Qt Wayland Compositor: $QT_SO"
RET='\xc0\x03\x5f\xd6' # ARM64: ret instruction
# xdg_popup_destroy (0xada78) + thunk (0xadb14)
printf "$RET" | dd of="$QT_SO" bs=1 seek=$((0xada78)) conv=notrunc 2>/dev/null
printf "$RET" | dd of="$QT_SO" bs=1 seek=$((0xadb14)) conv=notrunc 2>/dev/null
# xdg_popup_grab (0xadbb0) + thunk (0xadc4c)
printf "$RET" | dd of="$QT_SO" bs=1 seek=$((0xadbb0)) conv=notrunc 2>/dev/null
printf "$RET" | dd of="$QT_SO" bs=1 seek=$((0xadc4c)) conv=notrunc 2>/dev/null
# subsurface_set_desync (0xc5050)
printf "$RET" | dd of="$QT_SO" bs=1 seek=$((0xc5050)) conv=notrunc 2>/dev/null
# subsurface_set_sync (0xc4f50)
printf "$RET" | dd of="$QT_SO" bs=1 seek=$((0xc4f50)) conv=notrunc 2>/dev/null
echo " ✓ Qt Wayland stubs patched (xdg_popup_destroy/grab, subsurface sync/desync)"
else
echo " ⚠ libQt6WaylandCompositor.so.6.4.2 not found in rootfs, skipping patch"
fi
else
echo " ⏭ Qt Wayland patch skipped (x86_64 — ARM64-specific offsets)"
fi
# ─── Create User ───────────────────────────────────────────────
echo "[5/8] Creating default user..."
set +e # systemctl calls inside chroot will fail harmlessly
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
if ! id ainux 2>/dev/null; then
useradd -m -s /bin/bash -G sudo,video,audio,input,render,systemd-journal ainux
# Set a temporary password — first-boot wizard will force a change
echo "ainux:ainux" | chpasswd
fi
# Temporary sudo for first-boot setup — first-boot wizard will harden this
echo "ainux ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/ainux
chmod 440 /etc/sudoers.d/ainux
# SSH hardened defaults
mkdir -p /etc/ssh/sshd_config.d
cat > /etc/ssh/sshd_config.d/ainux-hardened.conf << SSHEOF
# TensorAgent OS — SSH Security Defaults
PermitRootLogin no
PasswordAuthentication yes
PubkeyAuthentication yes
MaxAuthTries 5
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowAgentForwarding no
PrintMotd no
Banner /etc/issue.net
SSHEOF
# Password quality enforcement
if [ -f /etc/security/pwquality.conf ]; then
cat > /etc/security/pwquality.conf << PWEOF
# TensorAgent OS — Password Policy
minlen = 8
minclass = 2
maxrepeat = 3
enforcing = 1
retry = 3
PWEOF
fi
'
echo " ✓ User created (temporary credentials — first-boot wizard will secure)"
# ─── Deploy Enterprise Security Infrastructure ────────────────
echo " → Deploying enterprise security infrastructure..."
# Deploy AppArmor profiles
if [ -d "${AINUX_ROOT}/rootfs-overlay/etc/apparmor.d" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/apparmor.d"
sudo cp -v "${AINUX_ROOT}/rootfs-overlay/etc/apparmor.d/"* "${ROOTFS_DIR}/etc/apparmor.d/" 2>/dev/null || true
echo " ✓ AppArmor profiles deployed"
fi
# Deploy audit rules
if [ -d "${AINUX_ROOT}/rootfs-overlay/etc/audit/rules.d" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/audit/rules.d"
sudo cp -v "${AINUX_ROOT}/rootfs-overlay/etc/audit/rules.d/"* "${ROOTFS_DIR}/etc/audit/rules.d/" 2>/dev/null || true
echo " ✓ Enterprise audit rules deployed"
fi
# Deploy security configuration
if [ -f "${AINUX_ROOT}/rootfs-overlay/etc/ainux/security.conf" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/ainux"
sudo cp "${AINUX_ROOT}/rootfs-overlay/etc/ainux/security.conf" "${ROOTFS_DIR}/etc/ainux/security.conf"
echo " ✓ Security configuration deployed"
fi
# Deploy SSSD template
if [ -f "${AINUX_ROOT}/rootfs-overlay/etc/sssd/sssd.conf.example" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/sssd"
sudo cp "${AINUX_ROOT}/rootfs-overlay/etc/sssd/sssd.conf.example" "${ROOTFS_DIR}/etc/sssd/sssd.conf.example"
echo " ✓ SSSD identity template deployed"
fi
# Deploy first-boot wizard
if [ -f "${AINUX_ROOT}/rootfs-overlay/opt/ainux/first-boot-setup.sh" ]; then
sudo cp "${AINUX_ROOT}/rootfs-overlay/opt/ainux/first-boot-setup.sh" "${ROOTFS_DIR}/opt/ainux/first-boot-setup.sh"
sudo chmod +x "${ROOTFS_DIR}/opt/ainux/first-boot-setup.sh"
echo " ✓ First-boot wizard deployed"
fi
# Deploy firewall script
if [ -f "${AINUX_ROOT}/rootfs-overlay/opt/ainux/configure-firewall.sh" ]; then
sudo cp "${AINUX_ROOT}/rootfs-overlay/opt/ainux/configure-firewall.sh" "${ROOTFS_DIR}/opt/ainux/configure-firewall.sh"
sudo chmod +x "${ROOTFS_DIR}/opt/ainux/configure-firewall.sh"
echo " ✓ Firewall configuration script deployed"
fi
# Deploy first-boot service
if [ -f "${AINUX_ROOT}/rootfs-overlay/etc/systemd/system/ainux-first-boot.service" ]; then
sudo cp "${AINUX_ROOT}/rootfs-overlay/etc/systemd/system/ainux-first-boot.service" \
"${ROOTFS_DIR}/etc/systemd/system/ainux-first-boot.service"
echo " ✓ First-boot service deployed"
fi
# Enable security services in chroot
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
# Enable AppArmor
systemctl enable apparmor 2>/dev/null || true
echo " ✓ AppArmor enabled"
# Enable audit daemon
systemctl enable auditd 2>/dev/null || true
echo " ✓ Audit daemon enabled"
# Enable fail2ban
systemctl enable fail2ban 2>/dev/null || true
echo " ✓ Fail2ban enabled"
# Enable first-boot wizard
systemctl enable ainux-first-boot.service 2>/dev/null || true
echo " ✓ First-boot wizard enabled"
# Configure firewall (UFW defaults)
ufw default deny incoming 2>/dev/null || true
ufw default allow outgoing 2>/dev/null || true
# Do not enable UFW yet — first-boot script handles activation
echo " ✓ Firewall defaults configured"
# Set login banner
cat > /etc/issue.net << BANNEREOF
***************************************************************************
* TensorAgent OS — Enterprise *
* *
* WARNING: Unauthorized access to this system is prohibited. *
* All connections and activities are monitored and recorded. *
* By accessing this system, you consent to monitoring. *
* *
* Disconnect IMMEDIATELY if you are not an authorized user. *
***************************************************************************
BANNEREOF
echo " ✓ Security banner configured"
'
# ─── Deploy Additional Enterprise Components ─────────────────────
echo " → Deploying enterprise security components..."
# Fail2ban jails and filters
for SUBDIR in jail.d filter.d; do
SRC="${AINUX_ROOT}/rootfs-overlay/etc/fail2ban/${SUBDIR}"
if [ -d "$SRC" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/fail2ban/${SUBDIR}"
sudo cp "$SRC"/* "${ROOTFS_DIR}/etc/fail2ban/${SUBDIR}/" 2>/dev/null || true
fi
done
echo " ✓ Fail2ban jails and filters deployed"
# osquery fleet config
if [ -f "${AINUX_ROOT}/rootfs-overlay/etc/osquery/osquery.conf" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/osquery"
sudo cp "${AINUX_ROOT}/rootfs-overlay/etc/osquery/osquery.conf" "${ROOTFS_DIR}/etc/osquery/"
echo " ✓ osquery fleet configuration deployed"
fi
# Fluent Bit log forwarding
if [ -f "${AINUX_ROOT}/rootfs-overlay/etc/fluent-bit/fluent-bit.conf" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/fluent-bit"
sudo cp "${AINUX_ROOT}/rootfs-overlay/etc/fluent-bit/fluent-bit.conf" "${ROOTFS_DIR}/etc/fluent-bit/"
echo " ✓ Fluent Bit log forwarding deployed"
fi
# TLS certificate generator + AI command policy + Prometheus
for SCRIPT in generate-tls.sh configure-firewall.sh first-boot-setup.sh; do
if [ -f "${AINUX_ROOT}/rootfs-overlay/opt/ainux/${SCRIPT}" ]; then
sudo cp "${AINUX_ROOT}/rootfs-overlay/opt/ainux/${SCRIPT}" "${ROOTFS_DIR}/opt/ainux/${SCRIPT}"
sudo chmod +x "${ROOTFS_DIR}/opt/ainux/${SCRIPT}"
fi
done
for CONF in command-policy.conf prometheus.yml; do
if [ -f "${AINUX_ROOT}/rootfs-overlay/etc/ainux/${CONF}" ]; then
sudo cp "${AINUX_ROOT}/rootfs-overlay/etc/ainux/${CONF}" "${ROOTFS_DIR}/etc/ainux/${CONF}"
fi
done
if [ -d "${AINUX_ROOT}/rootfs-overlay/etc/ainux/prometheus-rules" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/ainux/prometheus-rules"
sudo cp "${AINUX_ROOT}/rootfs-overlay/etc/ainux/prometheus-rules/"* "${ROOTFS_DIR}/etc/ainux/prometheus-rules/" 2>/dev/null || true
echo " ✓ Prometheus alert rules deployed"
fi
# Installer + update manager
for TOOL in tensoragent-install.sh ainux-update.sh; do
if [ -f "${AINUX_ROOT}/scripts/${TOOL}" ]; then
DEST_NAME="${TOOL%.sh}"
sudo cp "${AINUX_ROOT}/scripts/${TOOL}" "${ROOTFS_DIR}/usr/local/bin/${DEST_NAME}"
sudo chmod +x "${ROOTFS_DIR}/usr/local/bin/${DEST_NAME}"
echo " ✓ ${DEST_NAME} installed to PATH"
fi
done
# AIDE + unattended upgrades
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
command -v aide &>/dev/null && { aideinit 2>/dev/null || true; [ -f /var/lib/aide/aide.db.new ] && cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db 2>/dev/null; echo " ✓ AIDE initialized"; }
cat > /etc/apt/apt.conf.d/20auto-upgrades << AUTOUP
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
AUTOUP
echo " ✓ Unattended security updates enabled"
'
echo " ✓ All enterprise components deployed"
# Fix home directory ownership (useradd -m with existing dir leaves root:root)
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
chown -R ainux:ainux /home/ainux
# Create Works directory for agent workspace
mkdir -p /home/ainux/Works
chown ainux:ainux /home/ainux/Works
'
echo " ✓ Home directory ownership fixed"
echo " ✓ Works directory created"
# Deploy ainux.conf
if [ -f "${AINUX_ROOT}/rootfs-overlay/etc/ainux/ainux.conf" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/ainux"
sudo cp "${AINUX_ROOT}/rootfs-overlay/etc/ainux/ainux.conf" "${ROOTFS_DIR}/etc/ainux/ainux.conf"
echo " ✓ ainux.conf deployed"
fi
# ── Deploy systemd-networkd config overlay (DHCP for all VM types) ───────
# Files in rootfs-overlay/etc/systemd/network/ are baked in here so that
# every fresh boot — UTM, QEMU, VMware, bare metal — gets DHCP automatically
# without any manual systemd-networkd setup inside the image.
NETWORK_OVERLAY="${AINUX_ROOT}/rootfs-overlay/etc/systemd/network"
if [ -d "${NETWORK_OVERLAY}" ]; then
sudo mkdir -p "${ROOTFS_DIR}/etc/systemd/network"
sudo cp -v "${NETWORK_OVERLAY}/"*.network "${ROOTFS_DIR}/etc/systemd/network/"
echo " ✓ systemd-networkd DHCP configs deployed:"
ls -1 "${NETWORK_OVERLAY}/"*.network | while read f; do echo " $(basename $f)"; done
else
echo " ⚠ No network overlay files found at ${NETWORK_OVERLAY} — skipping"
fi
# ─── Install Ollama ────────────────────────────────────────────
echo " → Installing Ollama..."
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
curl -fsSL https://ollama.com/install.sh | sh 2>&1 | tail -3
'
echo " ✓ Ollama installed"
# Create Ollama systemd service
sudo tee "${ROOTFS_DIR}/etc/systemd/system/ollama.service" > /dev/null << 'OLLAMA_EOF'
[Unit]
Description=Ollama AI Model Server
After=network.target
[Service]
Type=simple
User=ainux
ExecStart=/usr/local/bin/ollama serve
Environment=HOME=/home/ainux OLLAMA_HOST=0.0.0.0
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
OLLAMA_EOF
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
systemctl enable ollama 2>/dev/null || true
'
echo " ✓ Ollama service enabled"
# ─── Integrate OpenWhale + WhaleOS ─────────────────────────────
echo "[6/8] Installing OpenWhale + WhaleOS..."
# Copy source into rootfs first (before building)
sudo mkdir -p "${ROOTFS_DIR}/opt/ainux"
sudo cp -r "${AINUX_ROOT}/packages/openwhale" "${ROOTFS_DIR}/opt/ainux/"
sudo cp -r "${AINUX_ROOT}/packages/whaleos" "${ROOTFS_DIR}/opt/ainux/"
# Build OpenWhale inside chroot where Node.js is available
echo " → Building OpenWhale in chroot..."
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
cd /opt/ainux/openwhale
echo " Installing dependencies..."
npm install 2>&1 | tail -5
echo " Building TypeScript..."
if npx tsc --version 2>/dev/null; then
npm run build 2>&1 | tail -10
if [ -f dist/index.js ]; then
echo " ✓ TypeScript compiled — dist/index.js exists"
# Clean devDependencies to save space
npm prune --production 2>/dev/null || true
else
echo " ⚠ tsc build failed — keeping tsx for runtime"
fi
else
echo " ⚠ tsc not found — keeping tsx for runtime"
fi
# Ensure tsx is available as runtime fallback
npm list tsx 2>/dev/null || npm install tsx 2>&1 | tail -3
# Rebuild native modules for ARM64
npm rebuild better-sqlite3 2>/dev/null || true
# Create data directory and fix ownership so ainux user can write
mkdir -p /opt/ainux/openwhale/data
chown -R 1000:1000 /opt/ainux/openwhale
'
# Build WhaleOS shell
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
cd /opt/ainux/whaleos
bash build.sh
'
echo " ✓ OpenWhale + WhaleOS installed"
# ─── Configure Systemd Services ───────────────────────────────
echo "[7/8] Configuring systemd services..."
# OpenWhale service
# Deploy the hardened service file from rootfs-overlay instead of inline
if [ -f "${AINUX_ROOT}/rootfs-overlay/etc/systemd/system/openwhale.service" ]; then
sudo cp "${AINUX_ROOT}/rootfs-overlay/etc/systemd/system/openwhale.service" \
"${ROOTFS_DIR}/etc/systemd/system/openwhale.service"
echo " ✓ Hardened OpenWhale service deployed (from rootfs-overlay)"
else
# Fallback: write hardened service inline
sudo tee "${ROOTFS_DIR}/etc/systemd/system/openwhale.service" > /dev/null << 'OWSERVICE'
[Unit]
Description=OpenWhale AI Platform
After=network.target
[Service]
Type=simple
User=ainux
Group=ainux
WorkingDirectory=/opt/ainux/openwhale
# Kill any process squatting on port 7777 before we start.
# Prevents EADDRINUSE on systemd restart after a crash that orphaned a Node process.
ExecStartPre=/bin/sh -c "fuser -k 7777/tcp 2>/dev/null; sleep 0.5; exit 0"
ExecStart=/usr/bin/node openwhale.mjs
Environment=NODE_ENV=production PORT=7777 HOME=/home/ainux AINUX_MODE=true
Environment=OPENWHALE_SANDBOX=true OPENWHALE_REQUIRE_APPROVAL=true
# on-failure: don't restart on clean exit (avoids port-contention restart loops)
Restart=on-failure
RestartSec=5
StartLimitBurst=5
StartLimitIntervalSec=120
# Kill the entire cgroup on stop — ensures tsx/node sub-processes don't outlive the unit
KillMode=control-group
KillSignal=SIGTERM
TimeoutStopSec=10
# Security hardening (enterprise)
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/home/ainux /opt/ainux/openwhale/data /tmp /home/ainux/Works
PrivateTmp=true
ProtectHome=tmpfs
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectClock=true
SystemCallArchitectures=native
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_DAC_READ_SEARCH
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
ProtectControlGroups=true
LockPersonality=true
RestrictRealtime=true
RestrictNamespaces=true
PrivateDevices=true
RemoveIPC=true
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=openwhale
[Install]
WantedBy=multi-user.target
OWSERVICE
fi
# WhaleOS helper service (port 7778 — system exec for QML)
sudo tee "${ROOTFS_DIR}/etc/systemd/system/whaleos-helper.service" > /dev/null << 'HELPERSERVICE'
[Unit]
Description=WhaleOS System Helper
After=network.target
[Service]
Type=simple
User=ainux
WorkingDirectory=/opt/ainux/whaleos
ExecStart=/usr/bin/node /opt/ainux/whaleos/whaleos-helper.mjs
Environment=NODE_ENV=production HOME=/home/ainux
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
HELPERSERVICE
sudo chroot "$ROOTFS_DIR" systemctl enable whaleos-helper 2>/dev/null || true
echo " ✓ WhaleOS helper service enabled (port 7778)"
# Hypervisor-aware GUI startup script
sudo tee "${ROOTFS_DIR}/opt/ainux/start-gui.sh" > /dev/null << 'STARTGUI'
#!/bin/bash
# TensorAgent OS — smart display startup
# WhaleOS IS a Wayland compositor — no need for Cage.
# Runs directly on the framebuffer via EGLFS.
LOGFILE=/tmp/tensoragent-gui.log
# Simple logging — no process substitution pipes that block VT
log() { echo "[TensorAgent] $*" | tee -a "$LOGFILE"; }
log "=== TensorAgent GUI starting at $(date) ==="
# Create XDG_RUNTIME_DIR if it doesn't exist
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
if [ ! -d "$XDG_RUNTIME_DIR" ]; then
mkdir -p "$XDG_RUNTIME_DIR"
chmod 0700 "$XDG_RUNTIME_DIR"
fi
# Qt rendering config — software rendering for VM compatibility
export QT_QPA_PLATFORM=eglfs
export QSG_RENDER_LOOP=basic
export QT_QUICK_BACKEND=software
export QML2_IMPORT_PATH=/usr/lib/aarch64-linux-gnu/qt6/qml:/usr/lib/qt6/qml
# EGLFS config — use linuxfb fallback if no working EGL
export QT_QPA_EGLFS_INTEGRATION=eglfs_kms
export QT_QPA_EGLFS_ALWAYS_SET_MODE=1
export LIBGL_ALWAYS_SOFTWARE=1
# XKB keyboard layout — CRITICAL: without this, Qt's Wayland compositor
# cannot create an XKB keymap and will never send wl_keyboard.keymap to
# native clients (Firefox, GTK apps), making keyboard input non-functional.
export XKB_DEFAULT_LAYOUT=us
export XKB_DEFAULT_MODEL=pc105
export XKB_DEFAULT_RULES=evdev
# XKB keyboard layout — required for Qt Wayland compositor to generate and
# send wl_keyboard.keymap to native Wayland clients (Firefox, GTK apps).
export XKB_DEFAULT_LAYOUT=us
export XKB_DEFAULT_MODEL=pc105
export XKB_DEFAULT_RULES=evdev
# Disable GTK client-side decorations — WhaleOS provides server-side title bars
export GTK_CSD=0
export XDG_CURRENT_DESKTOP=WhaleOS
# Detect hypervisor
HYPERVISOR=$(systemd-detect-virt 2>/dev/null || echo "none")
log "Detected hypervisor: $HYPERVISOR"
case "$HYPERVISOR" in
vmware)
log "VMware detected — forcing linuxfb platform"
export QT_QPA_PLATFORM=linuxfb
;;
qemu|kvm)
log "QEMU/KVM detected — using virtio-gpu"
;;
*)
log "Bare metal or unknown — auto-detecting display"
;;
esac
# Wait for DRM device (up to 10 seconds)
for i in $(seq 1 20); do
if ls /dev/dri/card* 2>/dev/null; then
log "DRM device found"
ls /dev/dri/ >> "$LOGFILE" 2>&1
break
fi
log "Waiting for DRM device... ($i/20)"
sleep 0.5
done
# If no DRM device, fall back to linuxfb
if ! ls /dev/dri/card* 2>/dev/null; then
log "No DRM device — falling back to linuxfb"
export QT_QPA_PLATFORM=linuxfb
fi
# Pre-flight checks
log "Checking WhaleOS binary..."
if [ ! -x /opt/ainux/whaleos/whaleos ]; then
log "ERROR: WhaleOS binary not found or not executable"
ls -la /opt/ainux/whaleos/ >> "$LOGFILE" 2>&1 || echo " whaleos directory missing" >> "$LOGFILE"
exit 1
fi
log "Checking main.qml..."
if [ ! -f /opt/ainux/whaleos/main.qml ]; then
log "ERROR: main.qml not found"
exit 1
fi
log "Environment:"
log " QT_QPA_PLATFORM=$QT_QPA_PLATFORM"
log " QT_QUICK_BACKEND=$QT_QUICK_BACKEND"
log " QSG_RENDER_LOOP=$QSG_RENDER_LOOP"
log " XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR"
# Launch WhaleOS directly — it IS a Wayland compositor
# No Cage needed. WhaleOS renders via EGLFS to the DRM device.
log "Starting WhaleOS compositor directly..."
# Refresh apt cache in background so Package Store works immediately
apt-get update -qq &>/dev/null &
# Apply LD_PRELOAD popup fix shim if available.
# Patches Qt 6.4.2's missing xdg_popup_destroy/xdg_popup_grab stubs
# that cause Chrome/Chromium to crash when right-clicking (context menu).
POPUP_SHIM="/opt/ainux/whaleos/libpopupfix.so"
if [ -f "$POPUP_SHIM" ]; then
export LD_PRELOAD="$POPUP_SHIM${LD_PRELOAD:+:$LD_PRELOAD}"
log "Popup fix shim loaded: $POPUP_SHIM"
fi
# Ensure udev has enumerated all input devices before compositor starts.
# libinput needs udev to discover keyboard/mouse — without this, it fails
# silently and the compositor gets no keyboard input at all.
udevadm trigger --subsystem-match=input 2>/dev/null || true
udevadm settle --timeout=3 2>/dev/null || true
exec /opt/ainux/whaleos/whaleos >> "$LOGFILE" 2>&1
STARTGUI
sudo chmod +x "${ROOTFS_DIR}/opt/ainux/start-gui.sh"
# ── GUI Launch via Getty Autologin (Kiosk Mode) ──
# Standard Cage kiosk setup: getty autologin → PAM session → logind seat → Cage gets DRM
# This gives Cage proper seat access that a standalone systemd service cannot provide.
# Auto-login on tty1
sudo mkdir -p "${ROOTFS_DIR}/etc/systemd/system/getty@tty1.service.d"
sudo tee "${ROOTFS_DIR}/etc/systemd/system/getty@tty1.service.d/autologin.conf" > /dev/null << 'AUTOLOGIN'
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin ainux --noclear %I $TERM
AUTOLOGIN
# Launch GUI from .bash_profile on tty1 (only runs on console login, not SSH)
sudo tee "${ROOTFS_DIR}/home/ainux/.bash_profile" > /dev/null << 'BASHPROFILE'
# TensorAgent OS — Auto-launch GUI on tty1
if [ "$(tty)" = "/dev/tty1" ] && [ -z "$WAYLAND_DISPLAY" ]; then
echo "[TensorAgent] Starting desktop environment..."
exec /opt/ainux/start-gui.sh
fi
# Normal shell for other TTYs and SSH
[ -f ~/.bashrc ] && source ~/.bashrc
BASHPROFILE
sudo chown 1000:1000 "${ROOTFS_DIR}/home/ainux/.bash_profile"
# Keep OpenWhale as a service (backend)
# Enable services
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
systemctl enable openwhale.service
systemctl set-default graphical.target
systemctl enable systemd-logind
systemctl enable tensoragent-disk.service 2>/dev/null || true
'
echo " ✓ Services configured"
set -e # Re-enable strict errors for ISO generation (critical step)
# ─── Generate Bootable ISO ────────────────────────────────────
echo "[8/8] Generating bootable ISO..."
# Install live-boot in chroot (required for boot=live kernel parameter)
set +e
sudo chroot "$ROOTFS_DIR" /bin/bash -c '
apt-get update -qq 2>/dev/null
apt-get install -y -qq live-boot 2>/dev/null || true
apt-get clean
'
set -e
# Create squashfs from rootfs
sudo umount "${ROOTFS_DIR}/dev" 2>/dev/null || true
sudo umount "${ROOTFS_DIR}/proc" 2>/dev/null || true
sudo umount "${ROOTFS_DIR}/sys" 2>/dev/null || true
sudo mkdir -p "${ISO_DIR}/live"
if [ "$ARCH" = "x86_64" ]; then
sudo mksquashfs "$ROOTFS_DIR" "${ISO_DIR}/live/filesystem.squashfs" \
-comp xz -Xbcj x86 -b 1M -no-exports -noappend 2>/dev/null || \
sudo mksquashfs "$ROOTFS_DIR" "${ISO_DIR}/live/filesystem.squashfs" \
-comp gzip -b 1M -no-exports -noappend
else
sudo mksquashfs "$ROOTFS_DIR" "${ISO_DIR}/live/filesystem.squashfs" \
-comp gzip -b 1M -no-exports -noappend
fi
# Copy kernel and initrd for live boot
sudo mkdir -p "${ISO_DIR}/live"
# Find and copy kernel
KERNEL=$(find "${ROOTFS_DIR}/boot" -name "vmlinuz-*" -type f 2>/dev/null | sort -V | tail -1)
if [ -z "$KERNEL" ]; then
# Try symlink
KERNEL="${ROOTFS_DIR}/boot/vmlinuz"
fi
if [ -f "$KERNEL" ]; then
sudo cp "$KERNEL" "${ISO_DIR}/live/vmlinuz"
echo " ✓ Kernel: $(basename $KERNEL)"
else
echo " ✗ ERROR: No kernel found in ${ROOTFS_DIR}/boot/"
ls -la "${ROOTFS_DIR}/boot/" || true
fi
# Find and copy initrd
INITRD=$(find "${ROOTFS_DIR}/boot" -name "initrd.img-*" -type f 2>/dev/null | sort -V | tail -1)
if [ -z "$INITRD" ]; then
INITRD="${ROOTFS_DIR}/boot/initrd.img"
fi
if [ -f "$INITRD" ]; then
sudo cp "$INITRD" "${ISO_DIR}/live/initrd"
echo " ✓ Initrd: $(basename $INITRD)"
else
echo " ✗ ERROR: No initrd found in ${ROOTFS_DIR}/boot/"
ls -la "${ROOTFS_DIR}/boot/" || true
fi
# Verify both files exist before proceeding
if [ ! -f "${ISO_DIR}/live/vmlinuz" ] || [ ! -f "${ISO_DIR}/live/initrd" ]; then
echo "FATAL: Kernel or initrd missing from ISO live directory!"
echo "Contents of ${ISO_DIR}/live/:"
ls -la "${ISO_DIR}/live/" || true
fi
# Create GRUB config
sudo mkdir -p "${ISO_DIR}/boot/grub"
sudo tee "${ISO_DIR}/boot/grub/grub.cfg" > /dev/null << 'GRUB'
insmod all_video
insmod gzio
insmod part_gpt
insmod part_msdos
insmod iso9660
insmod search_label
search --no-floppy --set=root --label TENSORAGENT
set timeout=3
set default=0
menuentry "TensorAgent OS" {
linux /live/vmlinuz boot=live quiet splash
initrd /live/initrd
}