From c80f64ddccb5bcb5aeba29cc1759a52aea3f1abf Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:19:51 +0300 Subject: [PATCH 01/33] archiso: modernize profile for current archiso - bootmodes: replace the 6 removed names with bios.syslinux + uefi.systemd-boot (uefi.systemd-boot already covers x64 + mixed-mode IA32; mkarchiso rejects it alongside uefi.grub) - drop buildmodes=('iso') (implicit default) and iso_icon (not read by mkarchiso) - rename ISO to simulationos so mkarchiso emits simulationos--x86_64.iso - pacman.conf: declare [cachyos] with explicit Server= URLs instead of Include=/etc/pacman.d/cachyos-mirrorlist, so the build no longer silently depends on a developer-specific host file - pacman.conf: remove DisableSandbox; keep SigLevel = Required DatabaseOptional - order Arch repos before [cachyos] so shared packages come from Arch --- .gitignore | 3 ++ pacman.conf | 97 +++++++++++---------------------------------------- profiledef.sh | 81 ++++++++++++++++-------------------------- 3 files changed, 53 insertions(+), 128 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b08c068 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# mkarchiso build artefacts +/work/ +/out/ diff --git a/pacman.conf b/pacman.conf index c4dd9b1..5d549e4 100644 --- a/pacman.conf +++ b/pacman.conf @@ -1,105 +1,48 @@ # -# /etc/pacman.conf +# SimulationOS build-host pacman.conf # -# See the pacman.conf(5) manpage for option and repository directives - -# -# GENERAL OPTIONS +# Used by mkarchiso/pacstrap to populate the ISO root. This file is read on the +# BUILD MACHINE, so it must not depend on files that only exist inside the ISO. # +# The [cachyos] repository is declared with an explicit Server= URL rather than +# `Include = /etc/pacman.d/cachyos-mirrorlist` on purpose: the build must not +# silently require a developer-specific /etc/pacman.d file to be present. +# The CachyOS *keyring* is still required on the build host so signatures can be +# verified - scripts/build-iso.sh checks for it and refuses to continue without +# it. Signature verification is never disabled. + [options] -# The following paths are commented out with their default values listed. -# If you wish to use different paths, uncomment and update the paths. -#RootDir = / -#DBPath = /var/lib/pacman/ -#CacheDir = /var/cache/pacman/pkg/ -#LogFile = /var/log/pacman.log -#GPGDir = /etc/pacman.d/gnupg/ -#HookDir = /etc/pacman.d/hooks/ HoldPkg = pacman glibc -#XferCommand = /usr/bin/curl -L -C - -f -o %o %u -#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u -#CleanMethod = KeepInstalled -Architecture = auto - -# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup -#IgnorePkg = -#IgnoreGroup = +Architecture = x86_64 -#NoUpgrade = -#NoExtract = - -# Misc options -#UseSyslog Color -#NoProgressBar -# We cannot check disk space from within a chroot environment -#CheckSpace +CheckSpace VerbosePkgLists -DisableDownloadTimeout ParallelDownloads = 10 -ILoveCandy -#DownloadUser = alpm -DisableSandbox +DisableDownloadTimeout -# By default, pacman accepts packages signed by keys that its local keyring -# trusts (see pacman-key and its man page), as well as unsigned packages. SigLevel = Required DatabaseOptional LocalFileSigLevel = Optional -#RemoteFileSigLevel = Required - -# NOTE: You must run `pacman-key --init` before first using pacman; the local -# keyring can then be populated with the keys of all official Arch Linux -# packagers with `pacman-key --populate archlinux`. # # REPOSITORIES -# - can be defined here or included from another file -# - pacman will search repositories in the order defined here -# - local/custom mirrors can be added here or in separate files -# - repositories listed first will take precedence when packages -# have identical names, regardless of version number -# - URLs will have $repo replaced by the name of the current repo -# - URLs will have $arch replaced by the name of the architecture # -# Repository entries are of the format: -# [repo-name] -# Server = ServerName -# Include = IncludePath +# Order matters: Arch repositories are listed FIRST so that a package present in +# both Arch and CachyOS is taken from Arch. SimulationOS deliberately uses +# CachyOS only for what is exclusive to it (linux-cachyos, cachyos-calamares, +# ckbcomp, mkinitcpio-openswap, cachyos-keyring, cachyos-mirrorlist) instead of +# adopting the whole CachyOS-optimised package set. # -# The header [repo-name] is crucial - it must be present and -# uncommented to enable the repo. -# - -# The testing repositories are disabled by default. To enable, uncomment the -# repo name header and Include lines. You can add preferred servers immediately -# after the header, and they will be used before the default mirrors. - -#[core-testing] -#Include = /etc/pacman.d/mirrorlist [core] Include = /etc/pacman.d/mirrorlist -#[extra-testing] -#Include = /etc/pacman.d/mirrorlist - [extra] Include = /etc/pacman.d/mirrorlist -# If you want to run 32 bit applications on your x86_64 system, -# enable the multilib repositories as required here. - -#[multilib-testing] -#Include = /etc/pacman.d/mirrorlist - [multilib] Include = /etc/pacman.d/mirrorlist -# An example of a custom package repository. See the pacman manpage for -# tips on creating your own repositories. -#[custom] -#SigLevel = Optional TrustAll -#Server = file:///home/custompkgs - [cachyos] -Include = /etc/pacman.d/cachyos-mirrorlist +Server = https://mirror.cachyos.org/repo/$arch/$repo +Server = https://cdn.cachyos.org/repo/$arch/$repo diff --git a/profiledef.sh b/profiledef.sh index 4b30203..2f4fb75 100644 --- a/profiledef.sh +++ b/profiledef.sh @@ -1,72 +1,51 @@ #!/usr/bin/env bash # shellcheck disable=SC2034 - -iso_name="SimulationOS" -# file_name="${iso_name}-{iso_version}-x86_64.iso" +# +# SimulationOS archiso profile definition. +# +# Only variables that mkarchiso actually consumes are set here. See +# archiso's docs/README.profile.rst for the authoritative list. +# +# Produces: out/simulationos--x86_64.iso +# (mkarchiso builds "${iso_name}-${iso_version}-${arch}.iso") + +iso_name="simulationos" iso_label="SIMOS_$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y%m)" iso_publisher="SimulationOS " -iso_application="SimulationOS - Arch-based Rescue and Custom System" +iso_application="SimulationOS Live/Install Medium" iso_version="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)" install_dir="arch" -buildmodes=('iso') -# systemd-boot -bootmodes=( - 'bios.syslinux.mbr' - 'bios.syslinux.eltorito' - 'uefi-ia32.systemd-boot.esp' - 'uefi-x64.systemd-boot.esp' - 'uefi-ia32.systemd-boot.eltorito' - 'uefi-x64.systemd-boot.eltorito' -) +# buildmodes is intentionally unset: 'iso' is the implicit default. -arch="x86_64" +# Current archiso boot mode names. The pre-0.90 names +# (bios.syslinux.mbr / bios.syslinux.eltorito / uefi-x64.systemd-boot.esp / +# uefi-ia32.systemd-boot.eltorito / ...) are deprecated shims. +# 'uefi.systemd-boot' already covers x64 *and* mixed-mode IA32 on x86_64, +# and it cannot be combined with 'uefi.grub' (mkarchiso rejects that). +bootmodes=('bios.syslinux' + 'uefi.systemd-boot') +arch="x86_64" pacman_conf="pacman.conf" airootfs_image_type="squashfs" airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-b' '1M' '-Xdict-size' '1M') bootstrap_tarball_compression=('zstd' '-c' '-T0' '--auto-threads=logical' '--long' '-19') +# A trailing "/" makes mkarchiso apply ownership/mode recursively. file_permissions=( ["/etc/shadow"]="0:0:400" ["/etc/gshadow"]="0:0:400" ["/root"]="0:0:750" - ["/etc/polkit-1/rules.d"]="0:0:750" - ["/etc/sudoers.d"]="0:0:750" - ["/etc/sudoers.d/g_wheel"]="0:0:440" ["/root/.automated_script.sh"]="0:0:755" ["/root/.gnupg"]="0:0:700" - ["/usr/local/bin/choose-mirror"]="0:0:755" - ["/usr/local/bin/dmcheck"]="0:0:755" - ["/usr/local/bin/calamares-online.sh"]="0:0:755" - ["/usr/local/bin/remove-nvidia"]="0:0:755" - ["/usr/local/bin/removeun"]="0:0:755" - ["/usr/local/bin/removeun-online"]="0:0:755" - ["/usr/local/bin/prepare-live-desktop.sh"]="0:0:755" - ["/usr/local/bin/nvidia-module-loader"]="0:0:755" + ["/etc/sudoers.d"]="0:0:750" + ["/etc/sudoers.d/10-simulationos-live"]="0:0:440" + ["/etc/polkit-1/rules.d"]="0:0:750" + ["/usr/local/bin/livecd-sound"]="0:0:755" + ["/usr/local/bin/simulationos-install"]="0:0:755" + ["/usr/local/bin/simos-screenshot"]="0:0:755" + ["/usr/local/bin/simos-powermenu"]="0:0:755" + ["/usr/local/bin/simos-clipboard"]="0:0:755" + ["/usr/share/simulationos/calamares/scripts/simulationos-deloop"]="0:0:755" ) - -#profiles_description="SimulationOS Arch-based custom live ISO for rescue and simulation purposes" - -#install_dir_disktop="Desktop" - -iso_icon="syslinux/splash.png" - -#additional_packages=( -#"nvim" -#"networkmanager" -#"git" -#"base-devel" -# "linux-headers" -# "btop" -#) - -#kernel_parameters="quite loglevel=3" - -#run_once_custom_airoots_customize() { -# systemctl enable NetworkManager.service -#} - -#run_once_custom_airoots_finallize() { -# rm -rf "${AIROOTFS}/tmp/*" -#} From c6b5f22d55cdd6520f6a858bb7b931e786483f59 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:20:13 +0300 Subject: [PATCH 02/33] boot/kernel: make every boot path agree on linux-cachyos The profile installed linux-cachyos but the boot entries referenced vmlinuz-linux and the custom preset referenced a nonexistent vmlinuz-linux-simos. Three names, none of which lined up; the ISO could not have booted. - all boot entries now use vmlinuz-linux-cachyos / initramfs-linux-cachyos.img, confirmed from the linux-cachyos PKGBUILD (pkgbase=linux-cachyos) - delete airootfs/etc/mkinitcpio.d/linux.preset: presets are keyed to pkgbase, so it was orphaned. Upstream archiso removed its own preset for the same reason ('the default preset is good enough') - rebrand boot menus to SimulationOS; rename entries to 01/02/03-simulationos-* - delete grub/grub.cfg: only the uefi.grub bootmode reads it, and that cannot be combined with uefi.systemd-boot. grub/loopback.cfg is kept because mkarchiso installs it whenever no *grub* bootmode is set - drop broadcom-wl: removed from the Arch repositories, would abort pacstrap --- airootfs/etc/mkinitcpio.d/linux.preset | 8 - .../entries/01-archiso-x86_64-linux.conf | 5 - .../loader/entries/01-simulationos-linux.conf | 5 + .../02-archiso-x86_64-speech-linux.conf | 5 - .../entries/02-simulationos-speech-linux.conf | 5 + ...onf => 03-simulationos-memtest86+x64.conf} | 3 +- efiboot/loader/loader.conf | 2 +- grub/grub.cfg | 107 ------- grub/loopback.cfg | 49 +-- packages.x86_64 | 292 +++++++++++------- syslinux/archiso_head.cfg | 2 +- syslinux/archiso_pxe-linux.cfg | 37 +-- syslinux/archiso_sys-linux.cfg | 27 +- syslinux/archiso_sys.cfg | 2 +- 14 files changed, 244 insertions(+), 305 deletions(-) delete mode 100644 airootfs/etc/mkinitcpio.d/linux.preset delete mode 100644 efiboot/loader/entries/01-archiso-x86_64-linux.conf create mode 100644 efiboot/loader/entries/01-simulationos-linux.conf delete mode 100644 efiboot/loader/entries/02-archiso-x86_64-speech-linux.conf create mode 100644 efiboot/loader/entries/02-simulationos-speech-linux.conf rename efiboot/loader/entries/{03-archiso-x86_64-memtest86+.conf => 03-simulationos-memtest86+x64.conf} (51%) delete mode 100644 grub/grub.cfg diff --git a/airootfs/etc/mkinitcpio.d/linux.preset b/airootfs/etc/mkinitcpio.d/linux.preset deleted file mode 100644 index b83a16d..0000000 --- a/airootfs/etc/mkinitcpio.d/linux.preset +++ /dev/null @@ -1,8 +0,0 @@ -# mkinitcpio preset file for the 'linux' package on archiso - -PRESETS=('archiso') - -ALL_kver='/boot/vmlinuz-linux-simos' -archiso_config='/etc/mkinitcpio.conf.d/archiso.conf' - -archiso_image="/boot/initramfs-linux-simos.img" diff --git a/efiboot/loader/entries/01-archiso-x86_64-linux.conf b/efiboot/loader/entries/01-archiso-x86_64-linux.conf deleted file mode 100644 index bc8ab33..0000000 --- a/efiboot/loader/entries/01-archiso-x86_64-linux.conf +++ /dev/null @@ -1,5 +0,0 @@ -title Arch Linux install medium (x86_64, UEFI) -sort-key 01 -linux /%INSTALL_DIR%/boot/x86_64/vmlinuz-linux -initrd /%INSTALL_DIR%/boot/x86_64/initramfs-linux.img -options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% diff --git a/efiboot/loader/entries/01-simulationos-linux.conf b/efiboot/loader/entries/01-simulationos-linux.conf new file mode 100644 index 0000000..003f9eb --- /dev/null +++ b/efiboot/loader/entries/01-simulationos-linux.conf @@ -0,0 +1,5 @@ +title SimulationOS (%ARCH%, UEFI) +sort-key 01 +linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos +initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img +options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% %KERNEL_PARAMS% diff --git a/efiboot/loader/entries/02-archiso-x86_64-speech-linux.conf b/efiboot/loader/entries/02-archiso-x86_64-speech-linux.conf deleted file mode 100644 index c0cb1c8..0000000 --- a/efiboot/loader/entries/02-archiso-x86_64-speech-linux.conf +++ /dev/null @@ -1,5 +0,0 @@ -title Arch Linux install medium (x86_64, UEFI) with speech -sort-key 02 -linux /%INSTALL_DIR%/boot/x86_64/vmlinuz-linux -initrd /%INSTALL_DIR%/boot/x86_64/initramfs-linux.img -options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on diff --git a/efiboot/loader/entries/02-simulationos-speech-linux.conf b/efiboot/loader/entries/02-simulationos-speech-linux.conf new file mode 100644 index 0000000..7ab7440 --- /dev/null +++ b/efiboot/loader/entries/02-simulationos-speech-linux.conf @@ -0,0 +1,5 @@ +title SimulationOS (%ARCH%, UEFI) with speech +sort-key 02 +linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos +initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img +options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on %KERNEL_PARAMS% diff --git a/efiboot/loader/entries/03-archiso-x86_64-memtest86+.conf b/efiboot/loader/entries/03-simulationos-memtest86+x64.conf similarity index 51% rename from efiboot/loader/entries/03-archiso-x86_64-memtest86+.conf rename to efiboot/loader/entries/03-simulationos-memtest86+x64.conf index d0b305c..fd8af77 100644 --- a/efiboot/loader/entries/03-archiso-x86_64-memtest86+.conf +++ b/efiboot/loader/entries/03-simulationos-memtest86+x64.conf @@ -1,3 +1,4 @@ -title Memtest86+ +title Memtest86+ (RAM test) sort-key 03 efi /boot/memtest86+/memtest.efi +architecture x64 diff --git a/efiboot/loader/loader.conf b/efiboot/loader/loader.conf index 2d137aa..366e61f 100644 --- a/efiboot/loader/loader.conf +++ b/efiboot/loader/loader.conf @@ -1,3 +1,3 @@ timeout 15 -default 01-archiso-x86_64-linux.conf +default 01-simulationos-linux.conf beep on diff --git a/grub/grub.cfg b/grub/grub.cfg deleted file mode 100644 index 25194a3..0000000 --- a/grub/grub.cfg +++ /dev/null @@ -1,107 +0,0 @@ -# Load partition table and file system modules -insmod part_gpt -insmod part_msdos -insmod fat -insmod iso9660 -insmod ntfs -insmod ntfscomp -insmod exfat -insmod udf - -# Use graphics-mode output -if loadfont "${prefix}/fonts/unicode.pf2" ; then - insmod all_video - set gfxmode="auto" - terminal_input console - terminal_output console -fi - -# Enable serial console -insmod serial -insmod usbserial_common -insmod usbserial_ftdi -insmod usbserial_pl2303 -insmod usbserial_usbdebug -if serial --unit=0 --speed=115200; then - terminal_input --append serial - terminal_output --append serial -fi - -# Get a human readable platform identifier -if [ "${grub_platform}" == 'efi' ]; then - archiso_platform='UEFI' - if [ "${grub_cpu}" == 'x86_64' ]; then - archiso_platform="x64 ${archiso_platform}" - elif [ "${grub_cpu}" == 'i386' ]; then - archiso_platform="IA32 ${archiso_platform}" - else - archiso_platform="${grub_cpu} ${archiso_platform}" - fi -elif [ "${grub_platform}" == 'pc' ]; then - archiso_platform='BIOS' -else - archiso_platform="${grub_cpu} ${grub_platform}" -fi - -# Set default menu entry -default=archlinux -timeout=15 -timeout_style=menu - - -# Menu entries - -menuentry "Arch Linux install medium (%ARCH%, ${archiso_platform})" --class arch --class gnu-linux --class gnu --class os --id 'archlinux' { - set gfxpayload=keep - linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% - initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img -} - -menuentry "Arch Linux install medium with speakup screen reader (%ARCH%, ${archiso_platform})" --hotkey s --class arch --class gnu-linux --class gnu --class os --id 'archlinux-accessibility' { - set gfxpayload=keep - linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on - initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img -} - - -if [ "${grub_platform}" == 'efi' -a "${grub_cpu}" == 'x86_64' -a -f '/boot/memtest86+/memtest.efi' ]; then - menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool { - set gfxpayload=800x600,1024x768 - linux /boot/memtest86+/memtest.efi - } -fi -if [ "${grub_platform}" == 'pc' -a -f '/boot/memtest86+/memtest' ]; then - menuentry 'Run Memtest86+ (RAM test)' --class memtest86 --class memtest --class gnu --class tool { - set gfxpayload=800x600,1024x768 - linux /boot/memtest86+/memtest - } -fi -if [ "${grub_platform}" == 'efi' ]; then - if [ "${grub_cpu}" == 'x86_64' -a -f '/shellx64.efi' ]; then - menuentry 'UEFI Shell' --class efi { - chainloader /shellx64.efi - } - elif [ "${grub_cpu}" == "i386" -a -f '/shellia32.efi' ]; then - menuentry 'UEFI Shell' --class efi { - chainloader /shellia32.efi - } - fi - - menuentry 'UEFI Firmware Settings' --id 'uefi-firmware' { - fwsetup - } -fi - -menuentry 'System shutdown' --class shutdown --class poweroff { - echo 'System shutting down...' - halt -} - -menuentry 'System restart' --class reboot --class restart { - echo 'System rebooting...' - reboot -} - - -# GRUB init tune for accessibility -play 600 988 1 1319 4 diff --git a/grub/loopback.cfg b/grub/loopback.cfg index d7d5ece..5766853 100644 --- a/grub/loopback.cfg +++ b/grub/loopback.cfg @@ -1,4 +1,12 @@ # https://www.supergrubdisk.org/wiki/Loopback.cfg +# +# SimulationOS loopback entry point. +# +# NOTE: SimulationOS uses bootmodes=(bios.syslinux uefi.systemd-boot), so +# 'uefi.grub' is NOT a boot mode of this ISO and grub/grub.cfg is intentionally +# absent. mkarchiso still installs this file: _make_bootmodes() calls +# _make_common_grubenv_and_loopbackcfg whenever no *grub* bootmode is used, so +# loopback.cfg lets an already-installed GRUB boot this ISO from a file. # Search for the ISO volume search --no-floppy --set=archiso_img_dev --file "${iso_path}" @@ -7,37 +15,39 @@ probe --set archiso_img_dev_uuid --fs-uuid "${archiso_img_dev}" # Get a human readable platform identifier if [ "${grub_platform}" == 'efi' ]; then archiso_platform='UEFI' - if [ "${grub_cpu}" == 'x86_64' ]; then - archiso_platform="x64 ${archiso_platform}" - elif [ "${grub_cpu}" == 'i386' ]; then - archiso_platform="IA32 ${archiso_platform}" - else - archiso_platform="${grub_cpu} ${archiso_platform}" - fi elif [ "${grub_platform}" == 'pc' ]; then archiso_platform='BIOS' else - archiso_platform="${grub_cpu} ${grub_platform}" + archiso_platform="${grub_cpu}-${grub_platform}" +fi + +# Get the lowercase UEFI machine type short-name for architecture-specific tools +if [ "${grub_platform}" == 'efi' ]; then + if [ "${grub_cpu}" == 'x86_64' ]; then + uefi_machine_type_lower='x64' + elif [ "${grub_cpu}" == 'i386' ]; then + uefi_machine_type_lower='ia32' + fi fi # Set default menu entry -default=archlinux +default=simulationos timeout=15 timeout_style=menu # Menu entries -menuentry "Arch Linux install medium (%ARCH%, ${archiso_platform})" --class arch --class gnu-linux --class gnu --class os --id 'archlinux' { +menuentry "SimulationOS (%ARCH%, ${archiso_platform})" --class simulationos --class gnu-linux --class gnu --class os --id 'simulationos' { set gfxpayload=keep - linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" - initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" %KERNEL_PARAMS% + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img } -menuentry "Arch Linux install medium with speakup screen reader (%ARCH%, ${archiso_platform})" --hotkey s --class arch --class gnu-linux --class gnu --class os --id 'archlinux-accessibility' { +menuentry "SimulationOS with speakup screen reader (%ARCH%, ${archiso_platform})" --hotkey s --class simulationos --class gnu-linux --class gnu --class os --id 'simulationos-accessibility' { set gfxpayload=keep - linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" accessibility=on - initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img + linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos archisobasedir=%INSTALL_DIR% img_dev=UUID=${archiso_img_dev_uuid} img_loop="${iso_path}" accessibility=on %KERNEL_PARAMS% + initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img } @@ -53,14 +63,11 @@ if [ "${grub_platform}" == 'pc' -a -f '/boot/memtest86+/memtest' ]; then linux /boot/memtest86+/memtest } fi + if [ "${grub_platform}" == 'efi' ]; then - if [ "${grub_cpu}" == 'x86_64' -a -f '/shellx64.efi' ]; then - menuentry 'UEFI Shell' --class efi { - chainloader /shellx64.efi - } - elif [ "${grub_cpu}" == "i386" -a -f '/shellia32.efi' ]; then + if [ -f "/shell${uefi_machine_type_lower}.efi" ]; then menuentry 'UEFI Shell' --class efi { - chainloader /shellia32.efi + chainloader /shell${uefi_machine_type_lower}.efi } fi diff --git a/packages.x86_64 b/packages.x86_64 index b092a8c..df33414 100644 --- a/packages.x86_64 +++ b/packages.x86_64 @@ -1,133 +1,193 @@ -alsa-utils -amd-ucode -arch-install-scripts -archinstall -b43-fwcutter +# SimulationOS package list (x86_64) +# +# Grouped by purpose. Every entry is expected to resolve from: +# [core] [extra] [multilib] -> Arch Linux +# [cachyos] -> CachyOS (kernel, installer, keyring/mirrorlist) +# scripts/validate-profile.sh checks this file for structural problems; +# pacman resolves availability at build time. + +## ---------------------------------------------------------------- base system base -bcachefs-tools -bind -bolt -brltty -broadcom-wl -btrfs-progs -clonezilla -cloud-init -cryptsetup -darkhttpd -ddrescue -dhcpcd +bash +sudo +less +man-db +man-pages +nano +neovim +git +curl +wget +rsync +unzip +zip +p7zip +which +htop +btop +fastfetch +usbutils +lsscsi diffutils -dmidecode -dmraid -dnsmasq -dosfstools -e2fsprogs -edk2-shell -efibootmgr -espeakup -ethtool -exfatprogs -f2fs-tools -fatresize -foot-terminfo -fsarchiver -gpart -gpm -gptfdisk -grml-zsh-config -grub -hdparm -hyperv -intel-ucode -irssi -iw -iwd -jfsutils +pv +screen +tmux +terminus-font kitty-terminfo -ldns -less -lftp -libfido2 -libusb-compat -#linux +eza + +## ---------------------------------------------------------- kernel & firmware +# SimulationOS ships the CachyOS kernel. pkgbase is "linux-cachyos", so the +# installed files are /boot/vmlinuz-linux-cachyos and +# /boot/initramfs-linux-cachyos.img, and mkinitcpio generates +# /etc/mkinitcpio.d/linux-cachyos.preset. Boot entries must match those names. linux-cachyos -linux-cachyos-headers -linux-atm linux-firmware linux-firmware-marvell -livecd-sounds -lsscsi -lvm2 -lynx -man-db -man-pages -mc -mdadm -memtest86+ -memtest86+-efi +sof-firmware +amd-ucode +intel-ucode +# mkinitcpio and mkinitcpio-archiso are mandatory for archiso. mkinitcpio mkinitcpio-archiso -mkinitcpio-nfs-utils -mmc-utils -modemmanager -mtools -nano -nbd -ndisc6 -nfs-utils -nilfs-utils + +## ---------------------------------------------------------------- CachyOS repo +# Needed so the live system *and* the installed system keep working access to +# the repository the kernel came from. +cachyos-keyring +cachyos-mirrorlist +archlinux-keyring + +## -------------------------------------------------------------------- graphics +mesa +mesa-utils +vulkan-radeon +vulkan-intel +vulkan-swrast +vulkan-tools + +## ------------------------------------------------------------ Hyprland desktop +hyprland +hyprpaper +hyprpolkitagent +hyprlock +waybar +kitty +wofi +thunar +thunar-volman +gvfs +mako +libnotify +grim +slurp +wl-clipboard +cliphist +brightnessctl +polkit +xdg-desktop-portal +xdg-desktop-portal-hyprland +xdg-desktop-portal-gtk +xdg-user-dirs +xdg-utils +qt5-wayland +qt6-wayland +xorg-xwayland + +## ----------------------------------------------------------------------- fonts +ttf-dejavu +ttf-fira-sans +ttf-jetbrains-mono-nerd +ttf-nerd-fonts-symbols +noto-fonts +noto-fonts-emoji + +## ----------------------------------------------------------------------- themes +adwaita-icon-theme +papirus-icon-theme + +## ------------------------------------------------------------- display manager +sddm + +## ------------------------------------------------------------------------ audio +pipewire +pipewire-alsa +pipewire-pulse +pipewire-jack +wireplumber +pavucontrol +alsa-utils + +## ---------------------------------------------------------------------- network +networkmanager +network-manager-applet +nm-connection-editor +wpa_supplicant +wireless-regdb +wireless_tools +iw +ethtool +bind +tcpdump nmap +openssh +modemmanager +usb_modeswitch +b43-fwcutter + +## -------------------------------------------------------------------- bluetooth +bluez +bluez-utils +blueman + +## ------------------------------------------------------- filesystems & storage +btrfs-progs +e2fsprogs +xfsprogs +f2fs-tools +exfatprogs +dosfstools ntfs-3g nvme-cli -open-iscsi -open-vm-tools -openconnect -openpgp-card-tools -openssh -openvpn -partclone +mtools +gptfdisk parted -partimage -pcsclite -ppp -pptpclient -pv -qemu-guest-agent -refind -reflector -rsync -rxvt-unicode-terminfo -screen +cryptsetup +lvm2 +mdadm +hdparm sdparm -sequoia-sq -sg3_utils smartmontools -sof-firmware squashfs-tools -sudo -syslinux -systemd-resolvconf -tcpdump -terminus-font +dmidecode + +## -------------------------------------------------------------------- recovery +ddrescue testdisk -tmux -tpm2-tools -tpm2-tss -udftools -usb_modeswitch -usbmuxd -usbutils -neovim +partclone +fsarchiver +memtest86+ +memtest86+-efi + +## ------------------------------------------------------------------- installer +# cachyos-calamares provides /usr/bin/calamares (provides=calamares). +# SimulationOS supplies its own config tree, see +# airootfs/usr/share/simulationos/calamares/. +cachyos-calamares +gparted +inxi +arch-install-scripts +grub +efibootmgr +syslinux + +## -------------------------------------------------------------- accessibility +brltty +espeakup +livecd-sounds + +## ------------------------------------------------------------- VM guest agents +hyperv +open-vm-tools +qemu-guest-agent virtualbox-guest-utils-nox -vpnc -wireless-regdb -wireless_tools -wpa_supplicant -wvdial -xdg-utils -xfsprogs -xl2tpd -bash -eza -cachyos-keyring -cachyos-mirrorlist diff --git a/syslinux/archiso_head.cfg b/syslinux/archiso_head.cfg index 671ab4e..b8d6810 100644 --- a/syslinux/archiso_head.cfg +++ b/syslinux/archiso_head.cfg @@ -1,6 +1,6 @@ SERIAL 0 115200 UI vesamenu.c32 -MENU TITLE Arch Linux +MENU TITLE SimulationOS MENU BACKGROUND splash.png MENU WIDTH 78 diff --git a/syslinux/archiso_pxe-linux.cfg b/syslinux/archiso_pxe-linux.cfg index 5d0f741..726e5be 100644 --- a/syslinux/archiso_pxe-linux.cfg +++ b/syslinux/archiso_pxe-linux.cfg @@ -1,32 +1,19 @@ -LABEL arch64_nbd +LABEL simulationos TEXT HELP -Boot the Arch Linux install medium using NBD. -It allows you to install Arch Linux or perform system maintenance. +Boot SimulationOS via PXE. ENDTEXT -MENU LABEL Arch Linux install medium (x86_64, NBD) -LINUX ::/%INSTALL_DIR%/boot/x86_64/vmlinuz-linux -INITRD ::/%INSTALL_DIR%/boot/x86_64/initramfs-linux.img -APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% archiso_nbd_srv=${pxeserver} cms_verify=y +MENU LABEL SimulationOS (%ARCH%, PXE) +LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos +INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img +APPEND archisobasedir=%INSTALL_DIR% archiso_http_srv=${pxeserver}/ %KERNEL_PARAMS% SYSAPPEND 3 -LABEL arch64_nfs +LABEL simulationosspeech TEXT HELP -Boot the Arch Linux live medium using NFS. -It allows you to install Arch Linux or perform system maintenance. +Boot SimulationOS via PXE with the speakup screen reader. ENDTEXT -MENU LABEL Arch Linux install medium (x86_64, NFS) -LINUX ::/%INSTALL_DIR%/boot/x86_64/vmlinuz-linux -INITRD ::/%INSTALL_DIR%/boot/x86_64/initramfs-linux.img -APPEND archisobasedir=%INSTALL_DIR% archiso_nfs_srv=${pxeserver}:/run/archiso/bootmnt cms_verify=y -SYSAPPEND 3 - -LABEL arch64_http -TEXT HELP -Boot the Arch Linux live medium using HTTP. -It allows you to install Arch Linux or perform system maintenance. -ENDTEXT -MENU LABEL Arch Linux install medium (x86_64, HTTP) -LINUX ::/%INSTALL_DIR%/boot/x86_64/vmlinuz-linux -INITRD ::/%INSTALL_DIR%/boot/x86_64/initramfs-linux.img -APPEND archisobasedir=%INSTALL_DIR% archiso_http_srv=http://${pxeserver}/ cms_verify=y +MENU LABEL SimulationOS (%ARCH%, PXE) with ^speech +LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos +INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img +APPEND archisobasedir=%INSTALL_DIR% archiso_http_srv=${pxeserver}/ accessibility=on %KERNEL_PARAMS% SYSAPPEND 3 diff --git a/syslinux/archiso_sys-linux.cfg b/syslinux/archiso_sys-linux.cfg index 919e158..ca17917 100644 --- a/syslinux/archiso_sys-linux.cfg +++ b/syslinux/archiso_sys-linux.cfg @@ -1,20 +1,19 @@ -LABEL arch64 +LABEL simulationos TEXT HELP -Boot the Arch Linux install medium on BIOS. -It allows you to install Arch Linux or perform system maintenance. +Boot SimulationOS on BIOS. +Starts the live Hyprland desktop, from which SimulationOS can be installed. ENDTEXT -MENU LABEL Arch Linux install medium (x86_64, BIOS) -LINUX /%INSTALL_DIR%/boot/x86_64/vmlinuz-linux -INITRD /%INSTALL_DIR%/boot/x86_64/initramfs-linux.img -APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% +MENU LABEL SimulationOS (%ARCH%, BIOS) +LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos +INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img +APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% %KERNEL_PARAMS% # Accessibility boot option -LABEL arch64speech +LABEL simulationosspeech TEXT HELP -Boot the Arch Linux install medium on BIOS with speakup screen reader. -It allows you to install Arch Linux or perform system maintenance with speech feedback. +Boot SimulationOS on BIOS with the speakup screen reader. ENDTEXT -MENU LABEL Arch Linux install medium (x86_64, BIOS) with ^speech -LINUX /%INSTALL_DIR%/boot/x86_64/vmlinuz-linux -INITRD /%INSTALL_DIR%/boot/x86_64/initramfs-linux.img -APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on +MENU LABEL SimulationOS (%ARCH%, BIOS) with ^speech +LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos +INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img +APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on %KERNEL_PARAMS% diff --git a/syslinux/archiso_sys.cfg b/syslinux/archiso_sys.cfg index d93bcfe..1b8b1b9 100644 --- a/syslinux/archiso_sys.cfg +++ b/syslinux/archiso_sys.cfg @@ -1,6 +1,6 @@ INCLUDE archiso_head.cfg -DEFAULT arch64 +DEFAULT simulationos TIMEOUT 150 INCLUDE archiso_sys-linux.cfg From 90ea10c604a10201ec0754e8335c5f03ba8ad477 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:20:27 +0300 Subject: [PATCH 03/33] desktop: replace inherited personal dotfiles with a shipped Hyprland desktop The inherited config was the author's laptop setup and could not work on another machine: - exec-once = brightnessctl set 0% -> black screen on first login - wallpaper at /mnt/m.2_ssd/Linux/Wallpapers/1354205.jpeg - ~/.scripts/Acer/PredatorSense/... on autostart - hardcoded USB audio sink, SYNA7DB5:00 touchpad, eDP-2 output - required grimblast / auto-cpufreq / pamixer / ML4W, none shipped - two wallpaper daemons (swww + hyprpaper); monitor.conf AND monitors.conf; several files shipped but never sourced Replaced with one hyprland.conf plus an empty monitors.conf for per-machine overrides. Every exec-once and keybind target maps to a shipped package or a script in airootfs/usr/local/bin. Waybar uses built-in modules only, so a missing helper can never blank the bar. Also fixes .bashrc, which aliased rm to trash-put without shipping it, breaking rm in the live session. Wallpaper is a real 1920x1080 asset, reproducible via scripts/make-wallpaper.py. --- airootfs/etc/skel/.bashrc | 138 +++--- .../hypr/config/.jukit/.jukit_info.json | 1 - .../skel/.config/hypr/config/animations.conf | 37 -- .../skel/.config/hypr/config/autostart.conf | 87 ---- .../etc/skel/.config/hypr/config/colors.conf | 146 ------ .../skel/.config/hypr/config/decorations.conf | 97 ---- .../skel/.config/hypr/config/defaults.conf | 71 --- .../skel/.config/hypr/config/environment.conf | 108 ----- .../skel/.config/hypr/config/fan_curve.json | 56 --- .../etc/skel/.config/hypr/config/input.conf | 82 ---- .../skel/.config/hypr/config/keybinds.conf | 285 ------------ .../etc/skel/.config/hypr/config/monitor.conf | 20 - .../skel/.config/hypr/config/user-config.conf | 14 - .../skel/.config/hypr/config/variables.conf | 95 ---- .../skel/.config/hypr/config/windowrules.conf | 162 ------- .../skel/.config/hypr/config/workflows.conf | 18 - airootfs/etc/skel/.config/hypr/hyprland.conf | 217 +++++++-- airootfs/etc/skel/.config/hypr/hyprlock.conf | 33 ++ airootfs/etc/skel/.config/hypr/hyprpaper.conf | 9 +- airootfs/etc/skel/.config/hypr/monitors.conf | 20 +- .../etc/skel/.config/hypr/scripts/cava.js | 92 ---- .../etc/skel/.config/hypr/scripts/cleanup.sh | 14 - .../etc/skel/.config/hypr/scripts/cpufreq.sh | 15 - .../etc/skel/.config/hypr/scripts/gamemode.sh | 25 - .../hypr/scripts/generate_wallpapers.sh | 97 ---- .../hypr/scripts/init-wallpaper-engine.sh | 27 -- .../skel/.config/hypr/scripts/internal-mic.sh | 46 -- .../skel/.config/hypr/scripts/kill-window.sh | 4 - .../etc/skel/.config/hypr/scripts/light.sh | 6 - .../skel/.config/hypr/scripts/loadconfig.sh | 2 - .../.config/hypr/scripts/manage_wallpaper.sh | 23 - .../hypr/scripts/mic-default-volume.sh | 19 - .../etc/skel/.config/hypr/scripts/power.sh | 54 --- .../etc/skel/.config/hypr/scripts/screenshot | 3 - .../skel/.config/hypr/scripts/screenshot.sh | 224 --------- .../skel/.config/hypr/scripts/screenshot_area | 3 - .../skel/.config/hypr/scripts/screenshot_full | 3 - .../skel/.config/hypr/scripts/switch_audio.sh | 47 -- .../.config/hypr/scripts/toggle-touchpad.sh | 28 -- .../.config/hypr/scripts/toggle_display.sh | 10 - .../skel/.config/hypr/scripts/toggle_mic.sh | 16 - .../.config/hypr/scripts/wallpaper-cache.sh | 5 - .../.config/hypr/scripts/wallpaper-restore.sh | 42 -- .../skel/.config/hypr/scripts/wallpaper.sh | 28 -- .../.config/hypr/scripts/wofi-clipbaord.sh | 13 - airootfs/etc/skel/.config/hypr/scripts/xdg.sh | 33 -- .../etc/skel/.config/hypr/wallpapers.conf | 100 ---- .../etc/skel/.config/hypr/workspaces.conf | 0 airootfs/etc/skel/.config/kitty/kitty.conf | 9 + airootfs/etc/skel/.config/mako/config | 16 + airootfs/etc/skel/.config/waybar/config | 369 --------------- airootfs/etc/skel/.config/waybar/config.jsonc | 86 ++++ .../etc/skel/.config/waybar/mediaplayer.py | 127 ------ .../etc/skel/.config/waybar/modules/cava.sh | 44 -- .../skel/.config/waybar/modules/cpu-temp.sh | 160 ------- .../skel/.config/waybar/modules/cpufreq.sh | 69 --- .../skel/.config/waybar/modules/debug-temp.sh | 58 --- .../skel/.config/waybar/modules/gamemode.sh | 78 ---- .../skel/.config/waybar/modules/gpu-temp.sh | 93 ---- .../.config/waybar/modules/network_traffic.sh | 110 ----- .../.config/waybar/modules/power_details.sh | 41 -- .../.config/waybar/modules/power_monitor.sh | 179 -------- .../.config/waybar/modules/record-status.sh | 9 - .../.config/waybar/modules/refresh-rate.sh | 68 --- .../skel/.config/waybar/modules/storage.sh | 25 - .../.config/waybar/modules/system_power.sh | 49 -- .../.config/waybar/modules/toggle-record.sh | 32 -- airootfs/etc/skel/.config/waybar/style.css | 430 ++---------------- airootfs/usr/local/bin/simos-clipboard | 9 + airootfs/usr/local/bin/simos-powermenu | 19 + airootfs/usr/local/bin/simos-screenshot | 44 ++ .../simulationos/simulationos-alpha.png | Bin 0 -> 304608 bytes airootfs/usr/share/pixmaps/simulationos.svg | 15 + scripts/make-wallpaper.py | 71 +++ 74 files changed, 601 insertions(+), 4284 deletions(-) delete mode 100755 airootfs/etc/skel/.config/hypr/config/.jukit/.jukit_info.json delete mode 100755 airootfs/etc/skel/.config/hypr/config/animations.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/autostart.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/colors.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/decorations.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/defaults.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/environment.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/fan_curve.json delete mode 100755 airootfs/etc/skel/.config/hypr/config/input.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/keybinds.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/monitor.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/user-config.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/variables.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/windowrules.conf delete mode 100755 airootfs/etc/skel/.config/hypr/config/workflows.conf mode change 100755 => 100644 airootfs/etc/skel/.config/hypr/hyprland.conf create mode 100644 airootfs/etc/skel/.config/hypr/hyprlock.conf mode change 100755 => 100644 airootfs/etc/skel/.config/hypr/hyprpaper.conf mode change 100755 => 100644 airootfs/etc/skel/.config/hypr/monitors.conf delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/cava.js delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/cleanup.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/cpufreq.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/gamemode.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/generate_wallpapers.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/init-wallpaper-engine.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/internal-mic.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/kill-window.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/light.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/loadconfig.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/manage_wallpaper.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/mic-default-volume.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/power.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/screenshot delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/screenshot.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/screenshot_area delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/screenshot_full delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/switch_audio.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/toggle-touchpad.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/toggle_display.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/toggle_mic.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/wallpaper-cache.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/wallpaper-restore.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/wallpaper.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/wofi-clipbaord.sh delete mode 100755 airootfs/etc/skel/.config/hypr/scripts/xdg.sh delete mode 100755 airootfs/etc/skel/.config/hypr/wallpapers.conf delete mode 100755 airootfs/etc/skel/.config/hypr/workspaces.conf create mode 100644 airootfs/etc/skel/.config/kitty/kitty.conf create mode 100644 airootfs/etc/skel/.config/mako/config delete mode 100755 airootfs/etc/skel/.config/waybar/config create mode 100644 airootfs/etc/skel/.config/waybar/config.jsonc delete mode 100755 airootfs/etc/skel/.config/waybar/mediaplayer.py delete mode 100755 airootfs/etc/skel/.config/waybar/modules/cava.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/cpu-temp.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/cpufreq.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/debug-temp.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/gamemode.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/gpu-temp.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/network_traffic.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/power_details.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/power_monitor.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/record-status.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/refresh-rate.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/storage.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/system_power.sh delete mode 100755 airootfs/etc/skel/.config/waybar/modules/toggle-record.sh mode change 100755 => 100644 airootfs/etc/skel/.config/waybar/style.css create mode 100755 airootfs/usr/local/bin/simos-clipboard create mode 100755 airootfs/usr/local/bin/simos-powermenu create mode 100755 airootfs/usr/local/bin/simos-screenshot create mode 100644 airootfs/usr/share/backgrounds/simulationos/simulationos-alpha.png create mode 100644 airootfs/usr/share/pixmaps/simulationos.svg create mode 100755 scripts/make-wallpaper.py diff --git a/airootfs/etc/skel/.bashrc b/airootfs/etc/skel/.bashrc index 3f9e3b9..68c9ada 100644 --- a/airootfs/etc/skel/.bashrc +++ b/airootfs/etc/skel/.bashrc @@ -1,125 +1,91 @@ # -# ~/.bashrc +# ~/.bashrc - SimulationOS +# +# Every alias here refers to a command that SimulationOS actually installs. +# The previous version aliased `rm` to trash-put and used nvim/nvidia-smi/ +# snapper/auto-cpufreq without shipping any of them, which broke `rm` in the +# live session. # # If not running interactively, don't do anything [[ $- != *i* ]] && return -# Change the window title PS1='[\u@\h \W]\$ ' [ -r /usr/share/bash-completion/bash_completion ] && . /usr/share/bash-completion/bash_completion -# Export export EDITOR='nvim' export VISUAL='nvim' export HISTCONTROL=ignoreboth:erasedups -#ignore upper and lowercase when TAB completion -bind "set completion-ignore-case on" - -#system -alias edital='$EDITOR ~/.bashrc' -alias applyal='source ~/.bashrc' -alias please='sudo' - - +# ignore case during TAB completion +bind "set completion-ignore-case on" 2>/dev/null +# ------------------------------------------------------------------- listing +alias ls='eza --color=always --group-directories-first --icons' alias l='eza --color=always --group-directories-first --icons' -alias l.='eza -a | grep -e '\''^\.'\''' alias la='eza -al --color=always --group-directories-first --icons' alias ll='eza -l --color=always --group-directories-first --icons' -alias ls='eza -a --color=always --group-directories-first --icons' alias lt='eza -aT --color=always --group-directories-first --icons' + +# ------------------------------------------------------------------- general alias grep='grep --colour=auto' -alias egrep='egrep --colour=auto' -alias fgrep='fgrep --colour=auto' -alias df='df -h' # human-readable sizes -alias free='free -m' -alias c="clear" -alias n="nvim" -alias ex="exit" -alias fs="fastfetch" -alias nv='nvidia-smi' +alias df='df -h' +alias du='du -h --max-depth=1' +alias free='free -m' +alias c='clear' +alias n='nvim' +alias fs='fastfetch' alias ..='cd ..' alias ...='cd ../..' alias ....='cd ../../..' -alias .....='cd ../../../..' - -# git -alias du='du -h --max-depth=1' +# ----------------------------------------------------------------------- git +alias gs='git status' +alias ga='git add' alias gc='git commit -m' alias gco='git checkout' alias gb='git branch' alias gd='git diff' -alias ga='git add' -alias gs='git status' -alias gp='git push' -alias gpm='git push origin main' -alias gl='git pull' -alias gn='git clone' -alias gpr='git pull --rebase' -#newtworks -alias ports='netstat -tulanp' - -#pacman -alias belong='sudo pacman -Qo' +# -------------------------------------------------------------------- pacman alias in='sudo pacman -S' -alias qu='sudo pacman -Sii' alias re='sudo pacman -Rs' +alias up='sudo pacman -Syu' alias search='pacman -Ss' -alias up='sudo pacman -Syyu' +alias belong='pacman -Qo' -#etc -alias kernel='ls /usr/lib/modules' +# --------------------------------------------------------------- SimulationOS +alias install-simulationos='simulationos-install' alias kernels='ls /usr/lib/modules' -alias rm='trash-put' -alias snapchome='sudo snapper -c home create-config /home' -alias snapcroot='sudo snapper -c root create-config /' -alias sound='pavucontrol' - -ex () -{ - if [ -f $1 ] ; then - case $1 in - *.tar.bz2) tar xjf $1 ;; - *.tar.gz) tar xzf $1 ;; - *.bz2) bunzip2 $1 ;; - *.rar) unrar x $1 ;; - *.gz) gunzip $1 ;; - *.tar) tar xf $1 ;; - *.tbz2) tar xjf $1 ;; - *.tgz) tar xzf $1 ;; - *.zip) unzip $1 ;; - *.Z) uncompress $1;; - *.7z) 7z x $1 ;; - *.deb) ar x $1 ;; - *.tar.xz) tar xf $1 ;; - *.tar.zst) tar xf $1 ;; - *) echo "'$1' cannot be extracted via ex()" ;; - esac - else - echo "'$1' is not a valid file" +# extract almost anything +extract() { + if [ ! -f "$1" ]; then + printf 'extract: %s is not a valid file\n' "$1" >&2 + return 1 fi + case "$1" in + *.tar.bz2|*.tbz2) tar xjf "$1" ;; + *.tar.gz|*.tgz) tar xzf "$1" ;; + *.tar.xz|*.tar.zst|*.tar) tar xf "$1" ;; + *.bz2) bunzip2 "$1" ;; + *.gz) gunzip "$1" ;; + *.zip) unzip "$1" ;; + *.7z) 7z x "$1" ;; + *) printf 'extract: unsupported archive %s\n' "$1" >&2; return 1 ;; + esac } -# Bash won't get SIGWINCH if another process is in the foreground. -# Enable checkwinsize so that bash will check the terminal size when -# it regains control. #65623 -# http://cnswww.cns.cwru.edu/~chet/bash/FAQ (E11) shopt -s checkwinsize - -#beautiful stuff -shopt -s autocd # change to named directory -shopt -s cdspell # autocorrects cd misspellings -shopt -s cmdhist # save multi-line commands in history as single line -shopt -s dotglob -shopt -s histappend # do not overwrite history -shopt -s expand_aliases # expand aliases - - - -#fastfetch +shopt -s autocd +shopt -s cdspell +shopt -s cmdhist +shopt -s histappend +shopt -s expand_aliases + +# Short system summary on the first interactive shell of a session. +if [ -z "$SIMOS_FETCH_SHOWN" ] && [ -t 1 ]; then + export SIMOS_FETCH_SHOWN=1 + command -v fastfetch >/dev/null 2>&1 && fastfetch +fi diff --git a/airootfs/etc/skel/.config/hypr/config/.jukit/.jukit_info.json b/airootfs/etc/skel/.config/hypr/config/.jukit/.jukit_info.json deleted file mode 100755 index 92c7342..0000000 --- a/airootfs/etc/skel/.config/hypr/config/.jukit/.jukit_info.json +++ /dev/null @@ -1 +0,0 @@ -{"terminal": "nvimterm"} \ No newline at end of file diff --git a/airootfs/etc/skel/.config/hypr/config/animations.conf b/airootfs/etc/skel/.config/hypr/config/animations.conf deleted file mode 100755 index a6993aa..0000000 --- a/airootfs/etc/skel/.config/hypr/config/animations.conf +++ /dev/null @@ -1,37 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Animations Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# https://wiki.hyprland.org/0.45.0/Configuring/Animations/ - -animations { - enabled = yes - bezier = overshot, 0.13, 0.99, 0.29, 1.1 - # animation = windowsIn, 1, 4, overshot, slide - # animation = windowsOut, 1, 5, default, popin 80% - # animation = border, 1, 5, default - # animation = workspacesIn, 1, 6, overshot, slide - # animation = workspacesOut, 1, 6, overshot, slidefade 80% - - - bezier = linear, 0, 0, 1, 1 - bezier = md3_standard, 0.2, 0, 0, 1 - bezier = md3_decel, 0.05, 0.7, 0.1, 1 - bezier = md3_accel, 0.3, 0, 0.8, 0.15 - bezier = overshot_alt, 0.05, 0.9, 0.1, 1.1 - bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0 - bezier = menu_decel, 0.1, 1, 0, 1 - bezier = menu_accel, 0.38, 0.04, 1, 0.07 - - # Animation configs - balanced between aesthetics and speed - animation = windows, 1, 3, md3_decel, popin 60% - animation = windowsIn, 1, 3, md3_decel, popin 60% - animation = windowsOut, 1, 3, md3_accel, popin 60% - animation = border, 1, 5, default - animation = fade, 1, 3, md3_decel - animation = layersIn, 1, 3, menu_decel, slide - animation = layersOut, 1, 1.6, menu_accel - animation = fadeLayersIn, 1, 2, menu_decel - animation = fadeLayersOut, 1, 0.5, menu_accel - animation = workspaces, 1, 3, md3_decel, slide -} diff --git a/airootfs/etc/skel/.config/hypr/config/autostart.conf b/airootfs/etc/skel/.config/hypr/config/autostart.conf deleted file mode 100755 index f28172a..0000000 --- a/airootfs/etc/skel/.config/hypr/config/autostart.conf +++ /dev/null @@ -1,87 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Autostart Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -source = ~/.config/hypr/config/defaults.conf -#exec-once = python ~/.scripts/Acer/predator-handler.py -# Autostart wiki https://wiki.hyprland.org/0.45.0/Configuring/Keywords/#executing # - -#exec-once = swaybg -o \* -i /usr/share/wallpapers/cachyos-wallpapers/BlueFeathers.png -m fill -#exec-once = hyprpaper -exec-once = swww-daemon -exec-once = swww img /mnt/m.2_ssd/Linux/Wallpapers/1354205.jpeg -exec-once = waybar & -#exec-once = fcitx5 -d & -exec-once = brightnessctl set 0% -#exec-once = mako & -exec-once = swaync & -exec-once = nm-applet --indicator & - -#exec-once = bash -c "mkfifo /tmp/$HYPRLAND_INSTANCE_SIGNATURE.wob && tail -f /tmp/$HYPRLAND_INSTANCE_SIGNATURE.wob | wob -c ~/.config/hypr/wob.ini & disown" & - -exec-once = /usr/lib/polkit-kde-authentication-agent-1 & -#exec-once = hypridle -exec-once = ~/.config/hypr/scripts/mic-default-volume.sh -#exec-once = ~/.scripts/hyprland/sound-conig.sh - -# ## Slow app launch fix -exec-once = systemctl --user import-environment & -exec-once = hash dbus-update-activation-environment 2>/dev/null & -exec-once = dbus-update-activation-environment --systemd & -exec-once = ~/.scripts/Acer/PredatorSense/rgbkb/rgbkb.service.sh - -# ## Idle configuration -#exec-once = $idlehandler -#exec-once = gammastep -l auto -t 5500:3500 -b 1.0:0.8 -#exec-once = gammy & -# -#exec-once = ~/.config/hypr/scripts/light.sh - -#input { -# kb_layout = us,ara -# kb_variant = ,digits - # kb_options = grp:shifts_toggle,caps:swapescape -#} - - -# USER MADE - -# Clipboard -#exec-once = cliphist wipe; wl-paste --watch cliphist store -#exec-once = wl-paste --type text --watch cliphist store # Stores only text data -#exec-once = wl-paste --type image --watch cliphist store # Stores only image data -exec-once = wl-paste --watch cliphist store - - -#exec-once=waypaper --restore - -#env = QT_QPA_PLATFORMTHEME,kde -#env = XDG_MENU_PREFIX,arch- - -# exec-once = /usr/bin/lxqt-policykit-agent -# exec-once = /usr/lib/kdeconnectd -exec-once = kdeconnectd - -#exec-once=walker --gapplication-service # Application Launcher - - -#exec-once = systemctl --user start fan-curve - - - - - - - - - - - - - - - - - - - diff --git a/airootfs/etc/skel/.config/hypr/config/colors.conf b/airootfs/etc/skel/.config/hypr/config/colors.conf deleted file mode 100755 index 1a150bd..0000000 --- a/airootfs/etc/skel/.config/hypr/config/colors.conf +++ /dev/null @@ -1,146 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Colors Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -#$cachylgreen = rgba(82dcccff) -#$cachymgreen = rgba(00aa84ff) -#$cachydgreen = rgba(007d6fff) -#$cachylblue = rgba(01ccffff) -#$cachymblue = rgba(182545ff) -#$cachydblue = rgba(111826ff) -#$cachywhite = rgba(ffffffff) -#$cachygrey = rgba(ddddddff) -#$cachygray = rgba(798bb2ff) -#$kararwallpaper = rgba(f58f29ff) -#$kararwallpaper = rgba(e8aa3aff) - - - - - - - - - - - - -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Colors Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# Your custom accent colors -$cachylgreen = rgba(82dcccff) -$cachymgreen = rgba(00aa84ff) -$cachydgreen = rgba(007d6fff) -$cachylblue = rgba(01ccffff) -$cachymblue = rgba(182545ff) -$cachydblue = rgba(111826ff) -$cachywhite = rgba(ffffffff) -$cachygrey = rgba(ddddddff) -$cachygray = rgba(798bb2ff) - -$kararwallpaper = rgba(e8aa3aff) - -# Extended color palette for UI and theming -$background = rgba(18120cff) - -$error = rgba(ffb4abff) - -$error_container = rgba(93000aff) - -$inverse_on_surface = rgba(372f27ff) - -$inverse_primary = rgba(825513ff) - -$inverse_surface = rgba(eee0d4ff) - -$on_background = rgba(eee0d4ff) - -$on_error = rgba(690005ff) - -$on_error_container = rgba(ffdad6ff) - -$on_primary = rgba(472a00ff) - -$on_primary_container = rgba(ffddb7ff) - -$on_primary_fixed = rgba(2a1700ff) - -$on_primary_fixed_variant = rgba(653e00ff) - -$on_secondary = rgba(3f2d17ff) - -$on_secondary_container = rgba(fcddbcff) - -$on_secondary_fixed = rgba(281805ff) - -$on_secondary_fixed_variant = rgba(57432bff) - -$on_surface = rgba(eee0d4ff) - -$on_surface_variant = rgba(d4c4b5ff) - -$on_tertiary = rgba(273513ff) - -$on_tertiary_container = rgba(d7e9b9ff) - -$on_tertiary_fixed = rgba(121f02ff) - -$on_tertiary_fixed_variant = rgba(3c4c28ff) - -$outline = rgba(9c8e80ff) - -$outline_variant = rgba(504539ff) - -$primary = rgba(f7bb71ff) - -$primary_container = rgba(653e00ff) - -$primary_fixed = rgba(ffddb7ff) - -$primary_fixed_dim = rgba(f7bb71ff) - -$scrim = rgba(000000ff) - -$secondary = rgba(dfc2a2ff) - -$secondary_container = rgba(57432bff) - -$secondary_fixed = rgba(fcddbcff) - -$secondary_fixed_dim = rgba(dfc2a2ff) - -$shadow = rgba(000000ff) - -$source_color = rgba(c58012ff) - -$surface = rgba(18120cff) - -$surface_bright = rgba(403830ff) - -$surface_container = rgba(251e17ff) - -$surface_container_high = rgba(302921ff) - -$surface_container_highest = rgba(3b332bff) - -$surface_container_low = rgba(211a13ff) - -$surface_container_lowest = rgba(130d07ff) - -$surface_dim = rgba(18120cff) - -$surface_tint = rgba(f7bb71ff) - -$surface_variant = rgba(504539ff) - -$tertiary = rgba(bbcd9eff) - -$tertiary_container = rgba(3c4c28ff) - -$tertiary_fixed = rgba(d7e9b9ff) - -$tertiary_fixed_dim = rgba(bbcd9eff) - - diff --git a/airootfs/etc/skel/.config/hypr/config/decorations.conf b/airootfs/etc/skel/.config/hypr/config/decorations.conf deleted file mode 100755 index 8707190..0000000 --- a/airootfs/etc/skel/.config/hypr/config/decorations.conf +++ /dev/null @@ -1,97 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Decorations Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -#source = ~/.config/hypr/config/colors.conf - -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#decoration - -#decoration { -# active_opacity = 1 -# rounding = 4 - - # https://wiki.hyprland.org/0.45.0/Configuring/Variables/#blur -# blur { -# size = 15 -# passes = 2 # more passes = more resource intensive. -# xray = true -# } -# -# # https://wiki.hyprland.org/0.45.0/Configuring/Variables/#shadow -# shadow { -# enabled = true -# } -# -#} - - - - # ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Decorations Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -source = ~/.config/hypr/config/colors.conf - -decoration { - rounding = 8 - active_opacity = 1 - - # ── Blur Settings ────────────────────────────────────────── - blur { - enabled = true - xray = true # May be disabled for performance if needed - special = true - new_optimizations = true - size = 8 - passes = 1 - brightness = 2.0 - noise = 0.20 - contrast = 0.70 - popups = false - popups_ignorealpha = 0.6 - - # PERFORMANCE ADDITIONS: - ignore_opacity = true # Better performance with transparent windows - vibrancy = 0.0 # Disable vibrancy effects - vibrancy_darkness = 0.0 # Disable vibrancy darkness - } - - # ── Shadow Settings ──────────────────────────────────────── - shadow { - enabled = true - ignore_window = true - range = 12 - offset = 0 2 - render_power = 2 - color = rgba(0000002A) - - # PERFORMANCE ADDITIONS: - sharp = false # Soft shadows are faster - } - - # ── Inactive Dimming ─────────────────────────────────────── - dim_inactive = true - dim_strength = 0.1 - dim_special = 0 - - # ── Opacity (uncomment if needed) ────────────────────────── - inactive_opacity = 1 - fullscreen_opacity = 1 - - # ── Optional Shaders ─────────────────────────────────────── - # screen_shader = ~/.config/hypr/shaders/nothing.frag - # screen_shader = ~/.config/hypr/shaders/vibrance.frag -} - -layerrule = blur, swaync-control-center -layerrule = ignorezero, swaync-notification-window -layerrule = animation slide right, swaync-control-center - -layerrule = blur, swaync-control-center -layerrule = blur, swaync-notification-window - -layerrule = ignorezero, swaync-control-center -layerrule = ignorezero, swaync-notification-window - -layerrule = ignorealpha 0.5, swaync-control-center -layerrule = ignorealpha 0.5, swaync-notification-window diff --git a/airootfs/etc/skel/.config/hypr/config/defaults.conf b/airootfs/etc/skel/.config/hypr/config/defaults.conf deleted file mode 100755 index 7a5a2e6..0000000 --- a/airootfs/etc/skel/.config/hypr/config/defaults.conf +++ /dev/null @@ -1,71 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Defaults Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - - -# ── File Manager (Performance Choice) ────────────────────────── -# Option 1: Lightweight (fastest) -#$filemanager = thunar # Lighter than dolphin -# Option 2: Current (moderate performance) - $filemanager = dolphin --platformtheme kde - -# ── Browser (Performance Choice) ─────────────────────────────── -$browser = firefox # Good choice for performance - -# ── App Launcher (Performance Optimized) ─────────────────────── -# Simplified rofi without heavy themes -#$applauncher = rofi -show drun -no-plugins # Faster startup -# Or your current themed version (slightly slower but prettier) -$applauncher = rofi -show drun -theme ~/.config/rofi/launchers/type-3/style-5.rasi - -# ── Terminal (Performance Choice) ────────────────────────────── -# Current kitty is already fast -$terminal = kitty -# Alternative lightweight options: -# $terminal = foot # Faster startup -# $terminal = alacritty # Also very fast - -# ── Screen Capture (Optimized) ───────────────────────────────── -# Simpler capture without swappy for speed -#$capturing = grim -g "$(slurp)" ~/Pictures/screenshot-$(date +%Y%m%d_%H%M%S).png -$capturing = grimblast --freeze save area - | satty --filename - --copy-command wl-copy --save-after-copy --early-exit --init-tool brush -# Or keep your current with swappy (slower but more features) -# $capturing = grim -g "$(slurp)" - | swappy -f - - -# ── Clipboard (Performance Optimized) ────────────────────────── -# Simplified clipboard without images for speed -#$clip = cliphist list | rofi -dmenu -p "Clipboard" | cliphist decode | wl-copy -# Or keep your current with icons (slower but prettier) -$clip = rofi -modi clipboard:~/.local/bin/cliphist-rofi-img -show clipboard -show-icons -theme ~/.config/rofi/launchers/type-3/clipboard.rasi - -# ── PDF Viewer (Performance Choice) ──────────────────────────── -# Lighter alternatives to okular -#$pdfviewer = zathura # Much faster than okular -# Or keep okular if you need the features -$pdfviewer = okular --platformtheme kde - -# ── Additional Performance Variables ─────────────────────────── - - - -# Screenshots -#$shot-region = grimblast copy area -#$shot-window = grimblast copy active -#$shot-screen = grimblast copy output - - - -# OLD -#$filemanager = dolphin --platformtheme kde -#$browser = cachy-browser -#$browser = firefox -#$applauncher = rofi -show combi -modi window,run,combi -combi-modi window,run -#$applauncher = rofi -show drun -#$applauncher = rofi -show drun -theme ~/.config/rofi/launchers/type-3/style-5.rasi -#$terminal = kitty -#$idlehandler = swayidle -w timeout 300 'swaylock -f -c 000000' before-sleep 'swaylock -f -c 000000' -#$capturing = grim -g "$(slurp)" - | swappy -f - -#$power = ~/.config/rofi/powermenu/type-2/powermenu.sh -#$clip = rofi -modi clipboard:~/.local/bin/cliphist-rofi-img -show clipboard -show-icons -theme ~/.config/rofi/launchers/type-3/clipboard.rasi -#$clip = cliphist list | rofi -modi clipboard -show clipboard -theme ~/.config/rofi/launchers/type-3/clipboard.rasi -dmenu | cliphist decode | wl-copy -#$pdfviewer = okular --platformtheme kde diff --git a/airootfs/etc/skel/.config/hypr/config/environment.conf b/airootfs/etc/skel/.config/hypr/config/environment.conf deleted file mode 100755 index c105622..0000000 --- a/airootfs/etc/skel/.config/hypr/config/environment.conf +++ /dev/null @@ -1,108 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Enviroment Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -envd = HYPRCURSOR_SIZE,24 -envd = XCURSOR_SIZE,24 -envd = QT_CURSOR_SIZE,24 -env = GTK_USE_PORTAL,1 -env = HYPRCURSOR_SIZE,24 -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Environment Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# ── Cursor Sizes ────────────────────────────────────────────── -#envd = HYPRCURSOR_SIZE,24 -#envd = XCURSOR_SIZE,24 -#envd = QT_CURSOR_SIZE,24 -env = XCURSOR_SIZE,24 # In case apps ignore envd - -# ── Input Methods ───────────────────────────────────────────── -# I don't use fcitx - -#env = QT_IM_MODULE,fcitx -#env = XMODIFIERS,@im=fcitx -#env = SDL_IM_MODULE,fcitx -#env = GLFW_IM_MODULE,ibus -#env = INPUT_METHOD,fcitx - -# ── Virtual Environment (AGS) ───────────────────────────────── -env = ILLOGICAL_IMPULSE_VIRTUAL_ENV,~/.local/state/ags/.venv - -# ── Wayland / Desktop Session ───────────────────────────────── -#env = XDG_CURRENT_DESKTOP,KDE -#env = XDG_SESSION_TYPE,wayland -#env = XDG_SESSION_DESKTOP,Hyprland - -env = XDG_CURRENT_DESKTOP,Hyprland -env = XDG_SESSION_DESKTOP,Hyprland -env = XDG_SESSION_TYPE,wayland - -env = QT_QPA_PLATFORM,wayland -env = QT_QPA_PLATFORMTHEME,qt6ct -#env = QT_STYLE_OVERRIDE,kvantum -#env = QT_QPA_PLATFORMTHEME,qt5ct -#env = QT_QPA_PLATFORMTHEME,qt6ct -env = XDG_MENU_PREFIX,arch- - -#env = QT_QPA_PLATFORMTHEME,kde -#env = XDG_MENU_PREFIX,plasma- - - -# ── QT Configuration ────────────────────────────────────────── -#env = QT_QPA_PLATFORM,wayland;xcb -#env = QT_QPA_PLATFORMTHEME,qt6ct -env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1 -#env = QT_AUTO_SCREEN_SCALE_FACTOR,1 - -#env = QT_QPA_PLATFORMTHEME,qt6ct -#env = QT_QPA_PLATFORM,wayland -#env = QT_STYLE_OVERRIDE,Breeze - - -# ── GTK Configuration ───────────────────────────────────────── -#env = GDK_SCALE,1 - -#env = GTK_THEME,Breeze-Dark - -# ── Mozilla (Wayland support) ───────────────────────────────── -env = MOZ_ENABLE_WAYLAND,1 - -# Performance additions: -env = MOZ_WEBRENDER,1 # Enable hardware acceleration -env = MOZ_X11_EGL,1 # Better GPU acceleration - -# ── AppImage Launcher ───────────────────────────────────────── -env = APPIMAGELAUNCHER_DISABLE,1 - -# ── Ozone Platform ──────────────────────────────────────────── -env = OZONE_PLATFORM,wayland - -# ── Optional for Virtual Machines (commented) ───────────────── - env = WLR_NO_HARDWARE_CURSORS,1 - env = WLR_RENDERER_ALLOW_SOFTWARE,1 -# ── Optional NVIDIA Support (commented) ─────────────────────── - env = LIBVA_DRIVER_NAME,nvidia - env = GBM_BACKEND,nvidia-drm - env = __GLX_VENDOR_LIBRARY_NAME,nvidia - env = __GL_VRR_ALLOWED,1 -# env = WLR_DRM_NO_ATOMIC,1 - -env = __GL_VRR_ALLOWED,1 # Enable VRR for smoother experience -env = WLR_RENDERER_ALLOW_SOFTWARE,1 # Fallback option - -# ── Memory/CPU Performance ──────────────────────────────────── -#env = MALLOC_TRIM_THRESHOLD_,100000 # Better memory management -#env = MALLOC_MMAP_THRESHOLD_,131072 # Memory optimization - -# ── Disable Heavy Features ──────────────────────────────────── -# env = GTK_USE_PORTAL,1 # Commented out - portals can be slow -# env = QT_AUTO_SCREEN_SCALE_FACTOR,1 # Disable auto scaling - -# ── GPU USAGE ──────────────────────────────────── -#env = AQ_DRM_DEVICES,/dev/dri/card1:/dev/dri/card0 - -# ── ROFI Clipbaord ──────────────────────────────────── - -env = CLIPHIST_MAX_ITEMS=25 - diff --git a/airootfs/etc/skel/.config/hypr/config/fan_curve.json b/airootfs/etc/skel/.config/hypr/config/fan_curve.json deleted file mode 100755 index c9f2f00..0000000 --- a/airootfs/etc/skel/.config/hypr/config/fan_curve.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "cpu_curve": [ - { - "temp": 35, - "fan": 20 - }, - { - "temp": 50, - "fan": 35 - }, - { - "temp": 65, - "fan": 50 - }, - { - "temp": 75, - "fan": 70 - }, - { - "temp": 85, - "fan": 85 - }, - { - "temp": 95, - "fan": 100 - } - ], - "gpu_curve": [ - { - "temp": 40, - "fan": 25 - }, - { - "temp": 55, - "fan": 40 - }, - { - "temp": 70, - "fan": 55 - }, - { - "temp": 80, - "fan": 75 - }, - { - "temp": 90, - "fan": 90 - }, - { - "temp": 100, - "fan": 100 - } - ], - "hysteresis": 3, - "update_interval": 2 -} \ No newline at end of file diff --git a/airootfs/etc/skel/.config/hypr/config/input.conf b/airootfs/etc/skel/.config/hypr/config/input.conf deleted file mode 100755 index 71c838d..0000000 --- a/airootfs/etc/skel/.config/hypr/config/input.conf +++ /dev/null @@ -1,82 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Input Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# Input wiki https://wiki.hyprland.org/0.45.0/Configuring/Variables/#input - -# Invert touchoad scrolling -input { - touchpad { - natural_scroll = true - - disable_while_typing = true - clickfinger_behavior = true - } -} - -input { - kb_layout = us,ara - kb_variant = , - kb_options=grp:win_space_toggle - numlock_by_default= true - follow_mouse = 1 # 0|1|2|3 - float_switch_override_focus = 2 - repeat_delay = 250 - repeat_rate = 35 - special_fallthrough = true - resolve_binds_by_sym = 1 -# kb_options = grp:shifts_toggle,caps:swapescape -} - - -gestures { - workspace_swipe = true - workspace_swipe_distance = 700 - workspace_swipe_fingers = 4 - workspace_swipe_cancel_ratio = 0.2 - workspace_swipe_min_speed_to_force = 5 - workspace_swipe_direction_lock = true - workspace_swipe_direction_lock_threshold = 10 - workspace_swipe_create_new = true -} - - - -general { - # Gaps and border - gaps_in = 0 - gaps_out = 0 - gaps_workspaces = 0 - border_size = 3 - - # Fallback colors - -col.active_border = rgba(000000FF) # Solid black -col.inactive_border = rgba(00000000) # Fully transparent black - - - resize_on_border = true - no_focus_fallback = true - layout = dwindle - - #focus_to_other_workspaces = true # ahhhh i still haven't properly implemented this - allow_tearing = true # This just allows the `immediate` window rule to work -} - - -misc { - vfr = 1 - vrr = 0 - animate_manual_resizes = false - animate_mouse_windowdragging = false - enable_swallow = false - swallow_regex = (foot|kitty|allacritty|Alacritty) - - disable_hyprland_logo = true - force_default_wallpaper = 0 - new_window_takes_over_fullscreen = 2 - allow_session_lock_restore = true - - disable_splash_rendering = true - initial_workspace_tracking = false -} diff --git a/airootfs/etc/skel/.config/hypr/config/keybinds.conf b/airootfs/etc/skel/.config/hypr/config/keybinds.conf deleted file mode 100755 index 06b317a..0000000 --- a/airootfs/etc/skel/.config/hypr/config/keybinds.conf +++ /dev/null @@ -1,285 +0,0 @@ - -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Keybinds ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -source = ~/.config/hypr/config/defaults.conf - -# Example: Super + W changes wallpaper -bind = SUPER, print, exec, ~/.config/hypr/scripts/manage_wallpaper.sh - -# Mininize -bind = $mainMod, Z, togglespecialworkspace, magic -bind = $mainMod, Z, movetoworkspace, +0 -bind = $mainMod, Z, togglespecialworkspace, magic -bind = $mainMod, Z, movetoworkspace, special:magic -bind = $mainMod, Z, togglespecialworkspace, magic - - - - - -bindd= SUPER,Z,desc,exec,~/bin/zoomshot.sh -#hyprsunset stuff -bindd = ALT SHIFT, equal, ,exec, hyprctl hyprsunset temperature +500 -bindd = ALT SHIFT, minus, ,exec, hyprctl hyprsunset temperature -500 -bindd = CTRL ALT, equal, ,exec, hyprctl hyprsunset temperature 6500 -bindd = CTRL ALT, minus, ,exec, hyprctl hyprsunset temperature 3000 -# https://wiki.hyprland.org/Configuring/Binds/ -bindd = $mainMod , R, render hypr, exec, hyprctl reload -bindd = $mainMod , B, display or hide the bar, exec, pkill -SIGUSR1 waybar -bindd = $mainMod, RETURN, Opens your preferred terminal emulator ($terminal), exec, $terminal -bindd = $mainMod , SHIFT, power shit, exec, wlogout - -bindd = $mainMod, W, opens cachybrowser, exec, $browser -bindd = $mainMod, R, opens Brave Browser, exec, brave -bindd = $mainMod, E, Opens your preferred filemanager ($filemanager), exec, $filemanager -bindd = $mainMod, A, Screen capture selection, exec, $capturing -bindd = $mainMod, Q, Closes (not kill) current window, killactive, #?? -bindd = $mainMod SHIFT, Q, Exits Hyprland by terminating the user sessions, exec, loginctl terminate-user "" -bindd = $mainMod, M, Switches current window between floating and tiling mode, togglefloating, -bindd = $mainMod, D, Runs your application launcher, exec, $applauncher -bindd = $mainMod, F, Toggles current window fullscreen mode, fullscreen -bindd = $mainMod, Y, Pin current window (shows on all workspaces),pin -bindd = $mainMod ALT, J, Toggles curren window split mode, togglesplit, # dwindle #??bindd = $mainMod, P, powerop, exec, $power -bindd = $mainMod, P, PowerMenu, exec, $power -bindd = $mainMod, V, Clipboard, exec, $clip -bindd = $mainMod SHIFT, V, Wofi Clipboard, exec, ~/.config/hypr/scripts/wofi-clipbaord.sh -# USER MADE -#bind = SUPER+E, exec, dolphin -#bind = SUPER, V, exec, cliphist list | cut -d ' ' -f2- | head -n 20 | rofi -dmenu -i -p "Clipboard" | cliphist decode | wl-copy -#bind = SUPER, V, exec, cliphist list | rofi -dmenu | cliphist decode | wl-copy - - -#bind = , print, exec, grim -g "$(slurp)" - | swappy -f - -o /tmp/swappy_edit.png && wl-copy < /tmp/swappy_edit.png -#bind = , print, exec, grimblast --freeze save area - | satty --filename - --copy-command wl-copy --save-after-copy --early-exit --init-tool brush - -bind = , print, exec, grimblast --freeze save area - | satty --filename - --output-filename ~/Pictures/Screenshots/screenshot-$(date +%Y%m%d_%H%M%S).png --copy-command wl-copy --save-after-copy --early-exit --init-tool brush - -#bind = , print, exec, grim -g "$(slurp)" - | swappy -f - -#bind = SUPER, V, exec, rofi -modi clipboard:~/.local/bin/cliphist-rofi-img -show clipboard -show-icons - -# Volume -# Increase Volume: Right Shift + Up Arrow -# bind = RIGHTSHIFT,up,exec,pamixer --increase 5 -bind = RIGHTSHIFT, up, exec,pactl set-sink-volume @DEFAULT_SINK@ +5% - -# Decrease Volume: Right Shift + Down Arrow -bind = RIGHTSHIFT, down, exec, pamixer --decrease 5 - -# Mute Microphone: F8 -bind = , F8,exec, ~/.config/hypr/scripts/toggle_mic.sh - - -bind = , F5, exec, ~/.config/hypr/scripts/switch_audio.sh - -bind = . SHIFT, F5, exec, ~/.config/hypr/scripts/internal-mic.sh - - -#Turn off Display -bind = SUPER, XF86AudioPlay, exec, ~/.config/hypr/scripts/toggle_display.sh - - -# Rofi -bind = SUPER, TAB, exec, rofi -show window -bind = $mainMod, F11, fullscreen - -#bind = SUPER, X, exec, hyprctl dispatch killactive - - -# ======= Grouping Windows ======= -bindd = $mainMod, T, Toggles current window group mode (ungroup all related), togglegroup, -bindd = $mainMod, Tab, Switches to the next window in the group, changegroupactive, f - -# ======= Toggle Gaps ======= - -bindd = $mainMod SHIFT ,G, Set CachyOS default gaps, exec, hyprctl --batch "keyword general:gaps_out 5;keyword general:gaps_in 3" -bindd = $mainMod, G, Remove gaps between window, exec, hyprctl --batch "keyword general:gaps_out 0;keyword general:gaps_in 0" - -# ======= Volume Control ======= - -bindel = $mainMod SHIFT , equal, exec, pactl set-sink-volume @DEFAULT_SINK@ +5% && pactl get-sink-volume @DEFAULT_SINK@ | grep -oP '\d+(?=%)' | awk '{if($1>100) system("pactl set-sink-volume @DEFAULT_SINK@ 100%")}' # Raise Volume -bindel = $mainMod SHIFT , minus, exec, pactl set-sink-volume @DEFAULT_SINK@ -5% # Lower Volume -bindel = $mainMod SHIFT, M, exec, amixer sset Master toggle | sed -En '/\[on\]/ s/.*\[([0-9]+)%\].*/\1/ p; /\[off\]/ s/.*/0/p' | head -1 > /tmp/$HYPRLAND_INSTANCE_SIGNATURE.wob #Mutes player audio - -# ======= Playback Control ======= - -bindd = , XF86AudioPlay, Toggles play/pause, exec, playerctl play-pause -bindd = , XF86AudioNext, Next track, exec, playerctl next -bindd = , XF86AudioPrev, Previous track, exec, playerctl previous - -# ======= Screen Brightness ======= - -bindel = , XF86MonBrightnessUp, exec, brightnessctl s +5% #Increases brightness 5% -bindel = , XF86MonBrightnessDown, exec, brightnessctl s 5%- #Decreases brightness 5% -#bindd = $mainMod SHIFT, P, Runs the calculator application, exec, gnome-calculator -bindd = $mainMod SHIFT, X, Lock the screen, exec, swaylock-fancy -e -K -p 10 -f Hack-Regular -#bindd = $mainMod, O, Reload/restarts Waybar, exec, killall -SIGUSR2 waybar - -# ======= Window Actions ======= - -## Move window with mainMod + LMB/RMB and dragging -bindd = $mainMod, mouse:272, Move the window towards a direction, movewindow -## Move window towards a direction -bindd = $mainMod SHIFT, left, Move active window to the left, movewindow, l -bindd = $mainMod SHIFT, right, Move active window to the right, movewindow, r -bindd = $mainMod SHIFT, up, Move active window upwards, movewindow, u -bindd = $mainMod SHIFT, down, Move active window downwards, movewindow, d -## Move focus with mainMod + arrow keys -bindd = $mainMod, left, Move focus to the left, movefocus, l -bindd = $mainMod, right, Move focus to the right, movefocus, r -bindd = $mainMod, up, Move focus upwards, movefocus, u -bindd = $mainMod, down, Move focus downwards, movefocus, d -## Resizing windows -# Activate keyboard window resize mode -# https://wiki.hyprland.org/Configuring/Binds/#submaps -bindd = $mainMod SHIFT, R, Activates window resizing mode, submap, resize -submap = resize -bindd = , right, Resize to the right (resizing mode), resizeactive, 15 0 -bindd = , left, Resize to the left (resizing mode), resizeactive, -15 0 -bindd = , up, Resize upwards (resizing mode), resizeactive, 0 -15 -bindd = , down, Resize downwards (resizing mode), resizeactive, 0 15 -bindd = , l, Resize to the right (resizing mode), resizeactive, 15 0 -bindd = , h, Resize to the left (resizing mode), resizeactive, -15 0 -bindd = , k, Resize upwards (resizing mode), resizeactive, 0 -15 -bindd = , j, Resize downwards (resizing mode), resizeactive, 0 15 -bindd = , escape, Ends window resizing mode, submap, reset -submap = reset -# Quick resize window with keyboard -# !!! added $mainMod here because CTRL + SHIFT is used for word selection in various text editors -bindd = ALT SHIFT, right, Resize to the right, resizeactive, 15 0 -bindd = ALT SHIFT, left, Resize to the left, resizeactive, -15 0 -bindd = ALT SHIFT, up, Resize upwards, resizeactive, 0 -15 -bindd = ALT SHIFT, down, Resize downwards, resizeactive, 0 15 -bindd = ALT SHIFT, l, Resize to the right, resizeactive, 15 0 -bindd = ALT SHIFT, h, Resize to the left, resizeactive, -15 0 -bindd = ALT SHIFT, k, Resize upwards, resizeactive, 0 -15 -bindd = ALT SHIFT, j, Resize downwards, resizeactive, 0 15 -# Resize window with mainMod + LMB/RMB and dragging -bindm = $mainMod, mouse:273, resizewindow #Resize the window towards a direction -bindm = $mainMod, mouse:272, movewindow #Drag window -## Resizing Windows End # -## Move active window to a workspace with $mainMod + CTRL + [0-9] -bindd = $mainMod SHIFT, 1, Move window and switch to workspace 1, movetoworkspace, 1 -bindd = $mainMod SHIFT, 2, Move window and switch to workspace 2, movetoworkspace, 2 -bindd = $mainMod SHIFT, 3, Move window and switch to workspace 3, movetoworkspace, 3 -bindd = $mainMod SHIFT, 4, Move window and switch to workspace 4, movetoworkspace, 4 -bindd = $mainMod SHIFT, 5, Move window and switch to workspace 5, movetoworkspace, 5 -bindd = $mainMod SHIFT, 6, Move window and switch to workspace 6, movetoworkspace, 6 -bindd = $mainMod SHIFT, 7, Move window and switch to workspace 7, movetoworkspace, 7 -bindd = $mainMod SHIFT, 8, Move window and switch to workspace 8, movetoworkspace, 8 -bindd = $mainMod SHIFT, 9, Move window and switch to workspace 9, movetoworkspace, 9 -bindd = $mainMod SHIFT, 0, Move window and switch to workspace 10, movetoworkspace, 10 -bindd = $mainMod SHIFT, left, Move window and switch to the next workspace, movetoworkspace, -1 -bindd = $mainMod SHIFT, right, Move window and switch to the previous workspace, movetoworkspace, +1 -## Same as above, but doesn't switch to the workspace -bindd = $mainMod CTRL, 1, Move window silently to workspace 1, movetoworkspacesilent, 1 -bindd = $mainMod CTRL, 2, Move window silently to workspace 2, movetoworkspacesilent, 2 -bindd = $mainMod CTRL, 3, Move window silently to workspace 3, movetoworkspacesilent, 3 -bindd = $mainMod CTRL, 4, Move window silently to workspace 4, movetoworkspacesilent, 4 -bindd = $mainMod CTRL, 5, Move window silently to workspace 5, movetoworkspacesilent, 5 -bindd = $mainMod CTRL, 6, Move window silently to workspace 6, movetoworkspacesilent, 6 -bindd = $mainMod CTRL, 7, Move window silently to workspace 7, movetoworkspacesilent, 7 -bindd = $mainMod CTRL, 8, Move window silently to workspace 8, movetoworkspacesilent, 8 -bindd = $mainMod CTRL, 9, Move window silently to workspace 9, movetoworkspacesilent, 9 -bindd = $mainMod CTRL, 0, Move window silently to workspace 10, movetoworkspacesilent, 10 -# Window actions End # - -# ======= Workspace Actions ======= - -# Switch workspaces with mainMod + [0-9] -bindd = $mainMod, 1, Switch to workspace 1, workspace, 1 -bindd = $mainMod, 2, Switch to workspace 2, workspace, 2 -bindd = $mainMod, 3, Switch to workspace 3, workspace, 3 -bindd = $mainMod, 4, Switch to workspace 4, workspace, 4 -bindd = $mainMod, 5, Switch to workspace 5, workspace, 5 -bindd = $mainMod, 6, Switch to workspace 6, workspace, 6 -bindd = $mainMod, 7, Switch to workspace 7, workspace, 7 -bindd = $mainMod, 8, Switch to workspace 8, workspace, 8 -bindd = $mainMod, 9, Switch to workspace 9, workspace, 9 -bindd = $mainMod, 0, Switch to workspace 10, workspace, 10 -# Scroll through existing workspaces with mainMod + , or . -bindd = $mainMod SHIFT, PERIOD, Scroll through workspaces incrementally, workspace, e+1 -bindd = $mainMod SHIFT, COMMA, Scroll through workspaces decrementally, workspace, e-1 -# With $mainMod + scroll -bindd = $mainMod, mouse_down, Scroll through workspaces incrementally, workspace, e+1 -bindd = $mainMod, mouse_up, Scroll through workspaces decrementally, workspace, e-1 -bindd = $mainMod, slash, Switch to the previous workspace, workspace, previous -# Special workspaces (scratchpads) -bindd = $mainMod, PERIOD, Move active window to Special workspace, movetoworkspace,special -bindd = $mainMod, COMMA, Toggles the Special workspace, togglespecialworkspace, special -bindd = $mainMod ALT, 1, Call special workspace scratchpad, togglespecialworkspace, scratchpad -bindd = $mainMod ALT SHIFT, 1, Move active window to special workspace scratchpad, movetoworkspacesilent, special:scratchpad - -# ======= Additional Settings ======= - -# https://wiki.hyprland.org/Configuring/Binds -binds { - allow_workspace_cycles = 1 - workspace_back_and_forth = 1 - workspace_center_on = 1 - movefocus_cycles_fullscreen = true - window_direction_monitor_fallback = true -} - - - -bind = SUPER,X, exec, ~/.config/hypr/scripts/kill-window.sh - -bind = SUPER, KP_End, workspace, 1 -bind = SUPER, KP_Down, workspace, 2 -bind = SUPER, KP_Next, workspace, 3 -bind = SUPER, KP_Left, workspace, 4 -bind = SUPER, KP_Begin, workspace, 5 -bind = SUPER, KP_Right, workspace, 6 -bind = SUPER, KP_Home, workspace, 7 -bind = SUPER, KP_Up, workspace, 8 -bind = SUPER, KP_Prior, workspace, 9 -bind = SUPER, KP_Insert, workspace, 10 -# Move active window to a workspace with numpad + SHIFT + [0-9] -bind = $mainMod SHIFT, KP_End, movetoworkspace, 1 -bind = $mainMod SHIFT, KP_Down, movetoworkspace, 2 -bind = $mainMod SHIFT, KP_Next, movetoworkspace, 3 -bind = $mainMod SHIFT, KP_Left, movetoworkspace, 4 -bind = $mainMod SHIFT, KP_Begin, movetoworkspace, 5 -bind = $mainMod SHIFT, KP_Right, movetoworkspace, 6 -bind = $mainMod SHIFT, KP_Home, movetoworkspace, 7 -bind = $mainMod SHIFT, KP_Up, movetoworkspace, 8 -bind = $mainMod SHIFT, KP_Prior, movetoworkspace, 9 -bind = $mainMod SHIFT, KP_Insert, movetoworkspace, 10 - - - - -bind=,Print,exec,grimblast --freeze copy output - - -bind = SUPER, C, exec, ~/.config/rofi/scripts/rofi-calc.sh -bind = SUPER, S, exec, g4music -bind = SUPER, O, exec, grim -g "$(slurp)" - | tesseract - - -l eng+ara | wl-copy - -bind = ALT, SPACE, exec, nwg-drawer - -# Toggle freeze on active window -#bind = , PAUSE, exec, hyprfreeze -a - -bind = ALT, KP_Insert, exec, hyprfreeze -a - -# SWAYNC -bind = SUPER, N, exec, swaync-client -t - -# Predator Keys -# Mute -bindl=, XF86AudioMute,exec, pactl set-sink-mute @DEFAULT_SINK@ toggle -bindl=, XF86AudioLowerVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ -5% -bindl=, XF86AudioRaiseVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ +5% - -# Toggle_Touchpad -bind = SUPER, F10, exec, ~/.config/hypr/scripts/toggle-touchpad.sh - - -# DRAW -# bind = SUPERALT, G, tooglespecialworkspace, gromit -bind = , F11, exec, gromit-mpx --undo -bind = SHIFT , F11, exec, gromit-mpx --redo -bind = ALT , F11, exec, gromit-mpx --clear diff --git a/airootfs/etc/skel/.config/hypr/config/monitor.conf b/airootfs/etc/skel/.config/hypr/config/monitor.conf deleted file mode 100755 index 187f5e6..0000000 --- a/airootfs/etc/skel/.config/hypr/config/monitor.conf +++ /dev/null @@ -1,20 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Monitor Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# Monitor wiki https://wiki.hyprland.org/0.45.0/Configuring/Monitors/ - -monitor = eDP-2, preferred, 0x0, 1.25 -monitor = HDMI-A-1, preferred, 1920x0, 1 - -#monitor=eDP-2,1920x1080@240.0,0x0,1.0,mirror,eDP-2 -#monitor=HDMI-A-1,1920x1080@74.97,1920x0,1.0,mirror,eDP-2 - - -# If you need to scale things like steam etc, please uncomment these lines. -# Adjust GDK_SCALE accordingly to your liking. -#xwayland { -# force_zero_scaling = true # Unscale XWayland -#} - -#env = GDK_SCALE, 1.25 # GDK Scaling Factor diff --git a/airootfs/etc/skel/.config/hypr/config/user-config.conf b/airootfs/etc/skel/.config/hypr/config/user-config.conf deleted file mode 100755 index 56edba3..0000000 --- a/airootfs/etc/skel/.config/hypr/config/user-config.conf +++ /dev/null @@ -1,14 +0,0 @@ -cursor { - no_hardware_cursors = true -} - -misc { - no_focus_fallback = true - disable_hyprland_urgency = true -} - -misc { - #anr_missed_pings = false - enable_anr_dialog = false -} -misc:vfr = true diff --git a/airootfs/etc/skel/.config/hypr/config/variables.conf b/airootfs/etc/skel/.config/hypr/config/variables.conf deleted file mode 100755 index 75bf704..0000000 --- a/airootfs/etc/skel/.config/hypr/config/variables.conf +++ /dev/null @@ -1,95 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Variables Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -source = ~/.config/hypr/config/colors.conf - -# Variables wiki https://wiki.hyprland.org/0.45.0/Configuring/Variables/ # -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#general -general { - - # ADD THESE FOR PERFORMANCE: - allow_tearing = true # Reduces input lag - resize_on_border = false # Slight performance gain - extend_border_grab_area = 0 # Reduce border calculations - - gaps_in = 2 - gaps_out = 3 - border_size = 1 - col.active_border = $cachylgreen - col.inactive_border = $cachymblue - layout = dwindle # master|dwindle - - # https://wiki.hyprland.org/0.45.0/Configuring/Variables/#snap - snap { - enabled = true - } - -} - -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#gestures -gestures { - workspace_swipe = true - workspace_swipe_fingers = 4 - workspace_swipe_distance = 250 - workspace_swipe_min_speed_to_force = 15 - workspace_swipe_create_new = false -} - -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#group -group { - col.border_active = $cachydgreen - col.border_inactive = $cachylgreen - col.border_locked_active = $cachymgreen - col.border_locked_inactive = $cachydblue - - # https://wiki.hyprland.org/0.45.0/Configuring/Variables/#groupbar - groupbar { - font_family = "Fira Sans" - text_color = $cachydblue - col.active = $cachydgreen - col.inactive = $cachylgreen - col.locked_active = $cachymgreen - col.locked_inactive = $cachydblue - } -} -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#misc -misc { - font_family = "Fira Sans" - splash_font_family = "Fira Sans" - disable_hyprland_logo = true - col.splash = $cachylgreen - background_color = $cachydblue - enable_swallow = true - swallow_regex = ^(firefox|brave|Brave-browser|nautilus|org.kde.dolphin|nemo|thunar|btrfs-assistant.)$ - focus_on_activate = true - vrr = 2 - - # ADD THESE FOR PERFORMANCE: - vfr = true # Variable frame rate - saves power - disable_autoreload = true # Prevent unnecessary reloads - mouse_move_enables_dpms = true # Power saving - key_press_enables_dpms = true # Power saving -} - -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#render -render { - direct_scanout = true - # ADD THESE FOR PERFORMANCE: -# explicit_sync = 2 # Better GPU sync -# explicit_sync_kms = 2 # Better KMS sync -} - -# See https://wiki.hyprland.org/0.45.0/Configuring/Dwindle-Layout/ for more -dwindle { - special_scale_factor = 0.8 - pseudotile = true # master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below - preserve_split = true -} - -# See https://wiki.hyprland.org/0.45.0/Configuring/Master-Layout/ for more -master { - new_status = master - special_scale_factor = 0.8 - #special_scale_factor = 0.8 -} diff --git a/airootfs/etc/skel/.config/hypr/config/windowrules.conf b/airootfs/etc/skel/.config/hypr/config/windowrules.conf deleted file mode 100755 index a943632..0000000 --- a/airootfs/etc/skel/.config/hypr/config/windowrules.conf +++ /dev/null @@ -1,162 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Windowrules Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# Windows Rules https://wiki.hyprland.org/0.45.0/Configuring/Window-Rules/ # - -# Float Necessary Windows -windowrule = float, class:^Rofi$ -windowrulev2 = float, class:^(org.pulseaudio.pavucontrol) -windowrulev2 = float, class:^()$,title:^(Picture in picture)$ -windowrulev2 = float, class:^()$,title:^(Save File)$ -windowrulev2 = float, class:^()$,title:^(Open File)$ -windowrulev2 = float, class:^(LibreWolf)$,title:^(Picture-in-Picture)$ -windowrulev2 = float, class:^(blueman-manager)$ -windowrulev2 = float, class:^(xdg-desktop-portal-gtk|xdg-desktop-portal-kde|xdg-desktop-portal-hyprland)(.*)$ -windowrulev2 = float, class:^(polkit-gnome-authentication-agent-1|hyprpolkitagent|org.org.kde.polkit-kde-authentication-agent-1)(.*)$ -windowrulev2 = float, class:^(CachyOSHello)$ -windowrulev2 = float, class:^(zenity)$ -windowrulev2 = float, class:^()$,title:^(Steam - Self Updater)$ -# Increase the opacity -windowrulev2 = opacity 0.92, class:^(thunar|nemo|dolphin)$ -windowrulev2 = opacity 0.96, class:^(discord|vesktop|armcord|webcord)$ -windowrulev2 = opacity 0.95, title:^(QQ|Telegram)$ -windowrulev2 = opacity 0.95, title:^(NetEase Cloud Music Gtk4)$ -# General window rules -windowrulev2 = float, title:^(Picture-in-Picture)$ -windowrulev2 = size 960 540, title:^(Picture-in-Picture)$ -windowrulev2 = move 25%-, title:^(Picture-in-Picture)$ -windowrulev2 = float, title:^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp)$ -windowrulev2 = move 25%-, title:^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp)$ -windowrulev2 = size 960 540, title:^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp)$ -windowrulev2 = pin, title:^(danmufloat)$ -windowrulev2 = rounding 5, title:^(danmufloat|termfloat)$ -windowrulev2 = animation slide right, class:^(kitty|Alacritty)$ -windowrulev2 = noblur, class:^(org.mozilla.firefox)$ -windowrulev2 = noblur, class:^(Brave-browser)$ -# Decorations related to floating windows on workspaces 1 to 10 -windowrulev2 = bordersize 2, floating:1, onworkspace:w[fv1-10] -#windowrulev2 = bordercolor $cachylblue, floating:1, onworkspace:w[fv1-10] -#windowrulev2 = bordercolor $kararwallpaper, floating:1, onworkspace:w[fv1-10] -windowrulev2 = rounding 8, floating:1, onworkspace:w[fv1-10] -# Decorations related to tiling windows on workspaces 1 to 10 -windowrulev2 = bordersize 3, floating:0, onworkspace:f[1-10] -windowrulev2 = rounding 4, floating:0, onworkspace:f[1-10] -# Windows Rules End # - -# Workspaces Rules https://wiki.hyprland.org/0.45.0/Configuring/Workspace-Rules/ # -# workspace = 1, default:true, monitor:$priMon -# workspace = 6, default:true, monitor:$secMon -# Workspace selectors https://wiki.hyprland.org/0.45.0/Configuring/Workspace-Rules/#workspace-selectors -# workspace = r[1-5], monitor:$priMon -# workspace = r[6-10], monitor:$secMon -# workspace = special:scratchpad, on-created-empty:$applauncher -# no_gaps_when_only deprecated instead workspaces rules with selectors can do the same -# Smart gaps from 0.45.0 https://wiki.hyprland.org/0.45.0/Configuring/Workspace-Rules/#smart-gaps -workspace = w[tv1-10], gapsout:5, gapsin:3 -workspace = f[1], gapsout:5, gapsin:3 -# Workspaces Rules End # - -# Layers Rules # -layerrule = animation slide top, logout_dialog -# layerrule = animation popin 50%, waybar -layerrule = animation slide down, waybar -layerrule = animation fade 50%, wallpaper -# Layers Rules End # - - - - - - -# General Improvements -windowrulev2 = noblur, xwayland:1 -windowrulev2 = noshadow, floating:0 - -# Dialog windows – center + float common file pickers -windowrulev2 = center, title:^(Open File|Select a File|Choose wallpaper|Open Folder|Save As|Library|File Upload)(.*)$ -windowrulev2 = float, title:^(Open File|Select a File|Choose wallpaper|Open Folder|Save As|Library|File Upload)(.*)$ - -# Picture-in-Picture - improved matching and behavior -windowrulev2 = float, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ -windowrulev2 = keepaspectratio, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ -windowrulev2 = move 73% 72%, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ -windowrulev2 = size 25%, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ -windowrulev2 = pin, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ - -# Floating + sizing for settings/connection GUIs -windowrulev2 = float, class:^(nm-connection-editor|pavucontrol|org.pulseaudio.pavucontrol)$ -windowrulev2 = size 45%, class:^(nm-connection-editor|pavucontrol|org.pulseaudio.pavucontrol)$ -windowrulev2 = center, class:^(nm-connection-editor|pavucontrol|org.pulseaudio.pavucontrol)$ -windowrulev2 = float, class:^(steam|blueberry\.py|guifetch)$ - -# Force tiled layout for known apps -windowrulev2 = tile, class:^dev\.warp\.Warp$ - -# Tearing fixes (only if using Wine/Proton games/tools) -windowrulev2 = immediate, title:.*\.exe -windowrulev2 = immediate, class:^(steam_app) - - -windowrulev2 = float,class:gsimplecal -windowrulev2 = size 204 172,class:gsimplecal -windowrulev2 = move 1181 42,class:gsimplecal - - -# KDE FILE PICKER -windowrulev2 = float, class:^(org\.freedesktop\.impl\.portal\.desktop\.kde)$ -windowrulev2 = size 1084 827, class:^(org\.freedesktop\.impl\.portal\.desktop\.kde)$ - -# Fix some dragging issues with XWayland -windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 - -# Flameshot configuration -windowrulev2 = noanim, class:^(flameshot)$ -windowrulev2 = float, class:^(flameshot)$ -windowrulev2 = move 0 0, class:^(flameshot)$ -windowrulev2 = pin, class:^(flameshot)$ -# Set this to your leftmost monitor id -windowrulev2 = monitor 0, class:^(flameshot)$ - -# Keybind with environment variable to fix copy issues -bind = SUPER_ALT, S, exec, XDG_CURRENT_DESKTOP=sway flameshot gui - -# MUSIC GAPLESS -windowrulev2 = float,class:^(com\.github\.neithern\.g4music)$ -windowrulev2 = size 356 573,class:^(com\.github\.neithern\.g4music)$ -windowrulev2 = move 2202 1025,class:^(com\.github\.neithern\.g4music)$ -#windowrulev2 = pin,class:^(com\.github\.neithern\.g4music)$ - -#windowrulev2 = tile, title:^Cisco Packet Tracer$ - -# NO SHARE SCREEN -windowrulev2 = noscreenshare, class:^(org\.telegram\.desktop)$ -windowrulev2 = noscreenshare, class:^(Signal)$ -windowrulev2 = noscreenshare, class:^(org\.keepassxc\.KeePassXC)$ -windowrulev2 = noscreenshare, class:^(com\.obsproject\.Studio)$ -#windowrulev2 = noscreenshare, class:^(org\.kde\.dolphin)$ -windowrulev2 = noscreenshare, class:^(vesktop)$ -windowrulev2 = noscreenshare, title:^(gsr ui)$ -windowrulev2 = noscreenshare, class:^(FreeTube)$ -windowrulev2 = noscreenshare, class:io.ente.auth -windowrulev2 = noscreenshare, class:^(librewolf)$ -#windowrulev2 = noscreenshare, class:^(librewolf)$, title:^(— LibreWolf)$ - -# WAYDROID -windowrulev2 = float, class:^(Waydroid)$ -windowrulev2 = center, class:^(Waydroid)$ -windowrulev2 = size 800 1280, class:^(Waydroid)$ - -# DRAW -workspace = special:gromit, gapsin:0, gapsout:0 -#exec-once = gromit-mpx -a -windowrulev2 = noblur, class:^(Gromit-mpx)$ -windowrulev2 = opacity 1 1, class:^(Gromit-mpx)$ -windowrulev2 = noshadow, class:^(Gromit-mpx)$ -#windowrulev2 = nofullscreenrequest, class:^(Gromit-mpx)$ -windowrulev2 = size 100% 100%, class:^(Gromit-mpx)$ - -# Always float and set size for Ente Auth -windowrulev2 = float, class:io.ente.auth -windowrulev2 = size 441 746, class:io.ente.auth - diff --git a/airootfs/etc/skel/.config/hypr/config/workflows.conf b/airootfs/etc/skel/.config/hypr/config/workflows.conf deleted file mode 100755 index bb2ae65..0000000 --- a/airootfs/etc/skel/.config/hypr/config/workflows.conf +++ /dev/null @@ -1,18 +0,0 @@ -#! █░█░█ █▀█ █▀█ █▄▀ █▀▀ █░░ █▀█ █░█░█ █▀ -#! ▀▄▀▄▀ █▄█ █▀▄ █░█ █▀░ █▄▄ █▄█ ▀▄▀▄▀ ▄█ - -# This file sets the current workflow for Hyprland -# HyDE Controlled content // DO NOT EDIT -# Edit or add workflows in the ./workflows/ directory -# and run the 'workflows.sh select' command to update this file - -# Workflows are a set of configurations that can be applied to Hyprland -# that suits the actual workflow you are doing. -# It can be gaming mode, work mode, or anything else you can think of. -# you can also exec a command within the workflow - -$WORKFLOW = default -$WORKFLOW_ICON =  -$WORKFLOW_DESCRIPTION = Default HyDE configuration // Disables workflow overrides -$WORKFLOWS_PATH = ./workflows/default.conf -source = $WORKFLOWS_PATH diff --git a/airootfs/etc/skel/.config/hypr/hyprland.conf b/airootfs/etc/skel/.config/hypr/hyprland.conf old mode 100755 new mode 100644 index ab361e1..f2fc970 --- a/airootfs/etc/skel/.config/hypr/hyprland.conf +++ b/airootfs/etc/skel/.config/hypr/hyprland.conf @@ -1,38 +1,201 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ CachyOS Hyprland Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +# ┌──────────────────────────────────────────────────────────────────────────┐ +# │ SimulationOS - Hyprland configuration │ +# │ │ +# │ Deliberately small and flat so it can be debugged. Every command │ +# │ referenced here is provided by a package in packages.x86_64: │ +# │ kitty wofi thunar waybar mako hyprpaper hyprpolkitagent │ +# │ grim slurp wl-clipboard cliphist brightnessctl pavucontrol │ +# │ nm-connection-editor network-manager-applet blueman │ +# │ simos-screenshot simos-powermenu simos-clipboard (airootfs/usr/local) │ +# │ │ +# │ Put personal overrides in ~/.config/hypr/monitors.conf (sourced last). │ +# └──────────────────────────────────────────────────────────────────────────┘ $mainMod = SUPER +$terminal = kitty +$launcher = wofi --show drun +$fileManager = thunar -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Source Files ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -source = ~/.config/hypr/config/animations.conf -source = ~/.config/hypr/config/autostart.conf -source = ~/.config/hypr/config/decorations.conf -source = ~/.config/hypr/config/environment.conf -source = ~/.config/hypr/config/input.conf -source = ~/.config/hypr/config/keybinds.conf -source = ~/.config/hypr/config/monitor.conf -source = ~/.config/hypr/monitors.conf -source = ~/.config/hypr/config/variables.conf -source = ~/.config/hypr/config/windowrules.conf +# ──────────────────────────────────────────────────────────────── environment +env = XDG_CURRENT_DESKTOP,Hyprland +env = XDG_SESSION_TYPE,wayland +env = XDG_SESSION_DESKTOP,Hyprland +env = QT_QPA_PLATFORM,wayland;xcb +env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1 +env = QT_AUTO_SCREEN_SCALE_FACTOR,1 +env = GDK_BACKEND,wayland,x11 +env = SDL_VIDEODRIVER,wayland +env = CLUTTER_BACKEND,wayland +env = MOZ_ENABLE_WAYLAND,1 +env = ELECTRON_OZONE_PLATFORM_HINT,auto +# Allows the session to come up on machines and VMs without a real GPU. +env = WLR_RENDERER_ALLOW_SOFTWARE,1 + +# ────────────────────────────────────────────────────────────────── autostart +# Order matters: the portal/dbus environment must be exported before apps that +# rely on it (file pickers, screensharing) are launched. +exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP +exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP + +exec-once = hyprpaper +exec-once = waybar +exec-once = mako +exec-once = systemctl --user start hyprpolkitagent +exec-once = nm-applet --indicator +exec-once = blueman-applet +exec-once = wl-paste --watch cliphist store + +# ─────────────────────────────────────────────────────────────────── monitors +# Default: let Hyprland pick the preferred mode for every output. +monitor = , preferred, auto, 1 + +# ─────────────────────────────────────────────────────────────────────── input +input { + kb_layout = us + follow_mouse = 1 + sensitivity = 0 + touchpad { + natural_scroll = true + tap-to-click = true + disable_while_typing = true + } +} + +gestures { + workspace_swipe = true + workspace_swipe_fingers = 3 +} + +# ───────────────────────────────────────────────────────────────── appearance +general { + gaps_in = 4 + gaps_out = 8 + border_size = 2 + col.active_border = rgba(1793d1ee) rgba(33ccffee) 45deg + col.inactive_border = rgba(2a2a2aaa) + layout = dwindle + resize_on_border = true +} +decoration { + rounding = 8 + blur { + enabled = true + size = 3 + passes = 1 + } + shadow { + enabled = true + range = 12 + render_power = 3 + color = rgba(00000055) + } +} -#source = ~/.config/hypr/hyprlock.conf +animations { + enabled = true + bezier = easeOutQuint, 0.23, 1, 0.32, 1 + animation = windows, 1, 4, easeOutQuint, popin 90% + animation = fade, 1, 4, easeOutQuint + animation = workspaces, 1, 5, easeOutQuint, slide + animation = border, 1, 6, easeOutQuint +} -# Modifying these configs can be done by creating a user defined config in the home directory, e.g. -## ~/.config/hypr/config/user-config.conf -#source ~/.config/hypr/config/user-config.conf +dwindle { + pseudotile = true + preserve_split = true +} -#misc { -# no_focus_fallback = true -# disable_hyprland_urgency = true -#} +misc { + disable_hyprland_logo = true + disable_splash_rendering = true + force_default_wallpaper = 0 + font_family = Fira Sans +} +# Software cursors keep the pointer visible in VMs and on drivers with broken +# hardware cursor planes. SimulationOS is expected to be evaluated in a VM. +cursor { + no_hardware_cursors = true +} -#env = XDG_CURRENT_DESKTOP,KDE -#env = KDE_FULL_SESSION,true -#env = GTK_USE_PORTAL,1 +# ─────────────────────────────────────────────────────────────────── keybinds +bind = $mainMod, Return, exec, $terminal +bind = $mainMod, D, exec, $launcher +bind = $mainMod, E, exec, $fileManager +bind = $mainMod, Q, killactive, +bind = $mainMod SHIFT, E, exit, +bind = $mainMod, V, togglefloating, +bind = $mainMod, P, pseudo, +bind = $mainMod, J, togglesplit, +bind = $mainMod, F, fullscreen, 0 +bind = $mainMod, R, exec, hyprctl reload +bind = $mainMod, X, exec, simos-powermenu +bind = $mainMod, L, exec, hyprlock +bind = $mainMod, C, exec, simos-clipboard +bind = $mainMod SHIFT, N, exec, nm-connection-editor +# Screenshots +bind = , Print, exec, simos-screenshot full +bind = SHIFT, Print, exec, simos-screenshot area +bind = $mainMod, Print, exec, simos-screenshot window +# Focus +bind = $mainMod, left, movefocus, l +bind = $mainMod, right, movefocus, r +bind = $mainMod, up, movefocus, u +bind = $mainMod, down, movefocus, d + +# Move windows +bind = $mainMod SHIFT, left, movewindow, l +bind = $mainMod SHIFT, right, movewindow, r +bind = $mainMod SHIFT, up, movewindow, u +bind = $mainMod SHIFT, down, movewindow, d + +# Workspaces +bind = $mainMod, 1, workspace, 1 +bind = $mainMod, 2, workspace, 2 +bind = $mainMod, 3, workspace, 3 +bind = $mainMod, 4, workspace, 4 +bind = $mainMod, 5, workspace, 5 +bind = $mainMod, 6, workspace, 6 +bind = $mainMod, 7, workspace, 7 +bind = $mainMod, 8, workspace, 8 +bind = $mainMod, 9, workspace, 9 +bind = $mainMod, 0, workspace, 10 +bind = $mainMod SHIFT, 1, movetoworkspace, 1 +bind = $mainMod SHIFT, 2, movetoworkspace, 2 +bind = $mainMod SHIFT, 3, movetoworkspace, 3 +bind = $mainMod SHIFT, 4, movetoworkspace, 4 +bind = $mainMod SHIFT, 5, movetoworkspace, 5 +bind = $mainMod SHIFT, 6, movetoworkspace, 6 +bind = $mainMod SHIFT, 7, movetoworkspace, 7 +bind = $mainMod SHIFT, 8, movetoworkspace, 8 +bind = $mainMod SHIFT, 9, movetoworkspace, 9 +bind = $mainMod SHIFT, 0, movetoworkspace, 10 + +bind = $mainMod, mouse_down, workspace, e+1 +bind = $mainMod, mouse_up, workspace, e-1 +bindm = $mainMod, mouse:272, movewindow +bindm = $mainMod, mouse:273, resizewindow + +# Media / hardware keys +bindel = , XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ +bindel = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- +bindl = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle +bindl = , XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle +bindel = , XF86MonBrightnessUp, exec, brightnessctl set 5%+ +bindel = , XF86MonBrightnessDown, exec, brightnessctl set 5%- + +# ────────────────────────────────────────────────────────────────── windowrules +windowrulev2 = float, class:^(pavucontrol)$ +windowrulev2 = float, class:^(nm-connection-editor)$ +windowrulev2 = float, class:^(blueman-manager)$ +windowrulev2 = float, class:^(org.gnome.FileRoller)$ +windowrulev2 = float, title:^(Install SimulationOS)$ +windowrulev2 = size 1024 680, class:^(calamares)$ +windowrulev2 = center, class:^(calamares)$ +windowrulev2 = suppressevent maximize, class:.* + +# ───────────────────────────────────────────────────────── user overrides last +source = ~/.config/hypr/monitors.conf diff --git a/airootfs/etc/skel/.config/hypr/hyprlock.conf b/airootfs/etc/skel/.config/hypr/hyprlock.conf new file mode 100644 index 0000000..5aed749 --- /dev/null +++ b/airootfs/etc/skel/.config/hypr/hyprlock.conf @@ -0,0 +1,33 @@ +# SimulationOS - hyprlock +background { + monitor = + path = /usr/share/backgrounds/simulationos/simulationos-alpha.png + blur_passes = 2 + blur_size = 6 +} + +input-field { + monitor = + size = 320, 46 + outline_thickness = 2 + dots_center = true + outer_color = rgb(1793d1) + inner_color = rgb(14161a) + font_color = rgb(e6e6e6) + fail_color = rgb(c0392b) + placeholder_text = Password + position = 0, -40 + halign = center + valign = center +} + +label { + monitor = + text = SimulationOS + color = rgba(230, 230, 230, 1.0) + font_size = 30 + font_family = Fira Sans + position = 0, 60 + halign = center + valign = center +} diff --git a/airootfs/etc/skel/.config/hypr/hyprpaper.conf b/airootfs/etc/skel/.config/hypr/hyprpaper.conf old mode 100755 new mode 100644 index cbb27d7..691e130 --- a/airootfs/etc/skel/.config/hypr/hyprpaper.conf +++ b/airootfs/etc/skel/.config/hypr/hyprpaper.conf @@ -1,3 +1,6 @@ -preload = /mnt/m.2_ssd/Linux/Wallpapers/1354205.jpeg - -wallpaper = ,/mnt/m.2_ssd/Linux/Wallpapers/1354205.jpeg +# SimulationOS wallpaper. +# The image is shipped in airootfs/usr/share/backgrounds/simulationos/. +preload = /usr/share/backgrounds/simulationos/simulationos-alpha.png +wallpaper = , /usr/share/backgrounds/simulationos/simulationos-alpha.png +splash = false +ipc = on diff --git a/airootfs/etc/skel/.config/hypr/monitors.conf b/airootfs/etc/skel/.config/hypr/monitors.conf old mode 100755 new mode 100644 index fb5a07a..d8df80e --- a/airootfs/etc/skel/.config/hypr/monitors.conf +++ b/airootfs/etc/skel/.config/hypr/monitors.conf @@ -1,9 +1,11 @@ -# Generated by nwg-displays on 2025-04-09 at 10:17:38. Do not edit manually. - -#monitor=eDP-2,2560x1600.0,0x0,1.0,mirror,eDP-2 -monitor=HDMI-A-1,1920x1080@60.97,1920x0,1.0,mirror,eDP-2 -monitor=eDP-2,2560x1600@165,0x0,1.0 -#monitor=eDP-1,2560x1600@165,0x0,1.0 -monitor=eDP-1,2560x1600@60,0x0,1.0 - - +# SimulationOS - per-machine monitor and override file. +# +# This file is sourced at the END of hyprland.conf, so anything set here wins. +# It is intentionally empty by default: the generic +# monitor = , preferred, auto, 1 +# rule in hyprland.conf already configures every detected output. +# +# Examples: +# monitor = eDP-1, 1920x1080@60, 0x0, 1 +# monitor = HDMI-A-1, preferred, 1920x0, 1 +# monitor = , disable diff --git a/airootfs/etc/skel/.config/hypr/scripts/cava.js b/airootfs/etc/skel/.config/hypr/scripts/cava.js deleted file mode 100755 index 4950fd3..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/cava.js +++ /dev/null @@ -1,92 +0,0 @@ -import Gio from 'gi://Gio'; -import GLib from 'gi://GLib'; - -const confPath = `/tmp/ags/cava.conf`; -async function readCavaAsync(barNumber, bitFormat, callback, onError) { - try { - const conf = -`[general] -framerate = 30 -bars = ${barNumber} -[output] -method = raw -bit_format = ${bitFormat}`; - - Utils.writeFileSync(conf, confPath); - - const cava = Gio.Subprocess.new( - [`cava`, `-p`, confPath], - Gio.SubprocessFlags.STDOUT_PIPE | - Gio.SubprocessFlags.STDERR_PIPE, - ); - - const pipe = cava.get_stdout_pipe(); - if (!pipe) { - onError(Error(`cava stdout pipe is null`)); - return null; - } - - const read8bit = (stream) => { - stream.read_bytes_async(barNumber, GLib.PRIORITY_LOW, null, (stream, res) => { - try { - let data = stream.read_bytes_finish(res).get_data(); - const output = new Float64Array(data).map(d => d / 255); - callback(output); - read8bit(stream); - } catch (e) { - onError(e); - } - }); - }; - const read16bit = (stream) => { - stream.read_bytes_async(barNumber * 2, GLib.PRIORITY_LOW, null, (stream, res) => { - try { - const data = stream.read_bytes_finish(res).get_data(); - const output = new Float64Array(new Uint16Array(data.buffer)).map(d => d / 65535); - callback(output); - read16bit(stream); - } catch (e) { - onError(e); - } - }); - }; - - if (bitFormat === '8bit') { - read8bit(pipe); - } else if (bitFormat === '16bit'){ - read16bit(pipe); - } - - } catch (e) { - logError(e); - return null; - } -} - -export class Cava extends Service { - static { - Service.register(this, {}, { - 'bar-value': ['float[]', 'r'], - }); - } - - #barValues = []; - get bar_value() { - return this.#barValues; - } - - constructor(barNumber, bitFormat) { - super(); - readCavaAsync( - barNumber, bitFormat, (barValues) => { - this.#barValues = barValues; - this.#onChange(); - } - ) - } - - #onChange() { - this.emit('changed'); - this.notify('bar-value'); - } -} diff --git a/airootfs/etc/skel/.config/hypr/scripts/cleanup.sh b/airootfs/etc/skel/.config/hypr/scripts/cleanup.sh deleted file mode 100755 index af4196d..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/cleanup.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# ____ _ -# / ___| | ___ __ _ _ __ _ _ _ __ -# | | | |/ _ \/ _` | '_ \| | | | '_ \ -# | |___| | __/ (_| | | | | |_| | |_) | -# \____|_|\___|\__,_|_| |_|\__,_| .__/ -# |_| -# - -# Remove gamemode flag -if [ -f ~/.cache/gamemode ] ;then - rm ~/.cache/gamemode - echo ":: ~/.cache/gamemode removed" -fi \ No newline at end of file diff --git a/airootfs/etc/skel/.config/hypr/scripts/cpufreq.sh b/airootfs/etc/skel/.config/hypr/scripts/cpufreq.sh deleted file mode 100755 index c447d96..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/cpufreq.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -MODE=$(auto-cpufreq --show | grep 'Current mode:' | awk '{print $3}') -case $1 in - powersave) - sudo auto-cpufreq --force powersave - ;; - performance) - sudo auto-cpufreq --force performance - ;; - reset) - sudo auto-cpufreq --force reset - ;; -esac -echo "$MODE" - diff --git a/airootfs/etc/skel/.config/hypr/scripts/gamemode.sh b/airootfs/etc/skel/.config/hypr/scripts/gamemode.sh deleted file mode 100755 index 09708fa..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/gamemode.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# ____ _ -# / ___| __ _ _ __ ___ ___ _ __ ___ ___ __| | ___ -# | | _ / _` | '_ ` _ \ / _ \ '_ ` _ \ / _ \ / _` |/ _ \ -# | |_| | (_| | | | | | | __/ | | | | | (_) | (_| | __/ -# \____|\__,_|_| |_| |_|\___|_| |_| |_|\___/ \__,_|\___| -# -# - -if [ -f ~/.cache/gamemode ] ;then - hyprctl reload - rm ~/.cache/gamemode - notify-send "Gamemode deactivated" "Animations and blur enabled" -else - hyprctl --batch "\ - keyword animations:enabled 0;\ - keyword decoration:drop_shadow 0;\ - keyword decoration:blur:enabled 0;\ - keyword general:gaps_in 0;\ - keyword general:gaps_out 0;\ - keyword general:border_size 1;\ - keyword decoration:rounding 0" - touch ~/.cache/gamemode - notify-send "Gamemode activated" "Animations and blur disabled" -fi diff --git a/airootfs/etc/skel/.config/hypr/scripts/generate_wallpapers.sh b/airootfs/etc/skel/.config/hypr/scripts/generate_wallpapers.sh deleted file mode 100755 index 056bd11..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/generate_wallpapers.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash - -print_help() { - echo "Usage: wallpaper-changer.sh [-p PATH] [-i INTERVAL] [-h|--help]" - echo - echo "Options:" - echo -e " -i, --interval INTERVAL\tTime interval (in seconds) between wallpaper changes" - echo -e " -p, --path PATH\tpath to load wallpapers from.Default value is ~/Pictures/Wallpapers. Does not recursively load wallpapers" - echo -e " -m, --monitor MONITOR_NAME \t monitor for which the wallpapers must be applied. Use hyprctl monitors to see the list of monitors." - echo " -h, --help Print this help message and exit" -} - -preload_wallpapers() { - wallpaper_dir=$1 - wallpapers=$(find "$wallpaper_dir" -type f) - wallpapers_string="" - OLDIFS=$IFS - IFS=$'\n' - for wallpaper in $wallpapers; do - wallpapers_string+="preload = $wallpaper\n" - done - echo -en "$wallpapers_string" >>/tmp/wallpapers.tmp - touch ~/.config/hypr/hyprpaper.conf.tmp - cat /tmp/wallpapers.tmp ~/.config/hypr/hyprpaper.conf >~/.config/hypr/hyprpaper.conf.tmp && mv ~/.config/hypr/hyprpaper.conf.tmp ~/.config/hypr/hyprpaper.conf - rm /tmp/wallpapers.tmp - IFS=$OLDIFS - -} - -# set the path to the wallpaper directory -wallpaper_dir="/mnt/m.2_ssd/Linux/Wallpapers" - -# parse the command line arguments -interval=300 -while getopts ":hi:p:m:-:" opt; do - case $opt in - -) - case "${OPTARG}" in - interval) - interval=$OPTIND - OPTIND=$((OPTIND + 1)) - ;; - help) - print_help - exit 0 - ;; - path) - wallpaper_dir=$OPTIND - OPTIND=$((OPTIND + 1)) - ;; - esac - ;; - h) - print_help - exit 0 - ;; - i) - interval=$OPTARG - ;; - p) - wallpaper_dir=$OPTARG - ;; - m) - monitors=$OPTARG - ;; - - \?) - echo "Invalid option: -$OPTARG" >&2 - exit 1 - ;; - :) - echo "Option -$OPTARG requires an argument." >&2 - exit 1 - ;; - esac -done -if [ -z "$monitors" ]; then - echo "Error: -m/--monitors option is required" >&2 - exit 1 -fi -preload_wallpapers "$wallpaper_dir" -# get a list of all the wallpapers in the wallpaper directory -readarray -d '' wallpapers < <(find "$wallpaper_dir" -type f -print0) -killall hyprpaper -hyprpaper >/dev/null 2>&1 & -disown - -idx=0 -n=${#wallpapers[@]} -while true; do - # wait for the specified interval - sleep "$interval" - current_wallpaper=${wallpapers[$idx]} - wallpaper_cmd="hyprctl hyprpaper wallpaper $monitors,\"$current_wallpaper\"" - eval "$wallpaper_cmd" - idx=$(((idx + 1) % n)) -done diff --git a/airootfs/etc/skel/.config/hypr/scripts/init-wallpaper-engine.sh b/airootfs/etc/skel/.config/hypr/scripts/init-wallpaper-engine.sh deleted file mode 100755 index 6751156..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/init-wallpaper-engine.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# __ ______ _____ _ -# \ \ / / _ \ | ____|_ __ __ _(_)_ __ ___ -# \ \ /\ / /| |_) | | _| | '_ \ / _` | | '_ \ / _ \ -# \ V V / | __/ | |___| | | | (_| | | | | | __/ -# \_/\_/ |_| |_____|_| |_|\__, |_|_| |_|\___| -# |___/ -# - -wallpaper_engine=$(cat $HOME/.config/ml4w/settings/wallpaper-engine.sh) -if [ "$wallpaper_engine" == "swww" ] ;then - # swww - echo ":: Using swww" - swww init - swww-daemon --format xrgb - sleep 0.5 - ~/.config/hypr/scripts/wallpaper.sh init -elif [ "$wallpaper_engine" == "hyprpaper" ] ;then - # hyprpaper - echo ":: Using hyprpaper" - sleep 0.5 - ~/.config/hypr/scripts/wallpaper.sh init -else - echo ":: Wallpaper Engine disabled" - ~/.config/hypr/scripts/wallpaper.sh init -fi - diff --git a/airootfs/etc/skel/.config/hypr/scripts/internal-mic.sh b/airootfs/etc/skel/.config/hypr/scripts/internal-mic.sh deleted file mode 100755 index a932ec8..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/internal-mic.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -# Define sinks (output devices) -USB_SINK="alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo" -SPEAKER_SINK="alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Speaker__sink" - -# Define laptop mic (input device) -LAPTOP_MIC="alsa_input.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Mic1__source" - -# Function to move all app streams to the current default sink -move_streams_to_default_sink() { - DEFAULT_SINK=$(pactl info | grep "Default Sink" | awk '{print $3}') - pactl list short sink-inputs | while read -r id _; do - pactl move-sink-input "$id" "$DEFAULT_SINK" - done -} - -# Function to set mic volume and unmute -set_laptop_mic() { - pactl set-default-source "$LAPTOP_MIC" - pactl set-source-volume "$LAPTOP_MIC" 60% - pactl set-source-mute "$LAPTOP_MIC" 0 -} - -# Get current default sink -CURRENT_SINK=$(pactl info | grep 'Default Sink' | awk '{print $3}') - -# Toggle output sink and apply laptop mic -# if [[ "$CURRENT_SINK" == "$USB_SINK" ]]; then -# pactl set-default-sink "$SPEAKER_SINK" -# pactl set-sink-mute "$SPEAKER_SINK" 0 -# pactl set-sink-volume "$SPEAKER_SINK" 100% -# echo "Switched to Speaker output" -# else -# pactl set-default-sink "$USB_SINK" -# pactl set-sink-mute "$USB_SINK" 0 -# echo "Switched to USB output" -# fi - -# Apply laptop mic settings -set_laptop_mic -echo "Laptop Microphone set as input with 70% volume" - -# Move all streams to current default sink -move_streams_to_default_sink -echo "All app streams moved to default sink" diff --git a/airootfs/etc/skel/.config/hypr/scripts/kill-window.sh b/airootfs/etc/skel/.config/hypr/scripts/kill-window.sh deleted file mode 100755 index d829142..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/kill-window.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -notify-send -u critical -t 500 "Kill" "Click any window kill it" -hyprctl kill - diff --git a/airootfs/etc/skel/.config/hypr/scripts/light.sh b/airootfs/etc/skel/.config/hypr/scripts/light.sh deleted file mode 100755 index 7287d03..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/light.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -CONTENT=$(curl -s http://ip-api.com/json/) -longitude=$(echo $CONTENT | jq .lon) -latitude=$(echo $CONTENT | jq .lat) -wlsunset -l $latitude -L $longitude - diff --git a/airootfs/etc/skel/.config/hypr/scripts/loadconfig.sh b/airootfs/etc/skel/.config/hypr/scripts/loadconfig.sh deleted file mode 100755 index a51ce6d..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/loadconfig.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -hyprctl reload \ No newline at end of file diff --git a/airootfs/etc/skel/.config/hypr/scripts/manage_wallpaper.sh b/airootfs/etc/skel/.config/hypr/scripts/manage_wallpaper.sh deleted file mode 100755 index bb2e167..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/manage_wallpaper.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -WALLPAPER_DIR="/mnt/m.2_ssd/Linux/Wallpapers" -IMAGE=$(find "$WALLPAPER_DIR" -type f \( -iname "*.jpg" -o -iname "*.png" -o -iname "*.jpeg" \) | shuf -n 1) - -# Supported transitions -TRANSITIONS=("wipe" "grow" "outer" "center" "fade" "simple" "left" "right" "top" "bottom" "wave") -TRANSITION=${TRANSITIONS[$RANDOM % ${#TRANSITIONS[@]}]} - -# Random normalized position (0.0–1.0) -POS_X=$(awk -v seed=$RANDOM 'BEGIN { srand(seed); printf("%.2f", rand()) }') -POS_Y=$(awk -v seed=$RANDOM 'BEGIN { srand(seed); printf("%.2f", rand()) }') - -# Start swww-daemon if not already running -if ! pgrep -x swww-daemon >/dev/null; then - swww-daemon & - sleep 0.5 -fi - -# Set wallpaper with random transition and position -swww img "$IMAGE" --transition-type "$TRANSITION" --transition-pos "$POS_X,$POS_Y" --transition-duration 3 --transition-fps 60 - - diff --git a/airootfs/etc/skel/.config/hypr/scripts/mic-default-volume.sh b/airootfs/etc/skel/.config/hypr/scripts/mic-default-volume.sh deleted file mode 100755 index 40bcbd8..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/mic-default-volume.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -sleep 5 - -wpctl set-volume @DEFAULT_AUDIO_SOURCE@ 0.4 - - -#!/bin/bash - -# Set USB mic as default input -pactl set-default-source alsa_input.usb-GeneralPlus_USB_Audio_Device-00.mono-fallback -#pactl set-source-volume alsa_input.usb-GeneralPlus_USB_Audio_Device-00.mono-fallback 100% -pactl set-source-mute alsa_input.usb-GeneralPlus_USB_Audio_Device-00.mono-fallback 0 - -# Mute all other mics -pactl set-source-mute alsa_input.usb-Sony_Interactive_Entertainment_DualSense_Wireless_Controller-00.analog-stereo 1 -pactl set-source-mute alsa_input.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Mic1__source 1 -pactl set-source-mute alsa_input.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Mic2__source 1 - diff --git a/airootfs/etc/skel/.config/hypr/scripts/power.sh b/airootfs/etc/skel/.config/hypr/scripts/power.sh deleted file mode 100755 index 8d7394e..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/power.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -# ____ -# | _ \ _____ _____ _ __ -# | |_) / _ \ \ /\ / / _ \ '__| -# | __/ (_) \ V V / __/ | -# |_| \___/ \_/\_/ \___|_| -# - -if [[ "$1" == "exit" ]]; then - echo ":: Exit" - if [[ -f "$FILE" ]]; then - rm $FILE - fi - sleep 0.5 - killall -9 Hyprland - sleep 2 -fi - -if [[ "$1" == "lock" ]]; then - echo ":: Lock" - sleep 0.5 - hyprlock -fi - -if [[ "$1" == "reboot" ]]; then - echo ":: Reboot" - if [[ -f "$FILE" ]]; then - rm $FILE - fi - sleep 0.5 - systemctl reboot -fi - -if [[ "$1" == "shutdown" ]]; then - echo ":: Shutdown" - if [[ -f "$FILE" ]]; then - rm $FILE - fi - sleep 0.5 - systemctl poweroff -fi - -if [[ "$1" == "suspend" ]]; then - echo ":: Suspend" - sleep 0.5 - systemctl suspend -fi - -if [[ "$1" == "hibernate" ]]; then - echo ":: Hibernate" - sleep 1 - systemctl hibernate -fi - diff --git a/airootfs/etc/skel/.config/hypr/scripts/screenshot b/airootfs/etc/skel/.config/hypr/scripts/screenshot deleted file mode 100755 index ecc11b9..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/screenshot +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/bash -grim -g "$(slurp)" - | wl-copy -t image/png -notify-send "Screenshot copied to clipboard" diff --git a/airootfs/etc/skel/.config/hypr/scripts/screenshot.sh b/airootfs/etc/skel/.config/hypr/scripts/screenshot.sh deleted file mode 100755 index d08557a..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/screenshot.sh +++ /dev/null @@ -1,224 +0,0 @@ -#!/bin/bash -# ____ _ _ -# / ___| ___ _ __ ___ ___ _ __ ___| |__ ___ | |_ -# \___ \ / __| '__/ _ \/ _ \ '_ \/ __| '_ \ / _ \| __| -# ___) | (__| | | __/ __/ | | \__ \ | | | (_) | |_ -# |____/ \___|_| \___|\___|_| |_|___/_| |_|\___/ \__| -# -# Based on https://github.com/hyprwm/contrib/blob/main/grimblast/screenshot.sh -# ----------------------------------------------------- - -# Screenshots will be stored in $HOME by default. -# The screenshot will be moved into the screenshot directory - -# Add this to ~/.config/user-dirs.dirs to save screenshots in a custom folder: -# XDG_SCREENSHOTS_DIR="$HOME/Screenshots" - -prompt='Screenshot' -mesg="DIR: ~/Screenshots" - -# Screenshot Filename -source ~/.config/ml4w/settings/screenshot-filename.sh - -# Screenshot Folder -source ~/.config/ml4w/settings/screenshot-folder.sh - -# Screenshot Editor -export GRIMBLAST_EDITOR="$(cat ~/.config/ml4w/settings/screenshot-editor.sh)" - -# Example for keybindings -# bind = SUPER, p, exec, grimblast save active -# bind = SUPER SHIFT, p, exec, grimblast save area -# bind = SUPER ALT, p, exec, grimblast save output -# bind = SUPER CTRL, p, exec, grimblast save screen - -# Options -option_1="Immediate" -option_2="Delayed" - -option_capture_1="Capture Everything" -option_capture_2="Capture Active Display" -option_capture_3="Capture Selection" - -option_time_1="5s" -option_time_2="10s" -option_time_3="20s" -option_time_4="30s" -option_time_5="60s" -#option_time_4="Custom (in seconds)" # Roadmap or someone contribute :) - -list_col='1' -list_row='2' - -copy='Copy' -save='Save' -copy_save='Copy & Save' -edit='Edit' - -# Rofi CMD -rofi_cmd() { - rofi -dmenu -replace -config ~/.config/rofi/config-screenshot.rasi -i -no-show-icons -l 2 -width 30 -p "Take screenshot" -} - -# Pass variables to rofi dmenu -run_rofi() { - echo -e "$option_1\n$option_2" | rofi_cmd -} - -#### -# Choose Timer -# CMD -timer_cmd() { - rofi -dmenu -replace -config ~/.config/rofi/config-screenshot.rasi -i -no-show-icons -l 5 -width 30 -p "Choose timer" -} - -# Ask for confirmation -timer_exit() { - echo -e "$option_time_1\n$option_time_2\n$option_time_3\n$option_time_4\n$option_time_5" | timer_cmd -} - -# Confirm and execute -timer_run() { - selected_timer="$(timer_exit)" - if [[ "$selected_timer" == "$option_time_1" ]]; then - countdown=5 - ${1} - elif [[ "$selected_timer" == "$option_time_2" ]]; then - countdown=10 - ${1} - elif [[ "$selected_timer" == "$option_time_3" ]]; then - countdown=20 - ${1} - elif [[ "$selected_timer" == "$option_time_4" ]]; then - countdown=30 - ${1} - elif [[ "$selected_timer" == "$option_time_5" ]]; then - countdown=60 - ${1} - else - exit - fi -} -### - -#### -# Chose Screenshot Type -# CMD -type_screenshot_cmd() { - rofi -dmenu -replace -config ~/.config/rofi/config-screenshot.rasi -i -no-show-icons -l 3 -width 30 -p "Type of screenshot" -} - -# Ask for confirmation -type_screenshot_exit() { - echo -e "$option_capture_1\n$option_capture_2\n$option_capture_3" | type_screenshot_cmd -} - -# Confirm and execute -type_screenshot_run() { - selected_type_screenshot="$(type_screenshot_exit)" - if [[ "$selected_type_screenshot" == "$option_capture_1" ]]; then - option_type_screenshot=screen - ${1} - elif [[ "$selected_type_screenshot" == "$option_capture_2" ]]; then - option_type_screenshot=output - ${1} - elif [[ "$selected_type_screenshot" == "$option_capture_3" ]]; then - option_type_screenshot=area - ${1} - else - exit - fi -} -### - -#### -# Choose to save or copy photo -# CMD -copy_save_editor_cmd() { - rofi -dmenu -replace -config ~/.config/rofi/config-screenshot.rasi -i -no-show-icons -l 4 -width 30 -p "How to save" -} - -# Ask for confirmation -copy_save_editor_exit() { - echo -e "$copy\n$save\n$copy_save\n$edit" | copy_save_editor_cmd -} - -# Confirm and execute -copy_save_editor_run() { - selected_chosen="$(copy_save_editor_exit)" - if [[ "$selected_chosen" == "$copy" ]]; then - option_chosen=copy - ${1} - elif [[ "$selected_chosen" == "$save" ]]; then - option_chosen=save - ${1} - elif [[ "$selected_chosen" == "$copy_save" ]]; then - option_chosen=copysave - ${1} - elif [[ "$selected_chosen" == "$edit" ]]; then - option_chosen=edit - ${1} - else - exit - fi -} -### - -timer() { - if [[ $countdown -gt 10 ]]; then - notify-send -t 1000 "Taking screenshot in ${countdown} seconds" - countdown_less_10=$((countdown - 10)) - sleep $countdown_less_10 - countdown=10 - fi - while [[ $countdown -ne 0 ]]; do - notify-send -t 1000 "Taking screenshot in ${countdown} seconds" - countdown=$((countdown - 1)) - sleep 1 - done -} - -# take shots -takescreenshot() { - sleep 1 - grimblast --notify "$option_chosen" "$option_type_screenshot" $NAME - if [ -f $HOME/$NAME ] ;then - if [ -d $screenshot_folder ] ;then - mv $HOME/$NAME $screenshot_folder/ - fi - fi -} - -takescreenshot_timer() { - sleep 1 - timer - grimblast --notify "$option_chosen" "$option_type_screenshot" $NAME - if [ -f $HOME/$NAME ] ;then - if [ -d $screenshot_folder ] ;then - mv $HOME/$NAME $screenshot_folder/ - fi - fi -} - -# Execute Command -run_cmd() { - if [[ "$1" == '--opt1' ]]; then - type_screenshot_run - copy_save_editor_run "takescreenshot" - elif [[ "$1" == '--opt2' ]]; then - timer_run - type_screenshot_run - copy_save_editor_run "takescreenshot_timer" - fi -} - -# Actions -chosen="$(run_rofi)" -case ${chosen} in -$option_1) - run_cmd --opt1 - ;; -$option_2) - run_cmd --opt2 - ;; -esac diff --git a/airootfs/etc/skel/.config/hypr/scripts/screenshot_area b/airootfs/etc/skel/.config/hypr/scripts/screenshot_area deleted file mode 100755 index 52218e2..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/screenshot_area +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/bash -grimblast copy area || exit -notify-send "Screenshot copied to clipboard" diff --git a/airootfs/etc/skel/.config/hypr/scripts/screenshot_full b/airootfs/etc/skel/.config/hypr/scripts/screenshot_full deleted file mode 100755 index 0608891..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/screenshot_full +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/bash -grimblast copy output || exit -notify-send "Screenshot copied to clipboard" diff --git a/airootfs/etc/skel/.config/hypr/scripts/switch_audio.sh b/airootfs/etc/skel/.config/hypr/scripts/switch_audio.sh deleted file mode 100755 index 74bc8a1..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/switch_audio.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -# Define sinks (output devices) -USB_SINK="alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo" -SPEAKER_SINK="alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Speaker__sink" - -# Define USB mic (input device) -USB_MIC="alsa_input.usb-GeneralPlus_USB_Audio_Device-00.mono-fallback" - -# Function to move all app streams to the current default sink -move_streams_to_default_sink() { - DEFAULT_SINK=$(pactl info | grep "Default Sink" | awk '{print $3}') - pactl list short sink-inputs | while read -r id _; do - pactl move-sink-input "$id" "$DEFAULT_SINK" - done -} - -# Function to set mic volume and unmute -set_usb_mic() { - pactl set-default-source "$USB_MIC" - pactl set-source-volume "$USB_MIC" 70% - pactl set-source-mute "$USB_MIC" 0 -} - -# Get current default sink -CURRENT_SINK=$(pactl info | grep 'Default Sink' | awk '{print $3}') - -# Toggle output sink and apply USB mic -if [[ "$CURRENT_SINK" == "$USB_SINK" ]]; then - pactl set-default-sink "$SPEAKER_SINK" - pactl set-sink-mute "$SPEAKER_SINK" 0 - pactl set-sink-volume "$SPEAKER_SINK" 100% - echo "Switched to Speaker output" -else - pactl set-default-sink "$USB_SINK" - pactl set-sink-mute "$USB_SINK" 0 - echo "Switched to USB output" -fi - -# Apply USB mic settings -set_usb_mic -echo "USB Microphone set as input with 100% volume" - -# Move all streams to current default sink -move_streams_to_default_sink -echo "All app streams moved to default sink" - diff --git a/airootfs/etc/skel/.config/hypr/scripts/toggle-touchpad.sh b/airootfs/etc/skel/.config/hypr/scripts/toggle-touchpad.sh deleted file mode 100755 index 2b5dafb..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/toggle-touchpad.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -# Fast touchpad toggle for Hyprland - streamlined version - -# Find the touchpad device event number -TOUCHPAD_EVENT=$(grep -A 5 -B 5 "SYNA7DB5:00 06CB:CEAF Touchpad" /proc/bus/input/devices | grep -o 'event[0-9]*' | head -n1) - -[ -z "$TOUCHPAD_EVENT" ] && { notify-send -t 1000 "Error: Touchpad not found"; exit 1; } - -# Path to the device inhibit file -INHIBIT_FILE="/sys/class/input/$TOUCHPAD_EVENT/device/inhibited" - -# Check current state and toggle (0=enabled, 1=disabled) -CURRENT_STATE=$(cat "$INHIBIT_FILE" 2>/dev/null || echo "0") - -if [ "$CURRENT_STATE" = "0" ]; then - if echo 1 | tee "$INHIBIT_FILE" >/dev/null 2>&1; then - notify-send -t 1000 "Touchpad Disabled" - else - notify-send -t 1000 "Failed to disable" - fi -else - if echo 0 | tee "$INHIBIT_FILE" >/dev/null 2>&1; then - notify-send -t 1000 "Touchpad Enabled" - else - notify-send -t 1000 "Failed to enable" - fi -fi diff --git a/airootfs/etc/skel/.config/hypr/scripts/toggle_display.sh b/airootfs/etc/skel/.config/hypr/scripts/toggle_display.sh deleted file mode 100755 index e40cb77..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/toggle_display.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -STATUS=$(hyprctl monitors -j | jq -r '.[] | select(.name=="eDP-2") | .dpmsStatus') - -if [[ "$STATUS" == "true" ]]; then - hyprctl dispatch dpms off eDP-2 -else - hyprctl dispatch dpms on eDP-2 -fi - diff --git a/airootfs/etc/skel/.config/hypr/scripts/toggle_mic.sh b/airootfs/etc/skel/.config/hypr/scripts/toggle_mic.sh deleted file mode 100755 index 9d1a1ad..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/toggle_mic.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# Get only microphone sources (exclude monitors and virtual sources) -SOURCES=$(pamixer --list-sources | grep -v "monitor" | grep -v "virtual" | awk -F: '{print $1}') - -# Toggle mute for microphone sources only -for SRC in $SOURCES; do - pamixer --source "$SRC" --toggle-mute -done - -# Check and report the state of the default source -MIC_STATE=$(pamixer --default-source --get-mute) -if [ "$MIC_STATE" = "true" ]; then - notify-send -u low -i microphone-sensitivity-high -t 1000 "Microphone" "Muted" -else - notify-send -u low -i microphone-sensitivity-high -t 1000 "Microphone" "Unmuted" -fi diff --git a/airootfs/etc/skel/.config/hypr/scripts/wallpaper-cache.sh b/airootfs/etc/skel/.config/hypr/scripts/wallpaper-cache.sh deleted file mode 100755 index 899a06a..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/wallpaper-cache.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -generated_versions="$HOME/.config/ml4w/cache/wallpaper-generated" -rm $generated_versions/* -echo ":: Wallpaper cache cleared" -notify-send "Wallpaper cache cleared" \ No newline at end of file diff --git a/airootfs/etc/skel/.config/hypr/scripts/wallpaper-restore.sh b/airootfs/etc/skel/.config/hypr/scripts/wallpaper-restore.sh deleted file mode 100755 index 9c6cd6e..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/wallpaper-restore.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# _ _ -# __ ____ _| | |_ __ __ _ _ __ ___ _ __ -# \ \ /\ / / _` | | | '_ \ / _` | '_ \ / _ \ '__| -# \ V V / (_| | | | |_) | (_| | |_) | __/ | -# \_/\_/ \__,_|_|_| .__/ \__,_| .__/ \___|_| -# |_| |_| -# -# ----------------------------------------------------- -# Restore last wallpaper -# ----------------------------------------------------- - -# ----------------------------------------------------- -# Set defaults -# ----------------------------------------------------- - -defaultwallpaper="$HOME/wallpaper/default.jpg" -cachefile="$HOME/.config/ml4w/cache/current_wallpaper" - -# ----------------------------------------------------- -# Get current wallpaper -# ----------------------------------------------------- - -if [ -f $cachefile ] ;then - if [ -f $(cat $cachefile) ]; then - echo ":: Wallpaper $(cat $cachefile) exists" - wallpaper=$(cat $cachefile) - else - echo ":: Wallpaper $(cat $cachefile) not exists." - wallpaper=$defaultwallpaper - fi -else - echo ":: $cachefile not exists." - wallpaper=$defaultwallpaper -fi - -# ----------------------------------------------------- -# Set wallpaper -# ----------------------------------------------------- - -echo ":: Setting wallpaper with source image $wallpaper" -waypaper --wallpaper $wallpaper \ No newline at end of file diff --git a/airootfs/etc/skel/.config/hypr/scripts/wallpaper.sh b/airootfs/etc/skel/.config/hypr/scripts/wallpaper.sh deleted file mode 100755 index dfb453f..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/wallpaper.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# _ _ -# __ ____ _| | |_ __ __ _ _ __ ___ _ __ -# \ \ /\ / / _` | | | '_ \ / _` | '_ \ / _ \ '__| -# \ V V / (_| | | | |_) | (_| | |_) | __/ | -# \_/\_/ \__,_|_|_| .__/ \__,_| .__/ \___|_| -# |_| |_| -# -# ----------------------------------------------------- -# Execute pywal -# ----------------------------------------------------- - -echo ":: Execute pywal with $used_wallpaper" -wal -i ~/wallpapers/cachyos-wallpapers/ $used_wallpaper -q --cols16 - -# ----------------------------------------------------- -# Reload Waybar -# ----------------------------------------------------- - -$HOME/.config/waybar/launch.sh - -# ----------------------------------------------------- -# Pywalfox -# ----------------------------------------------------- - -if type pywalfox >/dev/null 2>&1; then - pywalfox update -fi diff --git a/airootfs/etc/skel/.config/hypr/scripts/wofi-clipbaord.sh b/airootfs/etc/skel/.config/hypr/scripts/wofi-clipbaord.sh deleted file mode 100755 index 0a6fed5..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/wofi-clipbaord.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -entries=$(cliphist list) - -selection=$(echo "$entries" | wofi --dmenu --prompt "Clipboard") - -if [[ -z "$selection" ]]; then - exit 0 -fi - -# Use cliphist to decode and copy -echo "$selection" | cliphist decode | wl-copy - diff --git a/airootfs/etc/skel/.config/hypr/scripts/xdg.sh b/airootfs/etc/skel/.config/hypr/scripts/xdg.sh deleted file mode 100755 index 6177b64..0000000 --- a/airootfs/etc/skel/.config/hypr/scripts/xdg.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -# __ ______ ____ -# \ \/ / _ \ / ___| -# \ /| | | | | _ -# / \| |_| | |_| | -# /_/\_\____/ \____| -# - -sleep 1 - -# kill all possible running xdg-desktop-portals -killall -e xdg-desktop-portal-hyprland -killall -e xdg-desktop-portal-gnome -killall -e xdg-desktop-portal-kde -killall -e xdg-desktop-portal-lxqt -killall -e xdg-desktop-portal-wlr -killall -e xdg-desktop-portal-gtk -killall -e xdg-desktop-portal -sleep 1 - -# start xdg-desktop-portal-hyprland -/usr/lib/xdg-desktop-portal-hyprland & -sleep 2 - -# start xdg-desktop-portal-gtk -if [ -f /usr/lib/xdg-desktop-portal-gtk ] ;then - /usr/lib/xdg-desktop-portal-gtk & - sleep 1 -fi - -# start xdg-desktop-portal -/usr/lib/xdg-desktop-portal & -sleep 1 \ No newline at end of file diff --git a/airootfs/etc/skel/.config/hypr/wallpapers.conf b/airootfs/etc/skel/.config/hypr/wallpapers.conf deleted file mode 100755 index 67d8576..0000000 --- a/airootfs/etc/skel/.config/hypr/wallpapers.conf +++ /dev/null @@ -1,100 +0,0 @@ -# Autogenerated by generate_wallpapers.sh -preload = /mnt/m.2_ssd/Linux/Wallpapers/1374345.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/1606897678677.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/1645962188236.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/185172182e113-screenshotUrl.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/185174785b28-screenshotUrl.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/185b3478b6571-screenshotUrl.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/1860b6f425825-screenshotUrl.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/1860b70503b7-screenshotUrl.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/1860c68af7a47-screenshotUrl.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/18a6c3dbcf085-screenshotUrl.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/18b22b9700271-screenshotUrl.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/20240607_184614.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/4.49a9d9bc4812520ba537c6c4f83de8b8.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/508286.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/5f3kzz9ga1y31.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/641193.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/72270.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/850.Mixed Wallpapers (207).jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/976234.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/9k48ji18et011.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/ae4xtKY.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/b4gl7p2j83u61.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/Bloodborne™_20220111055336.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/bored-at-home-try-some-windows-vhd256fzppknmaz8.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/cat-meme-background-f9i99husnvjkk492.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/CN62QXJ.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/ComputerDesktopWallpapersCollection1691__088.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/humor-wallpaper-preview.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/ii8669b9lz001.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/impoppy-20220404-0001.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/jx0cehys3yr31.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/kbrpdbfy1rmy.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/kxblq0jrdkyz.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/l7hn0bm9nom71.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/Linx.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/n1ci0trfgrey.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/penguin.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/Remini20220222180405870.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/The-Witcher-The-Witcher-3-Wild-Hunt-video-games-RPG-Video-Game-Art-1658261-wallhere.com.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/undefined - Imgur.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/wallpaperflare.com_wallpaper (1).jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/wallpaperflare.com_wallpaper (2).jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/wallpaperflare.com_wallpaper (3).jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/wallpaperflare.com_wallpaper (4).jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/wallpaperflare.com_wallpaper.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/wallpaperflare.com_wsallpaper.jpg -preload = /mnt/m.2_ssd/Linux/Wallpapers/1346531.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/1354205.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/4ccEiRx.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/9heDmcF.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/a16v87sw8rge1.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/GLnEJQm.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/hMWc6jB.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/Life+Goes+On.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/Melon+Dog.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/rXsN7UY.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/SU15wjN.jpeg -preload = /mnt/m.2_ssd/Linux/Wallpapers/1330715.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/1332803.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/1338180.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/1341419.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/1351005.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/2024-08-31_14-24.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/727088.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/be3xtefq3z3y.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/BlueFeathers.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Cachy depths 5K.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/DarkStreaks.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Dead Space (Remake) Screenshot 2024.09.10 - 01.00.19.63.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Dimensions.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Dracula.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/e8ssljkntvmy.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/eiznhxxoftde1.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Elden Ring Screenshot 2024.08.02 - 21.37.44.84.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Elden Ring Screenshot 2024.08.03 - 17.31.53.58.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Elden Ring Screenshot 2024.08.03 - 17.32.02.21.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Elden Ring Screenshot 2024.08.03 - 20.08.08.03.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Elden Ring Screenshot 2024.08.04 - 13.37.11.38.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Elden Ring Screenshot 2024.08.04 - 21.20.40.48.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Elden Ring Screenshot 2024.08.04 - 21.45.18.80.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Elden Ring Screenshot 2024.08.04 - 21.46.01.47.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Elden Ring Screenshot 2024.08.07 - 02.01.07.94.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/fqq781ici4ie1.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/p3uhyl5z9ub01.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Risk of Rain.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/s79v0pv9c1y01.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/Untitled design.png -preload = /mnt/m.2_ssd/Linux/Wallpapers/3840-x-2160-doom-the-dark-ages-v0-x2ppoyuknlse1.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/3840x2160-landscapes-volume-1-v0-c9ndhnr6b0te1.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/frozen-warthog-by-jeremy-cook-halo-mcc-3840x2160-v0-epkax6k90whe1.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/iv9elw3twuz61.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/landscape-illustrations-3840x2160-v0-6u9wxr8ecqae1.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/minimalistic-wallpapers-3840x2160-v0-qaj9e6lgtjfe1.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/rrml8yxhl7q61.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/some-cool-wallpapers-v0-9gay3iib47da1.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/some-cool-wallpapers-v0-zpf6isra47da1.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/world-of-cybernetics-3840x2160-v0-f28kvbmt7lde1.webp -preload = /mnt/m.2_ssd/Linux/Wallpapers/world-of-cybernetics-3840x2160-v0-yts28kew7lde1.webp -wallpaper = diff --git a/airootfs/etc/skel/.config/hypr/workspaces.conf b/airootfs/etc/skel/.config/hypr/workspaces.conf deleted file mode 100755 index e69de29..0000000 diff --git a/airootfs/etc/skel/.config/kitty/kitty.conf b/airootfs/etc/skel/.config/kitty/kitty.conf new file mode 100644 index 0000000..f8a4dae --- /dev/null +++ b/airootfs/etc/skel/.config/kitty/kitty.conf @@ -0,0 +1,9 @@ +# SimulationOS - kitty +font_family JetBrainsMono Nerd Font +font_size 11.0 +background_opacity 0.92 +window_padding_width 6 +scrollback_lines 10000 +enable_audio_bell no +confirm_os_window_close 0 +cursor_shape beam diff --git a/airootfs/etc/skel/.config/mako/config b/airootfs/etc/skel/.config/mako/config new file mode 100644 index 0000000..9e3aca7 --- /dev/null +++ b/airootfs/etc/skel/.config/mako/config @@ -0,0 +1,16 @@ +# SimulationOS - mako notification daemon +font=Fira Sans 10 +background-color=#14161ae6 +text-color=#e6e6e6 +border-color=#1793d1 +border-size=2 +border-radius=8 +padding=10 +margin=8 +default-timeout=6000 +anchor=top-right +width=360 + +[urgency=critical] +border-color=#c0392b +default-timeout=0 diff --git a/airootfs/etc/skel/.config/waybar/config b/airootfs/etc/skel/.config/waybar/config deleted file mode 100755 index 4a581af..0000000 --- a/airootfs/etc/skel/.config/waybar/config +++ /dev/null @@ -1,369 +0,0 @@ -{ - // ------------------------------------------------------------------------- - // Global configuration with hover groups - // ------------------------------------------------------------------------- - - "layer": "top", - "position": "top", - "margin-left": 10, - "margin-bottom": 0, - "margin-right": 10, - "spacing": 5, - - "modules-left": [ - "hyprland/workspaces", -// "hyprland/window", - "custom/spotify", - "custom/cava" - ], - "modules-center": [ - "group/clock", - ], - "modules-right": [ - "group/hardware", - "group/system", - "group/power", - //"custom/electricity", - "pulseaudio", - "network", - "tray" - ], - - // ------------------------------------------------------------------------- - // Groups configuration for hover functionality - // ------------------------------------------------------------------------- - - - "group/clock":{ - - "orientation": "horizontal", - "drawer": { - "transition-duration": 300, - "children-class": "clock-weather-child", - "transition-left-to-right": false - }, - "modules": [ - "clock", - //"custom/weather", - "temperature", - "clock#2" - ] - }, - - "group/power": { - "orientation": "horizontal", - "drawer": { - "transition-duration": 300, - "children-class": "power-child", - "transition-left-to-right": false - }, - "modules": [ - "battery", - "custom/network_traffic", - "custom/electricity" - ] -}, - - "group/network": { - "orientation": "horizontal", - "drawer": { - "transition-duration": 300, - "children-class": "power-child", - "transition-left-to-right": false - }, - "modules": [ - ] -}, - - "group/hardware": { - "orientation": "horizontal", - "drawer": { - "transition-duration": 300, - "children-class": "hardware-child", - "transition-left-to-right": false - }, - "modules": [ - "custom/cpufreq", - "custom/cpu-temp", - "custom/gpu-temp", - "custom/gamemode" - ] - }, - - "group/system": { - "orientation": "horizontal", - "drawer": { - "transition-duration": 300, - "children-class": "system-child", - "transition-left-to-right": false - }, - "modules": [ - "memory", - "custom/network_traffic", - "custom/recorder", - "cpu", - "custom/storage" - ] - }, - - // ------------------------------------------------------------------------- - // Individual module configurations - // ------------------------------------------------------------------------- - "hyprland/window": { - "format": "{}", - "icon":true, - "icon-size" : 20 - }, - "custom/network_traffic": { - "exec": "~/.config/waybar/modules/network_traffic.sh", - "return-type": "json", - "format-ethernet": "{icon} {ifname} ⇣{bandwidthDownBytes} ⇡{bandwidthUpBytes}", // optional -}, - "custom/cava": { - "format": "{}", - "exec": "~/.config/waybar/modules/cava.sh" - }, - - "custom/gamemode": { - "format": "{}", - "return-type": "", - "interval": 2, - "exec": "~/.config/waybar/modules/gamemode.sh status 2>/dev/null", - "on-click": "~/.config/waybar/modules/gamemode.sh toggle 2>/dev/null", - "signal": 8, - "max-length": 10, - "escape": true - }, - - "custom/cpu-temp": { - "exec": "~/.config/waybar/modules/cpu-temp.sh", - "return-type": "json", - "interval": 5, - "format": "{}", - "on-click": "sensors", - "on-click-right": "kitty -e watch -n 1 sensors" - }, - - "custom/gpu-temp": { - "exec": "~/.config/waybar/modules/gpu-temp.sh", - "return-type": "json", - "interval": 5, - "format": "{}", - "on-click": "nvidia-smi", - "on-click-right": "kitty -e watch -n 1 nvidia-smi" - }, - - "memory": { - "interval": 30, - "format": " {used:0.2f} / {total:0.0f} GB", - "on-click": "kitty -e btop" - }, - - "cpu": { - "interval": 1, - "format": " {max_frequency}GHz | {usage:2}%", - "format-alt": " {usage:2}%", - "max-length": 13, - "min-length": 13, - "tooltip": true, - "tooltip-format": "CPU Usage: {usage}%\nFrequency: {max_frequency}GHz\nLoad: {load}" - }, - - "custom/storage": { - "format": " {}", - "format-alt": "{percentage}% ", - "format-alt-click": "click-right", - "return-type": "json", - "interval": 60, - "exec": "~/.config/waybar/modules/storage.sh" - }, - - "disk": { - "interval": 30, - "format": " {percentage_used}%", - "path": "/" - }, - - "custom/cpufreq": { - "exec": "~/.config/waybar/modules/cpufreq.sh", - "interval": 10, - "format": "{}", - "on-click": "~/.config/waybar/modules/cpufreq.sh toggle", - "on-click-right": "~/.config/waybar/modules/refresh-rate.sh toggle", - "on-click-middle": "~/.config/waybar/modules/cpufreq.sh reset", - "tooltip": true, - "tooltip-format": "CPU Governor\nLeft-click: Change Current Mode ⚡\nRight-click: Change Refresh Rate 🔋\nMiddle-click: Reset" - }, - - "battery": { - "interval": 2, - "states": { - "good": 95, - "warning": 30, - "critical": 15 - }, - "format": "{icon} {capacity}%", - "format-charging": " {capacity}%", - "format-plugged": " {capacity}%", - "format-icons": ["", "", "", "", "" ], - }, - - "custom/electricity": { - "interval": 2, - "exec": "~/.config/waybar/modules/power_monitor.sh", - "format": "⚡ {}", - "tooltip": true, - "on-click": "kitty --class 'Power Details' \"$(~/.config/waybar/modules/power_details.sh)\"", - "on-click-right": "kitty --class 'System Power' \"$(~/.config/waybar/modules/system_power.sh)\"", - "return-type": "json" - }, - - "network": { - "format-wifi": " {essid} ({signalStrength}%)", - "format-ethernet": "󰲝 ", - "format-linked": "{ifname} (No IP) ", - "format": "", - "format-disconnected": "󰲜", - "format-alt": "{ifname}: {ipaddr}/{cidr} ", - "on-click": "wl-copy $(ip address show up scope global | grep inet | head -n1 | cut -d/ -f 1 | tr -d [:space:] | cut -c5-)", - "on-click-right": "wl-copy $(ip address show up scope global | grep inet6 | head -n1 | cut -d/ -f 1 | tr -d [:space:] | cut -c6-)", - "tooltip-format": " {bandwidthUpBits}  {bandwidthDownBits}\n{ifname} \n{ipaddr}/{cidr}\n", - "tooltip-format-wifi": " {essid} {frequency}MHz\nStrength: {signaldBm}dBm ({signalStrength}%)\nIP: {ipaddr}/{cidr}\n {bandwidthUpBits}  {bandwidthDownBits}", - "interval": 10 - }, - - "custom/screenshot_t": { - "format": " ", - "on-click": "~/.config/hypr/scripts/manage_wallpaper.sh", - "on-click-right": "~/.config/hypr/scripts/screenshot_area" - }, - - "pulseaudio": { - "scroll-step": 3, // %, can be a float - "format": "{icon} {volume}% {format_source}", - "format-bluetooth": "{volume}% {icon} {format_source}", - "format-bluetooth-muted": " {icon} {format_source}", - "format-muted": " {format_source}", - //"format-source": "{volume}% ", - //"format-source-muted": "", - "format-source": "", - "format-source-muted": "", - "format-icons": { - "headphone": "", - "hands-free": "", - "headset": "", - "phone": "", - "portable": "", - "car": "", - "default": ["", "", ""] - }, - "on-click": "pavucontrol", - "on-click-right": "amixer sset Master toggle" - }, - - "custom/spotify": { - "exec": "~/.config/waybar/mediaplayer.py", - "format": "{} 🎜", - "return-type": "json", - "on-click": "playerctl play-pause", - "on-scroll-up": "playerctl next", - "on-scroll-down": "playerctl previous" - }, - - "clock#date": { - "format": "󰥔 {:%I:%M \n %e %b}", - "tooltip-format": "{:%Y %B}\n{calendar}", - "today-format": "{}", - "on-click": "~/.config/hypr/scripts/manage_wallpaper.sh", - "on-click-right": "~/.config/hypr/scripts/screenshot_area" - }, - - "clock#1": { - "format": " {:%a}", - "tooltip": false, - "on-click": "gsimplecal" - }, - - "clock#2": { - "format": " {:%Y-%m-%d}", - "tooltip-format": "{:%Y %B %A}", - "tooltip": true, - // "on-click": "~/.scripts/Acer/PredatorSense/rgbkb/rgb_random.py", - "on-click": "swaync-client -t", - "on-click-middle": "~/.scripts/Acer/PredatorSense/rgbkb/rgbkb.service.sh", - }, - - "clock": { - "format": " {:%I:%M %p}", - "tooltip": false, - "on-click-middle": "gsimplecal", - "on-click-right": "~/.config/hypr/scripts/screenshot_area", - "on-click": "~/.config/hypr/scripts/manage_wallpaper.sh" - - }, - - - "custom/recorder": { - "exec": "~/.config/waybar/modules/record-status.sh", - "interval": 1, - "on-click": "~/.config/waybar/modules/toggle-record.sh", - "return-type": "json" - } - }, - - - "temperature": { - // "thermal-zone": 1, - "interval": 4, - //"hwmon-path": "/sys/class/hwmon/hwmon3/temp1_input", - "critical-threshold": 60, - // "format-critical": " {temperatureC}°C", - "format-critical": " {temperatureC}°C", - "format": "{icon} {temperatureC}°C", - "format-icons": ["", "", ""], - "max-length": 7, - "min-length": 7, - "on-click": "~/.scripts/Acer/PredatorSense/rgbkb/rgb_random.py", - "on-click-middle": "~/.scripts/Acer/PredatorSense/rgbkb/rgbkb.service.sh", - }, - - - - - - - - - - - - "custom/weather": { - "format": "{}", - "format-alt": "{alt}: {}", - "format-alt-click": "click-right", - "interval": 3600, - "exec": "curl -s 'https://wttr.in/Baghdad?format=1&m'", - "exec-if": "ping wttr.in -c1", - "on-click": "~/.scripts/Acer/PredatorSense/rgbkb/rgb_random.py", - "on-click-middle": "~/.scripts/Acer/PredatorSense/rgbkb/rgbkb.service.sh", - "refresh-on-click": true - }, - - "hyprland/workspaces": { - "all-outputs": true, - "format": "{name}", - "format-icons": { - "1": "一", "2": "二", "3": "三", "4": "四", "5": "五", - "6": "六", "7": "七", "8": "八", "9": "九", "10": "十" - }, - "on-scroll-up": "hyprctl dispatch workspace e+1 1>/dev/null", - "on-scroll-down": "hyprctl dispatch workspace e-1 1>/dev/null", - "sort-by-number": true, - "active-only": false - }, - - "tray": { - "icon-size": 15, - "spacing": 5 - } -} diff --git a/airootfs/etc/skel/.config/waybar/config.jsonc b/airootfs/etc/skel/.config/waybar/config.jsonc new file mode 100644 index 0000000..a997daa --- /dev/null +++ b/airootfs/etc/skel/.config/waybar/config.jsonc @@ -0,0 +1,86 @@ +// SimulationOS - Waybar +// +// Built-in modules only, plus one custom module that calls simos-powermenu +// (shipped in airootfs/usr/local/bin). Nothing here depends on an external +// helper script, so a missing script can never blank the bar. +{ + "layer": "top", + "position": "top", + "height": 34, + "spacing": 6, + + "modules-left": ["hyprland/workspaces", "hyprland/submap"], + "modules-center": ["hyprland/window"], + "modules-right": ["tray", "pulseaudio", "network", "cpu", "memory", "temperature", "battery", "clock", "custom/power"], + + "hyprland/workspaces": { + "format": "{id}", + "on-click": "activate", + "all-outputs": true + }, + + "hyprland/window": { + "format": "{title}", + "max-length": 70, + "separate-outputs": true + }, + + "tray": { + "icon-size": 16, + "spacing": 8 + }, + + "pulseaudio": { + "format": "{icon} {volume}%", + "format-muted": "󰝟 muted", + "format-icons": { "default": ["󰕿", "󰖀", "󰕾"] }, + "on-click": "pavucontrol", + "scroll-step": 5 + }, + + "network": { + "format-wifi": "󰖩 {essid}", + "format-ethernet": "󰈀 {ipaddr}", + "format-linked": "󰈀 no IP", + "format-disconnected": "󰖪 offline", + "tooltip-format": "{ifname} via {gwaddr}", + "on-click": "nm-connection-editor" + }, + + "cpu": { + "format": "󰻠 {usage}%", + "interval": 5 + }, + + "memory": { + "format": "󰍛 {percentage}%", + "interval": 5 + }, + + "temperature": { + "critical-threshold": 82, + "format": "{icon} {temperatureC}°C", + "format-icons": ["", "", ""] + }, + + "battery": { + "states": { "warning": 25, "critical": 10 }, + "format": "{icon} {capacity}%", + "format-charging": "󰂄 {capacity}%", + "format-plugged": "󰚥 {capacity}%", + "format-icons": ["󰁺", "󰁼", "󰁾", "󰂀", "󰂂"], + "tooltip-format": "{timeTo}" + }, + + "clock": { + "format": "󰥔 {:%H:%M}", + "format-alt": "󰃭 {:%a %d %b %Y}", + "tooltip-format": "{calendar}" + }, + + "custom/power": { + "format": "󰐥", + "tooltip": false, + "on-click": "simos-powermenu" + } +} diff --git a/airootfs/etc/skel/.config/waybar/mediaplayer.py b/airootfs/etc/skel/.config/waybar/mediaplayer.py deleted file mode 100755 index 7404f48..0000000 --- a/airootfs/etc/skel/.config/waybar/mediaplayer.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import logging -import sys -import signal -import gi -import json -gi.require_version('Playerctl', '2.0') -from gi.repository import Playerctl, GLib - -logger = logging.getLogger(__name__) - - -def write_output(text, player): - logger.info('Writing output') - - output = {'text': text, - 'class': 'custom-' + player.props.player_name, - 'alt': player.props.player_name} - - sys.stdout.write(json.dumps(output) + '\n') - sys.stdout.flush() - - -def on_play(player, status, manager): - logger.info('Received new playback status') - on_metadata(player, player.props.metadata, manager) - - -def on_metadata(player, metadata, manager): - logger.info('Received new metadata') - track_info = '' - - if player.props.player_name == 'spotify' and \ - 'mpris:trackid' in metadata.keys() and \ - ':ad:' in player.props.metadata['mpris:trackid']: - track_info = 'AD PLAYING' - elif player.get_artist() != '' and player.get_title() != '': - track_info = '{artist} - {title}'.format(artist=player.get_artist(), - title=player.get_title()) - else: - track_info = player.get_title() - - if player.props.status != 'Playing' and track_info: - track_info = ' ' + track_info - write_output(track_info, player) - - -def on_player_appeared(manager, player, selected_player=None): - if player is not None and (selected_player is None or player.name == selected_player): - init_player(manager, player) - else: - logger.debug("New player appeared, but it's not the selected player, skipping") - - -def on_player_vanished(manager, player): - logger.info('Player has vanished') - sys.stdout.write('\n') - sys.stdout.flush() - - -def init_player(manager, name): - logger.debug('Initialize player: {player}'.format(player=name.name)) - player = Playerctl.Player.new_from_name(name) - player.connect('playback-status', on_play, manager) - player.connect('metadata', on_metadata, manager) - manager.manage_player(player) - on_metadata(player, player.props.metadata, manager) - - -def signal_handler(sig, frame): - logger.debug('Received signal to stop, exiting') - sys.stdout.write('\n') - sys.stdout.flush() - # loop.quit() - sys.exit(0) - - -def parse_arguments(): - parser = argparse.ArgumentParser() - - # Increase verbosity with every occurance of -v - parser.add_argument('-v', '--verbose', action='count', default=0) - - # Define for which player we're listening - parser.add_argument('--player') - - return parser.parse_args() - - -def main(): - arguments = parse_arguments() - - # Initialize logging - logging.basicConfig(stream=sys.stderr, level=logging.DEBUG, - format='%(name)s %(levelname)s %(message)s') - - # Logging is set by default to WARN and higher. - # With every occurrence of -v it's lowered by one - logger.setLevel(max((3 - arguments.verbose) * 10, 0)) - - # Log the sent command line arguments - logger.debug('Arguments received {}'.format(vars(arguments))) - - manager = Playerctl.PlayerManager() - loop = GLib.MainLoop() - - manager.connect('name-appeared', lambda *args: on_player_appeared(*args, arguments.player)) - manager.connect('player-vanished', on_player_vanished) - - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - - for player in manager.props.player_names: - if arguments.player is not None and arguments.player != player.name: - logger.debug('{player} is not the filtered player, skipping it' - .format(player=player.name) - ) - continue - - init_player(manager, player) - - loop.run() - - -if __name__ == '__main__': - main() diff --git a/airootfs/etc/skel/.config/waybar/modules/cava.sh b/airootfs/etc/skel/.config/waybar/modules/cava.sh deleted file mode 100755 index 14ebaca..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/cava.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh -trap 'exit 0' PIPE TERM INT - -# Create vertical bars like terminal cava -bar="▁▂▃▄▅▆▇█" -dict="s/;//g;" -i=0 -while [ $i -lt 8 ]; do - dict="${dict}s/$i/${bar:$i:1}/g;" - i=$((i+1)) -done - -# Add spaces between bars for terminal look -dict="${dict}s/\(.\)/\1 /g;" - -# Write cava config for raw output -echo "[general] -bars = 12 -[output] -method = raw -raw_target = /dev/stdout -data_format = ascii -ascii_max_range = 7" > /tmp/cava_config - -check_counter=0 -current_state="stopped" - -cava -p /tmp/cava_config 2>/dev/null | while read -r line; do - check_counter=$((check_counter + 1)) - if [ $check_counter -ge 20 ]; then - if pactl list sink-inputs 2>/dev/null | grep -q "Sink Input"; then - current_state="playing" - else - current_state="stopped" - fi - check_counter=0 - fi - - if [ "$current_state" = "playing" ]; then - echo "$line" | sed "$dict" 2>/dev/null || exit 0 - else - echo "" 2>/dev/null || exit 0 - fi -done diff --git a/airootfs/etc/skel/.config/waybar/modules/cpu-temp.sh b/airootfs/etc/skel/.config/waybar/modules/cpu-temp.sh deleted file mode 100755 index 8eacc1c..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/cpu-temp.sh +++ /dev/null @@ -1,160 +0,0 @@ -#!/bin/bash -# ~/.config/waybar/modules/cpu-temp-fan.sh -# Combined CPU temperature and fan speed monitor for Waybar - -# SysFS path for fan speed (from your Predator Sense script) -FAN_SYSFS_PATH="/sys/module/linuwu_sense/drivers/platform:acer-wmi/acer-wmi/predator_sense/fan_speed" - -# Function to get CPU temperature -get_cpu_temp() { - local temp="" - - # Method 1: Use sensors command to get Package id 0 temperature - if command -v sensors &> /dev/null; then - temp=$(sensors 2>/dev/null | grep "Package id 0" | awk '{print $4}' | sed 's/+//g' | sed 's/°C//g' | cut -d'.' -f1) - fi - - # Method 2: Fallback to hwmon9 (coretemp) temp1_input which corresponds to Package id 0 - if [ -z "$temp" ] && [ -f "/sys/class/hwmon/hwmon9/temp1_input" ]; then - temp_raw=$(cat /sys/class/hwmon/hwmon9/temp1_input 2>/dev/null) - if [ -n "$temp_raw" ]; then - temp=$((temp_raw / 1000)) - fi - fi - - # Method 3: Alternative hwmon path fallback - if [ -z "$temp" ]; then - for hwmon in /sys/class/hwmon/hwmon*/name; do - if [ "$(cat "$hwmon" 2>/dev/null)" = "coretemp" ]; then - hwmon_dir=$(dirname "$hwmon") - if [ -f "$hwmon_dir/temp1_input" ]; then - temp_raw=$(cat "$hwmon_dir/temp1_input" 2>/dev/null) - if [ -n "$temp_raw" ]; then - temp=$((temp_raw / 1000)) - break - fi - fi - fi - done - fi - - # Remove any whitespace/newlines - temp=$(echo "$temp" | tr -d ' \n\r') - echo "$temp" -} - -# Function to get CPU fan speed percentage -get_cpu_fan_speed() { - local fan_speed="" - - # Check if the fan speed sysfs path exists - if [ -f "$FAN_SYSFS_PATH" ]; then - # Read the fan speed value (format: "cpu_speed,gpu_speed") - fan_data=$(cat "$FAN_SYSFS_PATH" 2>/dev/null) - if [ -n "$fan_data" ]; then - # Extract CPU fan speed (first value before comma) - fan_speed=$(echo "$fan_data" | cut -d',' -f1) - # Handle special case for auto mode (0,0) - if [ "$fan_speed" = "0" ]; then - fan_speed="Auto" - else - fan_speed="${fan_speed}%" - fi - fi - fi - - # Return empty string if no fan data available (instead of "N/A") - echo "$fan_speed" -} - -# Function to determine temperature class and icon -get_temp_status() { - local temp=$1 - - if [ "$temp" -lt 40 ]; then - echo "❄️" "cold" - elif [ "$temp" -lt 60 ]; then - echo "🟢" "good" - elif [ "$temp" -lt 70 ]; then - echo "🟡" "warm" - elif [ "$temp" -lt 80 ]; then - echo "🟠" "hot" - else - echo "🔥" "critical" - fi -} - -# Function to determine fan class -get_fan_status() { - local fan_speed=$1 - - case "$fan_speed" in - "Auto") echo "auto" ;; - "") echo "unknown" ;; # Empty string instead of "N/A" - *%) - # Extract numeric value for percentage-based classification - local speed_num=$(echo "$fan_speed" | sed 's/%//') - if [ "$speed_num" -lt 30 ]; then - echo "low" - elif [ "$speed_num" -lt 60 ]; then - echo "medium" - else - echo "high" - fi - ;; - *) echo "unknown" ;; - esac -} - -# Main execution -main() { - # Get CPU temperature - cpu_temp=$(get_cpu_temp) - - # Get CPU fan speed - cpu_fan=$(get_cpu_fan_speed) - - # Handle case where temperature is not available - if [ -z "$cpu_temp" ] || [ "$cpu_temp" = "0" ]; then - if [ -n "$cpu_fan" ]; then - echo "{\"text\":\"🔍 CPU N/A | 🌀 $cpu_fan\",\"class\":\"unknown\",\"tooltip\":\"CPU temperature not available\"}" - else - echo "{\"text\":\"🔍 CPU N/A\",\"class\":\"unknown\",\"tooltip\":\"CPU temperature not available\"}" - fi - exit 0 - fi - - # Get temperature status - read temp_icon temp_class <<< "$(get_temp_status "$cpu_temp")" - - # Get fan status - fan_class=$(get_fan_status "$cpu_fan") - - # Determine overall class (prioritize temperature for critical situations) - if [ "$temp_class" = "critical" ]; then - overall_class="critical" - elif [ "$temp_class" = "hot" ]; then - overall_class="hot" - elif [ "$temp_class" = "warm" ]; then - overall_class="warm" - elif [ "$fan_class" = "unknown" ]; then - overall_class="unknown" - else - overall_class="good" - fi - - # Format output text - only include fan info if available - if [ -n "$cpu_fan" ]; then - display_text="$temp_icon ${cpu_temp}°C | 🌀 $cpu_fan" - tooltip="CPU Temperature: ${cpu_temp}°C\\nCPU Fan Speed: $cpu_fan" - else - display_text="$temp_icon ${cpu_temp}°C" - tooltip="CPU Temperature: ${cpu_temp}°C\\nCPU Fan Speed: Not available" - fi - - # Output JSON for waybar - echo "{\"text\":\"$display_text\",\"class\":\"$overall_class\",\"tooltip\":\"$tooltip\"}" -} - -# Run main function -main diff --git a/airootfs/etc/skel/.config/waybar/modules/cpufreq.sh b/airootfs/etc/skel/.config/waybar/modules/cpufreq.sh deleted file mode 100755 index eec9755..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/cpufreq.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -# Get current governor -if [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then - GOVERNOR=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor) -else - GOVERNOR="unknown" -fi - -# Get current refresh rate from hyprctl -if command -v hyprctl >/dev/null 2>&1; then - REFRESH_RATE=$(hyprctl monitors | grep -E '@[0-9]+\.[0-9]+' | head -1 | sed -n 's/.*@\([0-9]\+\.[0-9]\+\).*/\1/p') - if [ -n "$REFRESH_RATE" ]; then - # Round to nearest integer for cleaner display - REFRESH_RATE=$(printf "%.0f" "$REFRESH_RATE") - REFRESH_DISPLAY=" | 󰹑 ${REFRESH_RATE}Hz" - else - REFRESH_DISPLAY="" - fi -else - REFRESH_DISPLAY="" -fi - -# Handle click actions -case $1 in - toggle) - # Toggle between powersave and performance - if [ "$GOVERNOR" = "powersave" ]; then - sudo -n auto-cpufreq --force performance >/dev/null 2>&1 - else - sudo -n auto-cpufreq --force powersave >/dev/null 2>&1 - fi - ;; - reset) - # Still requires explicit reset command - sudo -n auto-cpufreq --force reset >/dev/null 2>&1 - ;; -esac - -# Re-read governor after potential change -if [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then - GOVERNOR=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor) -else - GOVERNOR="unknown" -fi - -# Map governor to icon + text with refresh rate -case $GOVERNOR in - "performance") - echo "󰾆 performance${REFRESH_DISPLAY}" - ;; - "powersave") - echo "󰂏 powersave${REFRESH_DISPLAY}" - ;; - "ondemand") - echo "⚖️ ondemand${REFRESH_DISPLAY}" - ;; - "conservative") - echo "🐌 conservative${REFRESH_DISPLAY}" - ;; - "schedutil") - echo "📊 schedutil${REFRESH_DISPLAY}" - ;; - "userspace") - echo "👤 userspace${REFRESH_DISPLAY}" - ;; - *) - echo "❓ $GOVERNOR${REFRESH_DISPLAY}" - ;; -esac diff --git a/airootfs/etc/skel/.config/waybar/modules/debug-temp.sh b/airootfs/etc/skel/.config/waybar/modules/debug-temp.sh deleted file mode 100755 index 7c361ad..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/debug-temp.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -# ~/.config/waybar/modules/debug-temp.sh - -echo "=== Temperature Debug ===" -echo "" - -echo "1. CPU Temperature Methods:" -echo " sensors coretemp:" -sensors coretemp-isa-0000 2>/dev/null | grep "Package id 0" -echo "" - -echo " CPU from sensors (Package id 0):" -cpu_temp=$(sensors coretemp-isa-0000 2>/dev/null | grep "Package id 0" | awk '{print $4}' | sed 's/+//g' | sed 's/°C//g' | cut -d'.' -f1) -echo " Result: '$cpu_temp'" -echo "" - -echo "2. GPU Temperature Methods:" -echo " nvidia-settings:" -nvidia-settings -q gpucoretemp -t 2>/dev/null -echo "" - -echo " nvidia-smi:" -nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null -echo "" - -echo "3. Available hwmon sensors:" -for i in /sys/class/hwmon/hwmon*/name; do - if [ -f "$i" ]; then - echo " $i: $(cat $i 2>/dev/null)" - fi -done -echo "" - -echo "4. Available temperature inputs:" -for i in /sys/class/hwmon/hwmon*/temp*_input; do - if [ -f "$i" ]; then - temp_val=$(cat $i 2>/dev/null) - temp_c=$((temp_val / 1000)) - echo " $i: ${temp_val} (${temp_c}°C)" - fi -done -echo "" - -echo "5. Testing waybar scripts:" -echo " CPU script:" -if [ -f ~/.config/waybar/modules/cpu-temp.sh ]; then - ~/.config/waybar/modules/cpu-temp.sh -else - echo " CPU script not found" -fi -echo "" - -echo " GPU script:" -if [ -f ~/.config/waybar/modules/gpu-temp.sh ]; then - ~/.config/waybar/modules/gpu-temp.sh -else - echo " GPU script not found" -fi diff --git a/airootfs/etc/skel/.config/waybar/modules/gamemode.sh b/airootfs/etc/skel/.config/waybar/modules/gamemode.sh deleted file mode 100755 index ac6378b..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/gamemode.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash -# ____ _ -# / ___| ******* *** ___ ___ ** **_ **| | **_ -# | | * / *` | '_ ` * \ / * \ '_ ` \ / \ / `* |/ * \ -# | |_| | (_| | | | | | | __/ | | | | | (_) | (_| | __/ -# \____|\__,_|_| |_| |_|\___|_| |_| |_|\___/ \__,_|\___| -# -# Enhanced gamemode script with Waybar integration - -CACHE_FILE="$HOME/.cache/gamemode" - -# Function to get current status for Waybar (plain text output like cpufreq) -get_status() { - if [ -f "$CACHE_FILE" ]; then - printf "🎮 ON" - else - printf "🎮 OFF" - fi -} - -# Function to enable gamemode (silent for Waybar) -enable_gamemode() { - if [ -f "$CACHE_FILE" ]; then - return 0 - fi - - hyprctl --batch "\ - keyword animations:enabled 0;\ - keyword decoration:drop_shadow 0;\ - keyword decoration:blur:enabled 0;\ - keyword general:gaps_in 0;\ - keyword general:gaps_out 0;\ - keyword general:border_size 1;\ - keyword decoration:rounding 0" >/dev/null 2>&1 - - touch "$CACHE_FILE" - notify-send "Gamemode" "Activated" >/dev/null 2>&1 & -} - -# Function to disable gamemode (silent for Waybar) -disable_gamemode() { - if [ ! -f "$CACHE_FILE" ]; then - return 0 - fi - - hyprctl reload >/dev/null 2>&1 - rm -f "$CACHE_FILE" - notify-send "Gamemode" "Deactivated" >/dev/null 2>&1 & -} - -# Function for toggle -toggle_gamemode() { - if [ -f "$CACHE_FILE" ]; then - disable_gamemode - else - enable_gamemode - fi -} - -# Handle command line arguments -case "$1" in - "status") - get_status - ;; - "enable") - enable_gamemode - ;; - "disable") - disable_gamemode - ;; - "toggle") - toggle_gamemode - ;; - *) - echo "Usage: $0 {enable|disable|toggle|status}" - exit 1 - ;; -esac diff --git a/airootfs/etc/skel/.config/waybar/modules/gpu-temp.sh b/airootfs/etc/skel/.config/waybar/modules/gpu-temp.sh deleted file mode 100755 index 458a97c..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/gpu-temp.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash -# ~/.config/waybar/modules/gpu-temp-fan.sh -# Combined GPU temperature and fan speed monitor for Waybar - -# SysFS path for fan speed (from your Predator Sense script) -FAN_SYSFS_PATH="/sys/module/linuwu_sense/drivers/platform:acer-wmi/acer-wmi/predator_sense/fan_speed" - -# Get GPU temperature -get_gpu_temp() { - local temp="" - - # Try nvidia-smi first (faster and doesn't require X) - if command -v nvidia-smi &>/dev/null; then - temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -d '\n') - fi - - # Fallback to nvidia-settings if needed - if [[ -z "$temp" || "$temp" == "0" ]] && command -v nvidia-settings &>/dev/null; then - temp=$(DISPLAY=:0 nvidia-settings -q gpucoretemp -t 2>/dev/null | head -1 | tr -d '\n') - fi - - echo "$temp" -} - -# Get GPU fan speed percentage -get_gpu_fan_speed() { - local fan_speed="" - - # Check if the fan speed sysfs path exists - if [ -f "$FAN_SYSFS_PATH" ]; then - # Read the fan speed value (format: "cpu_speed,gpu_speed") - fan_data=$(cat "$FAN_SYSFS_PATH" 2>/dev/null) - if [ -n "$fan_data" ]; then - # Extract GPU fan speed (second value after comma) - fan_speed=$(echo "$fan_data" | cut -d',' -f2) - # Handle special case for auto mode (0,0) - if [ "$fan_speed" = "0" ]; then - fan_speed="Auto" - else - fan_speed="${fan_speed}%" - fi - fi - fi - - # Return empty string if no fan data available (instead of "N/A") - echo "$fan_speed" -} - -# Get status based on temperature -get_status() { - local temp=$1 - - if [[ $temp -lt 50 ]]; then - echo "❄️" "cold" - elif [[ $temp -lt 70 ]]; then - echo "🟢" "good" - elif [[ $temp -lt 80 ]]; then - echo "🟡" "warm" - elif [[ $temp -lt 85 ]]; then - echo "🟠" "hot" - else - echo "🔥" "critical" - fi -} - -# Main execution -temp=$(get_gpu_temp) -fan=$(get_gpu_fan_speed) - -# Handle missing temperature data -if [[ -z "$temp" || "$temp" == "0" ]]; then - if [ -n "$fan" ]; then - echo "{\"text\":\"🔍 GPU N/A | 🌀 $fan\",\"class\":\"unknown\",\"tooltip\":\"GPU temperature not available\"}" - else - echo "{\"text\":\"🔍 GPU N/A\",\"class\":\"unknown\",\"tooltip\":\"GPU temperature not available\"}" - fi - exit 0 -fi - -# Get status -read icon class <<< "$(get_status "$temp")" - -# Format output text - only include fan info if available -if [ -n "$fan" ]; then - display_text="$icon ${temp}°C | 🌀 $fan" - tooltip="GPU: ${temp}°C\\nGPU Fan: $fan" -else - display_text="$icon ${temp}°C" - tooltip="GPU: ${temp}°C\\nGPU Fan: Not available" -fi - -# Output JSON for waybar -echo "{\"text\":\"$display_text\",\"class\":\"$class\",\"tooltip\":\"$tooltip\"}" diff --git a/airootfs/etc/skel/.config/waybar/modules/network_traffic.sh b/airootfs/etc/skel/.config/waybar/modules/network_traffic.sh deleted file mode 100755 index 4a68cc8..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/network_traffic.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/bash - -# network_traffic.sh [-tPOLLING_INTERVAL] [NETWORK_INTERFACE...] - -getopts t: __ && shift -isecs=${OPTARG:-1} -ifaces=($@) -: ${rate_max:=1000000} # maximum transfer rate for {percent}, can be overridden setting the env var - -# `snore` adapted from https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever -# without MacOS workaround, TODO: with _snore_fd initialized separatedly, also i dont touch IFS so dont bother with it -snore() { - local IFS - [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null - read ${1:+-t "$1"} -u $_snore_fd || : -} - -human_readable() { - local hrunits=( B K M G T P ) - local ndigits=${#1} - local idxunit=$(( (2 + ndigits) / 3 - 1)) - local lentrim=$(( ndigits - (idxunit * 3 ) )) - echo ${1::$lentrim}${hrunits[$idxunit]} -} - -exit_err() { - printf '{"text": "⚠ %s", "tooltip": "%s", "class": "error"}\n' "$@" - exit -} - -if test ${#ifaces[@]} -gt 0; then -# sanity check the interface names - for iface in ${ifaces[@]}; do - test -h "/sys/class/net/${iface}" || exit_err "${iface}" "${iface} is not an existing network interface name" - done -else -# default to all interfaces except `lo` - ifaces=(/sys/class/net/*) - ifaces=(${ifaces[@]##*/}) - ifaces=(${ifaces[@]^(eth|wlan|enp|wlp|eno)}) -fi - -# sanity check polling interval -if test ${isecs} -lt 1; then - exit_err "${isecs}" "${isecs} is not a valid polling interval" -fi -# NOTE: `snore` would take a decimal interval but bash arithmetic does not -#if test $(echo "${isecs} >= 0.2" |bc) -eq 0; then -# exit_err "${isecs}" "${isecs} is not a valid polling interval" -#fi - -# NOTE: `/proc/net/dev` format is: -# interface: -# RX bytes packets errs drop fifo frame compressed multicast -# TX bytes packets errs drop fifo colls carrier compressed - -# NOTE: array items are: -# 0=rx_bytes 1=rx_packets 2=rx_errs 3=rx_drop -# 4=tx_bytes 5=tx_packets 6=tx_errs 7=tx_drop -for iface in ${ifaces[@]} aggregate; do - declare -a traffic_prev_${iface} traffic_curr_${iface} traffic_delt_${iface} - declare -n traffic_prev=traffic_prev_${iface} - declare -n traffic_curr=traffic_curr_${iface} - declare -n traffic_delt=traffic_delt_${iface} - traffic_prev=( 0 0 0 0 0 0 0 0 ) - traffic_curr=( 0 0 0 0 0 0 0 0 ) - traffic_delt=( 0 0 0 0 0 0 0 0 ) -done - -# TODO: rearrange the loop, do not show bogus on first iteration -while snore ${isecs} ;do - tooltip="" - traffic_delt_aggregate=( 0 0 0 0 0 0 0 0 ) - - readarray -s2 proc_net_dev /dev/null; then - battery_device=$(upower -e | grep -i bat | head -1) - if [[ -n "$battery_device" ]]; then - upower -i "$battery_device" | grep -E "state|energy|voltage|percentage|time to|power" | sed 's/^ *//' - else - echo "No battery device found" - fi -else - echo "upower not installed" -fi - -echo -echo "=== POWER SUPPLY FILES ===" -echo - -# Raw power supply information -for bat in /sys/class/power_supply/BAT*; do - if [[ -d "$bat" ]]; then - echo "Battery: $(basename "$bat")" - if [[ -f "$bat/power_now" ]]; then - power_uw=$(cat "$bat/power_now" 2>/dev/null) - power_w=$(awk "BEGIN {printf \"%.2f\", $power_uw / 1000000}" 2>/dev/null) - echo " Power Now: $power_uw µW ($power_w W)" - fi - [[ -f "$bat/voltage_now" ]] && echo " Voltage: $(cat "$bat/voltage_now" 2>/dev/null) µV" - [[ -f "$bat/current_now" ]] && echo " Current: $(cat "$bat/current_now" 2>/dev/null) µA" - [[ -f "$bat/status" ]] && echo " Status: $(cat "$bat/status" 2>/dev/null)" - [[ -f "$bat/capacity" ]] && echo " Capacity: $(cat "$bat/capacity" 2>/dev/null)%" - echo - fi -done - - diff --git a/airootfs/etc/skel/.config/waybar/modules/power_monitor.sh b/airootfs/etc/skel/.config/waybar/modules/power_monitor.sh deleted file mode 100755 index 278466f..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/power_monitor.sh +++ /dev/null @@ -1,179 +0,0 @@ -#!/bin/bash -# ~/.config/waybar/scripts/power_monitor.sh -# Portable power monitoring script for waybar - -# Get battery voltage information from any available battery -get_battery_voltage() { - # Use the same approach as your working version - find first available battery - local battery_dir=$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -1) - - if [[ -d "$battery_dir" ]]; then - # Try voltage_now first (like your working version) - local voltage_now=$(cat "$battery_dir/voltage_now" 2>/dev/null) - if [[ -n "$voltage_now" && "$voltage_now" -gt 0 ]]; then - local voltage_v=$(awk "BEGIN {printf \"%.2f\", $voltage_now / 1000000}") - echo "$voltage_v V" - return - fi - - # Try voltage_avg as alternative - local voltage_avg=$(cat "$battery_dir/voltage_avg" 2>/dev/null) - if [[ -n "$voltage_avg" && "$voltage_avg" -gt 0 ]]; then - local voltage_v=$(awk "BEGIN {printf \"%.2f\", $voltage_avg / 1000000}") - echo "$voltage_v V" - return - fi - - # Fallback to design voltage - local design_voltage=$(cat "$battery_dir/voltage_min_design" 2>/dev/null) - if [[ -n "$design_voltage" && "$design_voltage" -gt 0 ]]; then - local voltage_v=$(awk "BEGIN {printf \"%.2f\", $design_voltage / 1000000}") - echo "$voltage_v V (design)" - return - fi - - # Try alternative design voltage file - local design_voltage2=$(cat "$battery_dir/voltage_design" 2>/dev/null) - if [[ -n "$design_voltage2" && "$design_voltage2" -gt 0 ]]; then - local voltage_v=$(awk "BEGIN {printf \"%.2f\", $design_voltage2 / 1000000}") - echo "$voltage_v V (design)" - return - fi - fi - - echo "N/A" -} - -# Get power consumption (watts) if available and non-zero -get_power_consumption() { - # Use the same battery finding approach for consistency - local battery_dir=$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -1) - - if [[ -d "$battery_dir" ]]; then - # Check for power_now file first (most direct) - if [[ -f "$battery_dir/power_now" ]]; then - local power_now=$(cat "$battery_dir/power_now" 2>/dev/null) - if [[ -n "$power_now" && "$power_now" != "0" && "$power_now" =~ ^-?[0-9]+$ ]]; then - local power_w=$(awk "BEGIN {printf \"%.1f\", $power_now / 1000000}") - echo "${power_w}W" - return - fi - fi - - # Alternative: calculate from current and voltage - if [[ -f "$battery_dir/current_now" ]]; then - local current_now=$(cat "$battery_dir/current_now" 2>/dev/null) - if [[ -n "$current_now" && "$current_now" != "0" && "$current_now" =~ ^-?[0-9]+$ ]]; then - # Get voltage for power calculation - local voltage_now=$(cat "$battery_dir/voltage_now" 2>/dev/null) - if [[ -z "$voltage_now" || "$voltage_now" == "0" ]]; then - voltage_now=$(cat "$battery_dir/voltage_avg" 2>/dev/null) - fi - - if [[ -n "$voltage_now" && "$voltage_now" -gt 0 ]]; then - # Calculate power in watts (current in microamps * voltage in microvolts / 1000000000000) - local power_uw=$(awk "BEGIN {printf \"%.0f\", ($current_now * $voltage_now) / 1000000}") - if [[ "$power_uw" != "0" ]]; then - local power_w=$(awk "BEGIN {printf \"%.1f\", $power_uw / 1000000}") - echo "${power_w}W" - return - fi - fi - fi - fi - fi - - echo "" -} - -# Get battery status from first available battery -get_battery_status() { - local status_file=$(ls /sys/class/power_supply/BAT*/status 2>/dev/null | head -1) - if [[ -f "$status_file" ]]; then - cat "$status_file" 2>/dev/null | tr '[:upper:]' '[:lower:]' - else - echo "unknown" - fi -} - -# Get battery percentage from first available battery -get_battery_percentage() { - local capacity_file=$(ls /sys/class/power_supply/BAT*/capacity 2>/dev/null | head -1) - if [[ -f "$capacity_file" ]]; then - cat "$capacity_file" 2>/dev/null - else - echo "N/A" - fi -} - -# Check if AC adapter is connected (supports various naming conventions) -is_ac_connected() { - # Check standard patterns like your working version - for ac in /sys/class/power_supply/A{C,DP}*; do - if [[ -d "$ac" && -f "$ac/online" ]]; then - [[ $(cat "$ac/online" 2>/dev/null) == "1" ]] && return 0 - fi - done - - # Additional check for ACAD pattern - for ac in /sys/class/power_supply/ACAD*; do - if [[ -d "$ac" && -f "$ac/online" ]]; then - [[ $(cat "$ac/online" 2>/dev/null) == "1" ]] && return 0 - fi - done - - return 1 -} - -# Get battery count for tooltip -get_battery_count() { - local count=$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | wc -l) - echo "$count" -} - -# Main execution -voltage=$(get_battery_voltage) -power=$(get_power_consumption) -status=$(get_battery_status) -percentage=$(get_battery_percentage) -battery_count=$(get_battery_count) - -# Build display text -text="$voltage" -if [[ -n "$power" ]]; then - text="$text | $power" -fi - -# Create detailed tooltip -if is_ac_connected; then - tooltip="🔌 AC Connected" -else - tooltip="🔋 On Battery" -fi - -tooltip="$tooltip | Voltage: $voltage" - -if [[ -n "$power" ]]; then - tooltip="$tooltip | Power: $power" -fi - -tooltip="$tooltip | Battery: ${percentage}% (${status})" - -if [[ "$battery_count" -gt 1 ]]; then - tooltip="$tooltip | ${battery_count} batteries detected" -fi - -# Determine CSS class based on status -css_class="power-monitor" -if is_ac_connected; then - css_class="$css_class ac-connected" -else - css_class="$css_class battery-mode" - # Add low battery warning class - if [[ "$percentage" != "N/A" && "$percentage" -lt 20 ]]; then - css_class="$css_class low-battery" - fi -fi - -# Output JSON for waybar -printf '{"text": "%s", "tooltip": "%s", "class": "%s"}\n' "$text" "$tooltip" "$css_class" diff --git a/airootfs/etc/skel/.config/waybar/modules/record-status.sh b/airootfs/etc/skel/.config/waybar/modules/record-status.sh deleted file mode 100755 index b0bc9da..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/record-status.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -RECORD_PID_FILE="/tmp/wf-recording.pid" - -if [ -f "$RECORD_PID_FILE" ] && kill -0 "$(cat "$RECORD_PID_FILE")" 2>/dev/null; then - echo '{"text": "", "tooltip": "Recording", "class": "recording"}' -else - echo '{"text": " ", "tooltip": "Not recording", "class": "idle"}' -fi diff --git a/airootfs/etc/skel/.config/waybar/modules/refresh-rate.sh b/airootfs/etc/skel/.config/waybar/modules/refresh-rate.sh deleted file mode 100755 index 403493d..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/refresh-rate.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash - -CONFIG_FILE="$HOME/.config/hypr/monitors.conf" -MONITOR_NAME="eDP-1" - -# Get current refresh rate from hyprctl -get_current_refresh_rate() { - hyprctl monitors | grep -A 1 "Monitor $MONITOR_NAME" | grep -o '@[0-9]*\.[0-9]*' | sed 's/@//' | head -1 -} - -# Update the config file -update_config_file() { - local target_rate=$1 - - if [ -f "$CONFIG_FILE" ]; then - # Comment out current line and uncomment target line - if [ "$target_rate" = "165" ]; then - sed -i 's/^monitor=eDP-1,2560x1600@165/#&/' "$CONFIG_FILE" - sed -i 's/^#monitor=eDP-1,2560x1600@60/monitor=eDP-1,2560x1600@60/' "$CONFIG_FILE" - else - sed -i 's/^monitor=eDP-1,2560x1600@60/#&/' "$CONFIG_FILE" - sed -i 's/^#monitor=eDP-1,2560x1600@165/monitor=eDP-1,2560x1600@165/' "$CONFIG_FILE" - fi - fi -} - -# Handle click actions -case $1 in - toggle) - # Get current refresh rate - CURRENT_RATE=$(get_current_refresh_rate) - CURRENT_RATE_INT=$(printf "%.0f" "$CURRENT_RATE") - - if [ "$CURRENT_RATE_INT" = "165" ] || [ "$CURRENT_RATE_INT" = "164" ]; then - # Switch to 60Hz - hyprctl keyword monitor "$MONITOR_NAME,2560x1600@60,0x0,1.0" >/dev/null 2>&1 - update_config_file 165 - echo "🔄 Switched to 60Hz" - else - # Switch to 165Hz - hyprctl keyword monitor "$MONITOR_NAME,2560x1600@165,0x0,1.0" >/dev/null 2>&1 - update_config_file 60 - echo "🔄 Switched to 165Hz" - fi - ;; - status) - # Show current refresh rate - CURRENT_RATE=$(get_current_refresh_rate) - CURRENT_RATE_INT=$(printf "%.0f" "$CURRENT_RATE") - - if [ "$CURRENT_RATE_INT" = "165" ] || [ "$CURRENT_RATE_INT" = "164" ]; then - echo "🖥️ ${CURRENT_RATE_INT}Hz" - else - echo "🖥️ ${CURRENT_RATE_INT}Hz" - fi - ;; - *) - # Default: show current status - CURRENT_RATE=$(get_current_refresh_rate) - CURRENT_RATE_INT=$(printf "%.0f" "$CURRENT_RATE") - - if [ "$CURRENT_RATE_INT" = "165" ] || [ "$CURRENT_RATE_INT" = "164" ]; then - echo "🖥️ ${CURRENT_RATE_INT}Hz" - else - echo "🖥️ ${CURRENT_RATE_INT}Hz" - fi - ;; -esac diff --git a/airootfs/etc/skel/.config/waybar/modules/storage.sh b/airootfs/etc/skel/.config/waybar/modules/storage.sh deleted file mode 100755 index 531a6fe..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/storage.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -mount="/" -warning=20 -critical=10 - -df -h -P -l "$mount" | awk -v warning=$warning -v critical=$critical ' -/\/.*/ { - text=$4 - text=" "text - tooltip="Filesystem: "$1" \rSize: "$2"\rUsed: "$3"\rAvail: "$4"\rUse%: "$5"\rMounted on: "$6 - use=$5 - exit 0 -} -END { - class="" - gsub(/%$/,"",use) - if ((100 - use) < critical) { - class="critical" - } else if ((100 - use) < warning) { - class="warning" - } - print "{\"text\":\""text"\", \"percentage\":"use",\"tooltip\":\""tooltip"\", \"class\":\""class"\"}" -} -' diff --git a/airootfs/etc/skel/.config/waybar/modules/system_power.sh b/airootfs/etc/skel/.config/waybar/modules/system_power.sh deleted file mode 100755 index a9459e9..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/system_power.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -# ~/.config/waybar/scripts/system_power.sh - - -echo "=== SYSTEM POWER USAGE ===" -echo - -# Check for powertop -if command -v powertop &> /dev/null; then - echo "Getting system power usage (this may take a few seconds)..." - sudo powertop --dump --quiet --time=1 2>/dev/null | grep -A 10 "Power est" | head -10 - echo -fi - -# CPU frequency and governor -echo "=== CPU INFO ===" -if [[ -f /proc/cpuinfo ]]; then - echo "CPU: $(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2 | xargs)" -fi - -if [[ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]]; then - echo "Governor: $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)" -fi - -if [[ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq ]]; then - freq=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq) - echo "Frequency: $(awk "BEGIN {printf \"%.2f\", $freq / 1000}")MHz" -fi - -echo -echo "=== THERMAL INFO ===" -# Temperature info -if command -v sensors &> /dev/null; then - sensors | grep -E "(Core|Package|temp)" | head -5 -elif [[ -f /sys/class/thermal/thermal_zone0/temp ]]; then - temp=$(cat /sys/class/thermal/thermal_zone0/temp) - echo "CPU Temp: $(awk "BEGIN {printf \"%.1f\", $temp / 1000}")°C" -fi - -echo -echo "=== LOAD AVERAGE ===" -echo "Load: $(uptime | awk -F'load average:' '{print $2}' | xargs)" - -# Memory usage -echo -echo "=== MEMORY USAGE ===" -free -h | grep -E "(Mem|Swap)" | head -2 - - diff --git a/airootfs/etc/skel/.config/waybar/modules/toggle-record.sh b/airootfs/etc/skel/.config/waybar/modules/toggle-record.sh deleted file mode 100755 index 504a8cb..0000000 --- a/airootfs/etc/skel/.config/waybar/modules/toggle-record.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -RECORD_PID_FILE="/tmp/wf-recording.pid" -#AUDIO_SOURCE="$(pactl list short sources | awk '/\.monitor/ && /RUNNING/ { print $2; exit }')" -AUDIO_SOURCE="alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo.monitor" - -# Function to stop recording -stop_recording() { - if [ -f "$RECORD_PID_FILE" ]; then - kill -INT "$(cat "$RECORD_PID_FILE")" 2>/dev/null && rm -f "$RECORD_PID_FILE" - fi -} - -# If already recording, stop it -if [ -f "$RECORD_PID_FILE" ] && kill -0 "$(cat "$RECORD_PID_FILE")" 2>/dev/null; then - stop_recording - exit 0 -fi - -# Otherwise, start recording -geometry="$(slurp -d)" -if [ -n "$geometry" ]; then - pkill -USR1 -x record-screend - mkdir -p ~/Videos/Recordings - wf-recorder \ - -f ~/Videos/Recordings/"screen-record-$(date +%Y-%m-%d-%H-%M-%S).mp4" \ - -g "$geometry" \ - --audio \ - --audio-source="$AUDIO_SOURCE" & - echo $! > "$RECORD_PID_FILE" - pkill -USR2 -x record-screend -fi diff --git a/airootfs/etc/skel/.config/waybar/style.css b/airootfs/etc/skel/.config/waybar/style.css old mode 100755 new mode 100644 index 97beef5..f721b13 --- a/airootfs/etc/skel/.config/waybar/style.css +++ b/airootfs/etc/skel/.config/waybar/style.css @@ -1,424 +1,74 @@ +/* SimulationOS - Waybar stylesheet */ * { - font-family: "Fira Sans Semibold", "Font Awesome 6 Free", FontAwesome, Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; /* Slightly smaller font size */ - font-weight: 900; - margin: 0; - padding: 0; - transition-property: background-color; - transition-duration: 0.5s; + font-family: "JetBrainsMono Nerd Font", "Fira Sans", sans-serif; + font-size: 13px; + border: none; + border-radius: 0; + min-height: 0; } -/* Reset all styles */ -* { - border: none; - border-radius: 3px; - min-height: 0; - margin: 0.2em 0.3em 0.2em 0.3em; -} - -/* The whole bar */ -#waybar { - background-color: transparent; - color: #ffffff; - transition-property: background-color; - transition-duration: 0.5s; - border-radius: 0px; - margin: 0px 0px; -} - -window#waybar.hidden { - opacity: 0.2; +window#waybar { + background: rgba(20, 22, 26, 0.85); + color: #e6e6e6; } #workspaces button { - padding: 3px 5px; /* Adjusted padding to reduce height */ - margin: 3px 5px; /* Reduced margin slightly */ - border-radius: 6px; - color: @foreground; - background-color: #111827; - transition: all 0.3s ease-in-out; - font-size: 13px; /* Slightly smaller font size */ + padding: 0 9px; + margin: 3px 2px; + color: #8a8f98; + background: transparent; + border-radius: 6px; } #workspaces button.active { - color: @foreground; - background: #025939; -} - -#workspaces button:hover { - background: #333333; + color: #ffffff; + background: #1793d1; } #workspaces button.urgent { - background-color: #eb4d4b; + color: #ffffff; + background: #c0392b; } -#workspaces { - background-color: #111827; - border-radius: 14px; - padding: 3px 6px; /* Adjusted padding to reduce height */ +#workspaces button:hover { + background: rgba(23, 147, 209, 0.35); } #window { - background-color: #111827; - font-size: 15px; /* Slightly smaller font size */ - font-weight: 800; - color: @foreground; - border-radius: 14px; - padding: 3px 6px; /* Reduced padding */ - margin: 2px; - opacity: 1; + color: #c8ccd4; } -#custom-network_traffic, -#hyprland-window, -#custom-gamemode, -#custom-electricity, -#custom-cpu-temp, -#custom-gpu-temp, -#clock.date, -#clock, -#battery, +#tray, +#pulseaudio, +#network, #cpu, -#custom-cpufreq, #memory, -#disk, #temperature, -#backlight, -#network, -#pulseaudio, -#wireplumber, -#custom-media, -#mode, -#idle_inhibitor, -#mpd, -#bluetooth, -#custom-hyprPicker, -#custom-power-menu, -#custom-spotify, -#custom-weather, -#custom-weather.severe, -#custom-recorder, -#custom-weather.sunnyDay, -#custom-weather.clearNight, -#custom-weather.cloudyFoggyDay, -#custom-weather.cloudyFoggyNight, -#custom-weather.rainyDay, -#custom-weather.rainyNight, -#custom-weather.showyIcyDay, -#custom-weather.snowyIcyNight, -#custom-weather.default { - background-color: #111827; - border-radius: 14px; - padding: 5px; /* Adjusted padding */ -} - - -#custom-recorder { - background-color: #111827; - border-radius: 10px; - padding: 5px; /* Adjusted padding */ - -} - -#custom-screenshot_t { - background-color: #111827; - border-radius: 14px; - padding: 6px; -} - -#custom-weather { - background-color: transparent; - border-radius: 14px; - padding: 6px; -} - -#clock.date { - font-size: 14px; - font-weight: bold; - background-color: #111827; - border-radius: 14px; - padding: 4px 8px; -} - -#custom-gpu-temp, -#custom-cpu-temp { - background-color: #111827; - color: #fb958b; /* match #cpu text color */ - border-radius: 14px; - padding: 6px; /* uniform padding */ - font-weight: bold; - font-size: 14px; - margin: 0 5px; -} - -#custom-electricity { - background-color: #111827; - color: #ebcb8b; /* match #custom-launcher text color */ - border-radius: 14px; /* unify radius to 14px */ - padding: 6px; /* unify padding */ - font-weight: bold; - font-size: 14px; - margin: 0 5px; -} - - -#custom-storage { - background-color: #111827; - color: #ebcb8b; /* Use memory module yellow */ - border-radius: 14px; - padding: 6px; - font-weight: bold; - font-size: 14px; - margin: 0 5px; -} - -#custom-cpufreq { - background-color: #111827; - color: #ffb958; - border-radius: 14px; - padding: 6px; - font-weight: bold; - font-size: 14px; - margin: 0 5px; -} - - - - - -/*()#custom-gpu-temp { - margin: 0 5px; - padding: 0 8px; - border-radius: 8px; - font-weight: bold; - min-width: 80px; -} - - - -#custom-cpu-temp { - margin: 0 5px; - padding: 0 8px; - border-radius: 8px; - font-weight: bold; - min-width: 80px; -} - -*/ - - - - -#custom-spotify { - background-color: #111827; - border-radius: 14px; - padding: 3px 12px; -} - -#tray { - background-color: #111827; - border-radius: 14px; - padding: 6px; /* Adjusted padding */ -} - -#cpu { - color: #fb958b; - background-color: #111827; - padding: 6px; -} - - -#memory { - color: #ebcb8b; - background-color: #111827; - padding: 6px; -} - +#battery, +#clock, #custom-power { - background-color: #111827; - border-radius: 14px; - padding: 6px; -} - -/*#custom-storage { - background-color: #111827; - border-radius: 14px; - padding: 6px; -}*/ - -#custom-launcher { - background-color: #1b242b; - color: #6a92d7; - border-radius: 7.5px; - padding: 3px 6px; /* Adjusted padding */ -} - -#custom-weather.severe { - color: #eb937d; -} - -#custom-weather.sunnyDay { - color: #c2ca76; -} - -#custom-weather.clearNight { - color: #cad3f5; -} - -#custom-weather.cloudyFoggyDay, -#custom-weather.cloudyFoggyNight { - color: #c2ddda; -} - -#custom-weather.rainyDay, -#custom-weather.rainyNight { - color: #5aaca5; -} - -#custom-weather.showyIcyDay, -#custom-weather.snowyIcyNight { - color: #d6e7e5; -} - -#custom-weather.default { - color: #dbd9d8; -} - -/* If workspaces is the leftmost module, omit left margin */ -.modules-left > widget:first-child > #workspaces { - margin-left: 0; -} - -/* If workspaces is the rightmost module, omit right margin */ -.modules-right > widget:last-child > #workspaces { - margin-right: 0; -} - -#pulseaudio { - color: #7d9bba; -} - -#backlight { - color: #8fbcbb; + padding: 0 10px; + margin: 3px 2px; + background: rgba(255, 255, 255, 0.05); + border-radius: 6px; } #clock { - color: #c8d2e0; -} - -#battery { - color: #c0caf5; -} - -#battery.charging, -#battery.full, -#battery.plugged { - color: #26a65b; -} - -@keyframes blink { - to { - background-color: rgba(30, 34, 42, 0.5); - color: #abb2bf; - } -} - -#battery.critical:not(.charging) { - color: #f53c3c; - animation-name: blink; - animation-duration: 0.5s; - animation-timing-function: linear; - animation-iteration-count: infinite; - animation-direction: alternate; -} - -label:focus { - background-color: #000000; + color: #ffffff; + background: rgba(23, 147, 209, 0.28); } -#disk { - background-color: #964b00; -} - -#bluetooth { - color: #707d9d; -} - -#bluetooth.disconnected { - color: #f53c3c; -} - -#network { - color: #b48ead; -} - -#network.disconnected { - color: #f53c3c; -} - -#custom-media { - background-color: #66cc99; - color: #2a5c45; - min-width: 100px; -} - -#custom-media.custom-spotify { - background-color: #66cc99; -} - -#custom-media.custom-vlc { - background-color: #ffa000; -} - -#temperature { - background-color: transparent; -} - -#temperature.critical { - background-color: #eb4d4b; -} - -#tray > .passive { - -gtk-icon-effect: dim; -} - -#tray > .needs-attention { - -gtk-icon-effect: highlight; - background-color: #eb4d4b; -} - -#idle_inhibitor { - background-color: #2d3436; -} - -#idle_inhibitor.activated { - background-color: #ecf0f1; - color: #2d3436; -} - -#language { - background: #00b093; - color: #740864; - padding: 0 0px; - margin: 0 5px; - min-width: 16px; -} - -#keyboard-state { - background: #97e1ad; - color: #000000; - padding: 0 0px; - margin: 0 5px; - min-width: 16px; +#custom-power { + color: #ff7b72; } -#keyboard-state > label { - padding: 0 0px; +#temperature.critical, +#battery.critical { + color: #ffffff; + background: #c0392b; } -#keyboard-state > label.locked { - background: rgba(0, 0, 0, 0.2); +#battery.warning { + color: #f0c674; } - diff --git a/airootfs/usr/local/bin/simos-clipboard b/airootfs/usr/local/bin/simos-clipboard new file mode 100755 index 0000000..b818d8a --- /dev/null +++ b/airootfs/usr/local/bin/simos-clipboard @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# SimulationOS clipboard history picker. +# Requires: cliphist, wofi, wl-clipboard. +set -euo pipefail + +cliphist list \ + | wofi --dmenu --prompt 'Clipboard' --width 720 --lines 12 \ + | cliphist decode \ + | wl-copy diff --git a/airootfs/usr/local/bin/simos-powermenu b/airootfs/usr/local/bin/simos-powermenu new file mode 100755 index 0000000..67f2f7c --- /dev/null +++ b/airootfs/usr/local/bin/simos-powermenu @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# SimulationOS power menu (wofi based). +set -euo pipefail + +choice="$(printf '%s\n' \ + 'Lock session' \ + 'Log out' \ + 'Reboot' \ + 'Power off' \ + | wofi --dmenu --prompt 'SimulationOS' --lines 5 --width 320)" || exit 0 + +case "$choice" in + 'Lock session') + hyprlock + ;; + 'Log out') hyprctl dispatch exit ;; + 'Reboot') systemctl reboot ;; + 'Power off') systemctl poweroff ;; +esac diff --git a/airootfs/usr/local/bin/simos-screenshot b/airootfs/usr/local/bin/simos-screenshot new file mode 100755 index 0000000..b116627 --- /dev/null +++ b/airootfs/usr/local/bin/simos-screenshot @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# SimulationOS screenshot helper. +# Requires: grim, slurp, wl-clipboard (all in packages.x86_64). +set -euo pipefail + +mode="${1:-full}" +outdir="${XDG_PICTURES_DIR:-$HOME/Pictures}/Screenshots" +mkdir -p "$outdir" +file="$outdir/simos-$(date +%Y%m%d-%H%M%S).png" + +notify() { + command -v notify-send >/dev/null 2>&1 && notify-send "Screenshot" "$1" || printf '%s\n' "$1" +} + +case "$mode" in + full) + grim "$file" + ;; + area) + geometry="$(slurp)" || exit 1 + grim -g "$geometry" "$file" + ;; + window) + # Ask Hyprland for the focused window's box; fall back to area select. + geometry="" + if command -v hyprctl >/dev/null 2>&1; then + geometry="$(hyprctl activewindow | awk ' + /^[[:space:]]*at:/ { gsub(/,/, " ", $2); ax=$2; ay=$3 } + /^[[:space:]]*size:/ { gsub(/,/, " ", $2); sw=$2; sh=$3 } + END { if (sw != "" && sh != "") printf "%d,%d %dx%d", ax, ay, sw, sh }')" + fi + if [ -z "$geometry" ]; then + geometry="$(slurp)" || exit 1 + fi + grim -g "$geometry" "$file" + ;; + *) + printf 'usage: %s [full|area|window]\n' "${0##*/}" >&2 + exit 2 + ;; +esac + +wl-copy --type image/png < "$file" +notify "Saved to $file and copied to the clipboard" diff --git a/airootfs/usr/share/backgrounds/simulationos/simulationos-alpha.png b/airootfs/usr/share/backgrounds/simulationos/simulationos-alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..15779f7ef15a53ed92a302fc4d410702475c2139 GIT binary patch literal 304608 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
RWxTVaANboQZh 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 z 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 K1QiEF?ff6HMrYOl literal 0 HcmV?d00001 diff --git a/airootfs/usr/share/pixmaps/simulationos.svg b/airootfs/usr/share/pixmaps/simulationos.svg new file mode 100644 index 0000000..bf9acb5 --- /dev/null +++ b/airootfs/usr/share/pixmaps/simulationos.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/scripts/make-wallpaper.py b/scripts/make-wallpaper.py new file mode 100755 index 0000000..f73bb51 --- /dev/null +++ b/scripts/make-wallpaper.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Generate the SimulationOS wallpaper. + +Pure stdlib (zlib + struct) so it runs anywhere without Pillow. Re-run to +regenerate airootfs/usr/share/backgrounds/simulationos/simulationos-alpha.png. +""" +import math +import os +import struct +import sys +import zlib + +W, H = 1920, 1080 +BASE = (0x10, 0x12, 0x16) # near-black +ACCENT = (0x17, 0x93, 0xD1) # SimulationOS blue +GLOW = (0x33, 0xCC, 0xFF) + + +def mix(a, b, t): + t = max(0.0, min(1.0, t)) + return tuple(int(round(a[i] + (b[i] - a[i]) * t)) for i in range(3)) + + +def build_rows(): + cx, cy = W * 0.72, H * 0.28 + maxd = math.hypot(W, H) + rows = [] + for y in range(H): + row = bytearray() + for x in range(W): + # Radial falloff from an off-centre light source. + d = math.hypot(x - cx, y - cy) / maxd + t = max(0.0, 1.0 - d * 1.55) ** 2.2 + r, g, b = mix(BASE, ACCENT, t * 0.85) + + # Diagonal sheen. + sheen = (math.sin((x + y) * math.pi / 900.0) + 1.0) * 0.5 + r, g, b = mix((r, g, b), GLOW, t * sheen * 0.18) + + # Faint 60px "simulation" grid, brighter near the light source. + if x % 60 == 0 or y % 60 == 0: + r, g, b = mix((r, g, b), GLOW, 0.05 + t * 0.10) + + row += bytes((r, g, b)) + rows.append(bytes(row)) + return rows + + +def write_png(path, rows): + raw = b"".join(b"\x00" + r for r in rows) + + def chunk(tag, data): + c = struct.pack(">I", len(data)) + tag + data + return c + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF) + + png = b"\x89PNG\r\n\x1a\n" + png += chunk(b"IHDR", struct.pack(">IIBBBBB", W, H, 8, 2, 0, 0, 0)) + png += chunk(b"IDAT", zlib.compress(raw, 9)) + png += chunk(b"IEND", b"") + with open(path, "wb") as fh: + fh.write(png) + + +if __name__ == "__main__": + out = sys.argv[1] if len(sys.argv) > 1 else os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "airootfs/usr/share/backgrounds/simulationos/simulationos-alpha.png", + ) + os.makedirs(os.path.dirname(out), exist_ok=True) + write_png(out, build_rows()) + print(f"wrote {out} ({os.path.getsize(out)} bytes, {W}x{H})") From 5b6c804c13054803b6094f8724aa09ca6d85faac Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:20:42 +0300 Subject: [PATCH 04/33] live: coherent session, service, network and audio configuration Session: - SDDM with the default X11 greeter (xorg-server/xorg-xauth are hard deps) launching the Wayland hyprland.desktop; theme 'maldives' ships in the sddm package, unlike the previously configured 'breeze' which needs Plasma - display-manager.service symlink instead of the never-invoked dmcheck guesser - the SDDM autologin config was previously filed under polkit-1/rules.d/, where SDDM would never read it Network: NetworkManager only. systemd-networkd, its .network files, iwd, statically enabled wpa_supplicant and systemd-resolved (plus the resolv.conf stub symlink that depended on it) are removed. Running two network stacks is a reliable source of nondeterministic breakage. Audio: pipewire/wireplumber user units are enabled explicitly via /etc/systemd/user/, derived from the units' actual [Install] sections, rather than relying on package presets (the packages ship no .wants symlinks). Security: sshd is installed but no longer enabled by default, and the permissive upstream drop-in (PermitRootLogin yes) is replaced. Live-only privilege grants are namespaced *-live-* so the installer can strip them. --- airootfs/etc/pacman.conf | 88 +++---------------- airootfs/etc/pacman.d/mirrorlist | 11 --- .../etc/polkit-1/49-nopasswd_global.rules | 9 -- .../49-simulationos-live-nopasswd.rules | 10 +++ .../rules.d/sddm.conf.d/autologin.conf | 15 ---- airootfs/etc/resolv.conf | 1 - airootfs/etc/sddm.conf.d/10-simulationos.conf | 9 ++ .../20-simulationos-live-autologin.conf | 6 ++ .../etc/ssh/sshd_config.d/10-archiso.conf | 3 - .../ssh/sshd_config.d/10-simulationos.conf | 17 ++++ airootfs/etc/sudoers.d/10-simulationos-live | 4 + airootfs/etc/sudoers.d/g_wheel | 1 - .../ipv6-privacy-extensions.conf | 2 - .../etc/systemd/network/20-ethernet.network | 24 ----- airootfs/etc/systemd/network/20-wlan.network | 20 ----- airootfs/etc/systemd/network/20-wwan.network | 19 ---- .../etc/systemd/resolved.conf.d/archiso.conf | 4 - .../system/dbus-fi.w1.wpa_supplicant1.service | 1 - .../systemd/system/display-manager.service | 1 + .../multi-user.target.wants/sshd.service | 1 - .../systemd-networkd.service | 1 - .../systemd-resolved.service | 1 - .../wpa_supplicant.service | 1 - .../systemd-networkd.socket | 1 - .../pipewire-pulse.service | 1 + .../default.target.wants/pipewire.service | 1 + .../user/pipewire-session-manager.service | 1 + .../wireplumber.service | 1 + .../pipewire-pulse.socket | 1 + .../user/sockets.target.wants/pipewire.socket | 1 + airootfs/etc/xdg/reflector/reflector.conf | 8 -- 31 files changed, 63 insertions(+), 201 deletions(-) delete mode 100644 airootfs/etc/pacman.d/mirrorlist delete mode 100644 airootfs/etc/polkit-1/49-nopasswd_global.rules create mode 100644 airootfs/etc/polkit-1/rules.d/49-simulationos-live-nopasswd.rules delete mode 100644 airootfs/etc/polkit-1/rules.d/sddm.conf.d/autologin.conf delete mode 120000 airootfs/etc/resolv.conf create mode 100644 airootfs/etc/sddm.conf.d/10-simulationos.conf create mode 100644 airootfs/etc/sddm.conf.d/20-simulationos-live-autologin.conf delete mode 100644 airootfs/etc/ssh/sshd_config.d/10-archiso.conf create mode 100644 airootfs/etc/ssh/sshd_config.d/10-simulationos.conf create mode 100644 airootfs/etc/sudoers.d/10-simulationos-live delete mode 100644 airootfs/etc/sudoers.d/g_wheel delete mode 100644 airootfs/etc/systemd/network.conf.d/ipv6-privacy-extensions.conf delete mode 100644 airootfs/etc/systemd/network/20-ethernet.network delete mode 100644 airootfs/etc/systemd/network/20-wlan.network delete mode 100644 airootfs/etc/systemd/network/20-wwan.network delete mode 100644 airootfs/etc/systemd/resolved.conf.d/archiso.conf delete mode 120000 airootfs/etc/systemd/system/dbus-fi.w1.wpa_supplicant1.service create mode 120000 airootfs/etc/systemd/system/display-manager.service delete mode 120000 airootfs/etc/systemd/system/multi-user.target.wants/sshd.service delete mode 120000 airootfs/etc/systemd/system/multi-user.target.wants/systemd-networkd.service delete mode 120000 airootfs/etc/systemd/system/multi-user.target.wants/systemd-resolved.service delete mode 120000 airootfs/etc/systemd/system/multi-user.target.wants/wpa_supplicant.service delete mode 120000 airootfs/etc/systemd/system/sockets.target.wants/systemd-networkd.socket create mode 120000 airootfs/etc/systemd/user/default.target.wants/pipewire-pulse.service create mode 120000 airootfs/etc/systemd/user/default.target.wants/pipewire.service create mode 120000 airootfs/etc/systemd/user/pipewire-session-manager.service create mode 120000 airootfs/etc/systemd/user/pipewire.service.wants/wireplumber.service create mode 120000 airootfs/etc/systemd/user/sockets.target.wants/pipewire-pulse.socket create mode 120000 airootfs/etc/systemd/user/sockets.target.wants/pipewire.socket delete mode 100644 airootfs/etc/xdg/reflector/reflector.conf diff --git a/airootfs/etc/pacman.conf b/airootfs/etc/pacman.conf index 57daefc..5584ccc 100644 --- a/airootfs/etc/pacman.conf +++ b/airootfs/etc/pacman.conf @@ -1,106 +1,38 @@ # -# /etc/pacman.conf +# /etc/pacman.conf - SimulationOS # -# See the pacman.conf(5) manpage for option and repository directives - -# -# GENERAL OPTIONS +# See the pacman.conf(5) manpage for option and repository directives. # + [options] -# The following paths are commented out with their default values listed. -# If you wish to use different paths, uncomment and update the paths. -#RootDir = / -#DBPath = /var/lib/pacman/ -#CacheDir = /var/cache/pacman/pkg/ -#LogFile = /var/log/pacman.log -#GPGDir = /etc/pacman.d/gnupg/ -#HookDir = /etc/pacman.d/hooks/ HoldPkg = pacman glibc -#XferCommand = /usr/bin/curl -L -C - -f -o %o %u -#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u -#CleanMethod = KeepInstalled -#UseDelta = 0.7 Architecture = auto -# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup -#IgnorePkg = -#IgnoreGroup = - -#NoUpgrade = -#NoExtract = - -# Misc options -#UseSyslog Color -ILoveCandy -#NoProgressBar -#CheckSpace +CheckSpace VerbosePkgLists +ParallelDownloads = 5 DisableDownloadTimeout -ParallelDownloads = 10 -DownloadUser = alpm -#DisableSandbox -# By default, pacman accepts packages signed by keys that its local keyring -# trusts (see pacman-key and its man page), as well as unsigned packages. SigLevel = Required DatabaseOptional LocalFileSigLevel = Optional -#RemoteFileSigLevel = Required - -# NOTE: You must run `pacman-key --init` before first using pacman; the local -# keyring can then be populated with the keys of all official Arch Linux -# packagers with `pacman-key --populate archlinux`. # # REPOSITORIES -# - can be defined here or included from another file -# - pacman will search repositories in the order defined here -# - local/custom mirrors can be added here or in separate files -# - repositories listed first will take precedence when packages -# have identical names, regardless of version number -# - URLs will have $repo replaced by the name of the current repo -# - URLs will have $arch replaced by the name of the architecture -# -# Repository entries are of the format: -# [repo-name] -# Server = ServerName -# Include = IncludePath # -# The header [repo-name] is crucial - it must be present and -# uncommented to enable the repo. +# Arch first, CachyOS last: see the comment in the build-host pacman.conf. +# Here the mirrorlists come from the pacman-mirrorlist and cachyos-mirrorlist +# packages, both of which are installed in SimulationOS. # -[cachyos] -SigLevel = Optional TrustAll -Include = /etc/pacman.d/cachyos-mirrorlist - -# The testing repositories are disabled by default. To enable, uncomment the -# repo name header and Include lines. You can add preferred servers immediately -# after the header, and they will be used before the default mirrors. - -#[core-testing] -#Include = /etc/pacman.d/mirrorlist - [core] Include = /etc/pacman.d/mirrorlist -#[extra-testing] -#Include = /etc/pacman.d/mirrorlist - [extra] Include = /etc/pacman.d/mirrorlist -# If you want to run 32 bit applications on your x86_64 system, -# enable the multilib repositories as required here. - -#[multilib-testing] -#Include = /etc/pacman.d/mirrorlist - [multilib] Include = /etc/pacman.d/mirrorlist -# An example of a custom package repository. See the pacman manpage for -# tips on creating your own repositories. -#[custom] -#SigLevel = Optional TrustAll -#Server = file:///home/custompkgs +[cachyos] +Include = /etc/pacman.d/cachyos-mirrorlist diff --git a/airootfs/etc/pacman.d/mirrorlist b/airootfs/etc/pacman.d/mirrorlist deleted file mode 100644 index 4e41670..0000000 --- a/airootfs/etc/pacman.d/mirrorlist +++ /dev/null @@ -1,11 +0,0 @@ -# CachyOS's Mirror -# Force good mirrors for installation -# Mirrors from india currently being reported as fully synced but they are not -# CachyOS provdes a CDN Cache archlinux mirror -# as fallback an alternative cloudflare mirror will be used -# as third fallback GEO mirrors from archlinux will be used -Server = https://archlinux.cachyos.org/repo/$repo/os/$arch -Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch -Server = https://build-1.cachyos.org/archlinux/$repo/os/$arch -Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch -Server = http://mirror.rackspace.com/archlinux/$repo/os/$arch diff --git a/airootfs/etc/polkit-1/49-nopasswd_global.rules b/airootfs/etc/polkit-1/49-nopasswd_global.rules deleted file mode 100644 index afc8670..0000000 --- a/airootfs/etc/polkit-1/49-nopasswd_global.rules +++ /dev/null @@ -1,9 +0,0 @@ -/* Allow members of the wheel group to execute any actions - * without password authentication, similar to "sudo NOPASSWD:" - */ -polkit.addRule(function(action, subject) { - if (subject.isInGroup("wheel")) { - return polkit.Result.YES; - } -}); - diff --git a/airootfs/etc/polkit-1/rules.d/49-simulationos-live-nopasswd.rules b/airootfs/etc/polkit-1/rules.d/49-simulationos-live-nopasswd.rules new file mode 100644 index 0000000..09bdabf --- /dev/null +++ b/airootfs/etc/polkit-1/rules.d/49-simulationos-live-nopasswd.rules @@ -0,0 +1,10 @@ +/* SimulationOS live medium only. + * Lets the live user run the installer and disk tools without a password + * prompt, because the live account has no password to type. + * Removed from the installed system by simulationos-deloop. + */ +polkit.addRule(function(action, subject) { + if (subject.isInGroup("wheel")) { + return polkit.Result.YES; + } +}); diff --git a/airootfs/etc/polkit-1/rules.d/sddm.conf.d/autologin.conf b/airootfs/etc/polkit-1/rules.d/sddm.conf.d/autologin.conf deleted file mode 100644 index 9acf8bf..0000000 --- a/airootfs/etc/polkit-1/rules.d/sddm.conf.d/autologin.conf +++ /dev/null @@ -1,15 +0,0 @@ -[Autologin] -Relogin=false -Session=hyprland -User=liveuser - -[General] -HaltCommand=/usr/bin/systemctl poweroff -RebootCommand=/usr/bin/systemctl reboot - -[Theme] -Current=breeze - -[Users] -MaximumUid=60000 -MinimumUid=1000 diff --git a/airootfs/etc/resolv.conf b/airootfs/etc/resolv.conf deleted file mode 120000 index 3639662..0000000 --- a/airootfs/etc/resolv.conf +++ /dev/null @@ -1 +0,0 @@ -/run/systemd/resolve/stub-resolv.conf \ No newline at end of file diff --git a/airootfs/etc/sddm.conf.d/10-simulationos.conf b/airootfs/etc/sddm.conf.d/10-simulationos.conf new file mode 100644 index 0000000..4603b79 --- /dev/null +++ b/airootfs/etc/sddm.conf.d/10-simulationos.conf @@ -0,0 +1,9 @@ +[General] +DisplayServer=x11 + +[Theme] +Current=maldives + +[Users] +RememberLastUser=true +RememberLastSession=true diff --git a/airootfs/etc/sddm.conf.d/20-simulationos-live-autologin.conf b/airootfs/etc/sddm.conf.d/20-simulationos-live-autologin.conf new file mode 100644 index 0000000..7f8c9fd --- /dev/null +++ b/airootfs/etc/sddm.conf.d/20-simulationos-live-autologin.conf @@ -0,0 +1,6 @@ +# SimulationOS live medium only. +# Removed from the installed system by simulationos-deloop. +[Autologin] +User=liveuser +Session=hyprland.desktop +Relogin=false diff --git a/airootfs/etc/ssh/sshd_config.d/10-archiso.conf b/airootfs/etc/ssh/sshd_config.d/10-archiso.conf deleted file mode 100644 index 6ea7b41..0000000 --- a/airootfs/etc/ssh/sshd_config.d/10-archiso.conf +++ /dev/null @@ -1,3 +0,0 @@ -# Allow root login using password authentication -PasswordAuthentication yes -PermitRootLogin yes diff --git a/airootfs/etc/ssh/sshd_config.d/10-simulationos.conf b/airootfs/etc/ssh/sshd_config.d/10-simulationos.conf new file mode 100644 index 0000000..3fd92fc --- /dev/null +++ b/airootfs/etc/ssh/sshd_config.d/10-simulationos.conf @@ -0,0 +1,17 @@ +# SimulationOS. +# +# sshd is installed but NOT enabled by default. SimulationOS is a graphical +# desktop medium, not a headless install medium, so there is no reason to +# expose a network service on a system whose live accounts have no password. +# +# Upstream archiso ships "PermitRootLogin yes" + "PasswordAuthentication yes" +# here for unattended/headless installs. That combination is only safe because +# OpenSSH defaults PermitEmptyPasswords to no. SimulationOS does not rely on +# that: root login over SSH is refused outright. +# +# To use SSH from the live session: +# passwd # give the account a real password +# sudo systemctl start sshd +PermitRootLogin no +PasswordAuthentication yes +PermitEmptyPasswords no diff --git a/airootfs/etc/sudoers.d/10-simulationos-live b/airootfs/etc/sudoers.d/10-simulationos-live new file mode 100644 index 0000000..0527127 --- /dev/null +++ b/airootfs/etc/sudoers.d/10-simulationos-live @@ -0,0 +1,4 @@ +# SimulationOS live medium only. +# Removed from the installed system by simulationos-deloop, which restores a +# password-prompting %wheel rule instead. +%wheel ALL=(ALL:ALL) NOPASSWD: ALL diff --git a/airootfs/etc/sudoers.d/g_wheel b/airootfs/etc/sudoers.d/g_wheel deleted file mode 100644 index 7c499c2..0000000 --- a/airootfs/etc/sudoers.d/g_wheel +++ /dev/null @@ -1 +0,0 @@ -%wheel ALL=(ALL) NOPASSWD: ALL diff --git a/airootfs/etc/systemd/network.conf.d/ipv6-privacy-extensions.conf b/airootfs/etc/systemd/network.conf.d/ipv6-privacy-extensions.conf deleted file mode 100644 index 0e9ceb4..0000000 --- a/airootfs/etc/systemd/network.conf.d/ipv6-privacy-extensions.conf +++ /dev/null @@ -1,2 +0,0 @@ -[Network] -IPv6PrivacyExtensions=yes diff --git a/airootfs/etc/systemd/network/20-ethernet.network b/airootfs/etc/systemd/network/20-ethernet.network deleted file mode 100644 index d3a3271..0000000 --- a/airootfs/etc/systemd/network/20-ethernet.network +++ /dev/null @@ -1,24 +0,0 @@ -[Match] -# Matching with "Type=ether" causes issues with containers because it also matches virtual Ethernet interfaces (veth*). -# See https://bugs.archlinux.org/task/70892 -# Instead match by globbing the network interface name. -Name=en* -Name=eth* - -[Link] -RequiredForOnline=routable - -[Network] -DHCP=yes -MulticastDNS=yes - -# systemd-networkd does not set per-interface-type default route metrics -# https://github.com/systemd/systemd/issues/17698 -# Explicitly set route metric, so that Ethernet is preferred over Wi-Fi and Wi-Fi is preferred over mobile broadband. -# Use values from NetworkManager. From nm_device_get_route_metric_default in -# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/core/devices/nm-device.c -[DHCPv4] -RouteMetric=100 - -[IPv6AcceptRA] -RouteMetric=100 diff --git a/airootfs/etc/systemd/network/20-wlan.network b/airootfs/etc/systemd/network/20-wlan.network deleted file mode 100644 index 8b70a95..0000000 --- a/airootfs/etc/systemd/network/20-wlan.network +++ /dev/null @@ -1,20 +0,0 @@ -[Match] -Name=wl* - -[Link] -RequiredForOnline=routable - -[Network] -DHCP=yes -MulticastDNS=yes - -# systemd-networkd does not set per-interface-type default route metrics -# https://github.com/systemd/systemd/issues/17698 -# Explicitly set route metric, so that Ethernet is preferred over Wi-Fi and Wi-Fi is preferred over mobile broadband. -# Use values from NetworkManager. From nm_device_get_route_metric_default in -# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/core/devices/nm-device.c -[DHCPv4] -RouteMetric=600 - -[IPv6AcceptRA] -RouteMetric=600 diff --git a/airootfs/etc/systemd/network/20-wwan.network b/airootfs/etc/systemd/network/20-wwan.network deleted file mode 100644 index 6e1c8dd..0000000 --- a/airootfs/etc/systemd/network/20-wwan.network +++ /dev/null @@ -1,19 +0,0 @@ -[Match] -Name=ww* - -[Link] -RequiredForOnline=routable - -[Network] -DHCP=yes - -# systemd-networkd does not set per-interface-type default route metrics -# https://github.com/systemd/systemd/issues/17698 -# Explicitly set route metric, so that Ethernet is preferred over Wi-Fi and Wi-Fi is preferred over mobile broadband. -# Use values from NetworkManager. From nm_device_get_route_metric_default in -# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/core/devices/nm-device.c -[DHCPv4] -RouteMetric=700 - -[IPv6AcceptRA] -RouteMetric=700 diff --git a/airootfs/etc/systemd/resolved.conf.d/archiso.conf b/airootfs/etc/systemd/resolved.conf.d/archiso.conf deleted file mode 100644 index 636f3bd..0000000 --- a/airootfs/etc/systemd/resolved.conf.d/archiso.conf +++ /dev/null @@ -1,4 +0,0 @@ -# Default systemd-resolved configuration for archiso - -[Resolve] -MulticastDNS=yes diff --git a/airootfs/etc/systemd/system/dbus-fi.w1.wpa_supplicant1.service b/airootfs/etc/systemd/system/dbus-fi.w1.wpa_supplicant1.service deleted file mode 120000 index 1aff68e..0000000 --- a/airootfs/etc/systemd/system/dbus-fi.w1.wpa_supplicant1.service +++ /dev/null @@ -1 +0,0 @@ -/usr/lib/systemd/system/wpa_supplicant.service \ No newline at end of file diff --git a/airootfs/etc/systemd/system/display-manager.service b/airootfs/etc/systemd/system/display-manager.service new file mode 120000 index 0000000..56f1df2 --- /dev/null +++ b/airootfs/etc/systemd/system/display-manager.service @@ -0,0 +1 @@ +/usr/lib/systemd/system/sddm.service \ No newline at end of file diff --git a/airootfs/etc/systemd/system/multi-user.target.wants/sshd.service b/airootfs/etc/systemd/system/multi-user.target.wants/sshd.service deleted file mode 120000 index d21ebd9..0000000 --- a/airootfs/etc/systemd/system/multi-user.target.wants/sshd.service +++ /dev/null @@ -1 +0,0 @@ -/usr/lib/systemd/system/sshd.service \ No newline at end of file diff --git a/airootfs/etc/systemd/system/multi-user.target.wants/systemd-networkd.service b/airootfs/etc/systemd/system/multi-user.target.wants/systemd-networkd.service deleted file mode 120000 index 4c158e6..0000000 --- a/airootfs/etc/systemd/system/multi-user.target.wants/systemd-networkd.service +++ /dev/null @@ -1 +0,0 @@ -/usr/lib/systemd/system/systemd-networkd.service \ No newline at end of file diff --git a/airootfs/etc/systemd/system/multi-user.target.wants/systemd-resolved.service b/airootfs/etc/systemd/system/multi-user.target.wants/systemd-resolved.service deleted file mode 120000 index 4f6ae34..0000000 --- a/airootfs/etc/systemd/system/multi-user.target.wants/systemd-resolved.service +++ /dev/null @@ -1 +0,0 @@ -/usr/lib/systemd/system/systemd-resolved.service \ No newline at end of file diff --git a/airootfs/etc/systemd/system/multi-user.target.wants/wpa_supplicant.service b/airootfs/etc/systemd/system/multi-user.target.wants/wpa_supplicant.service deleted file mode 120000 index 1aff68e..0000000 --- a/airootfs/etc/systemd/system/multi-user.target.wants/wpa_supplicant.service +++ /dev/null @@ -1 +0,0 @@ -/usr/lib/systemd/system/wpa_supplicant.service \ No newline at end of file diff --git a/airootfs/etc/systemd/system/sockets.target.wants/systemd-networkd.socket b/airootfs/etc/systemd/system/sockets.target.wants/systemd-networkd.socket deleted file mode 120000 index 51942c8..0000000 --- a/airootfs/etc/systemd/system/sockets.target.wants/systemd-networkd.socket +++ /dev/null @@ -1 +0,0 @@ -/usr/lib/systemd/system/systemd-networkd.socket \ No newline at end of file diff --git a/airootfs/etc/systemd/user/default.target.wants/pipewire-pulse.service b/airootfs/etc/systemd/user/default.target.wants/pipewire-pulse.service new file mode 120000 index 0000000..f631f69 --- /dev/null +++ b/airootfs/etc/systemd/user/default.target.wants/pipewire-pulse.service @@ -0,0 +1 @@ +/usr/lib/systemd/user/pipewire-pulse.service \ No newline at end of file diff --git a/airootfs/etc/systemd/user/default.target.wants/pipewire.service b/airootfs/etc/systemd/user/default.target.wants/pipewire.service new file mode 120000 index 0000000..47f171d --- /dev/null +++ b/airootfs/etc/systemd/user/default.target.wants/pipewire.service @@ -0,0 +1 @@ +/usr/lib/systemd/user/pipewire.service \ No newline at end of file diff --git a/airootfs/etc/systemd/user/pipewire-session-manager.service b/airootfs/etc/systemd/user/pipewire-session-manager.service new file mode 120000 index 0000000..a092a62 --- /dev/null +++ b/airootfs/etc/systemd/user/pipewire-session-manager.service @@ -0,0 +1 @@ +/usr/lib/systemd/user/wireplumber.service \ No newline at end of file diff --git a/airootfs/etc/systemd/user/pipewire.service.wants/wireplumber.service b/airootfs/etc/systemd/user/pipewire.service.wants/wireplumber.service new file mode 120000 index 0000000..a092a62 --- /dev/null +++ b/airootfs/etc/systemd/user/pipewire.service.wants/wireplumber.service @@ -0,0 +1 @@ +/usr/lib/systemd/user/wireplumber.service \ No newline at end of file diff --git a/airootfs/etc/systemd/user/sockets.target.wants/pipewire-pulse.socket b/airootfs/etc/systemd/user/sockets.target.wants/pipewire-pulse.socket new file mode 120000 index 0000000..45f6209 --- /dev/null +++ b/airootfs/etc/systemd/user/sockets.target.wants/pipewire-pulse.socket @@ -0,0 +1 @@ +/usr/lib/systemd/user/pipewire-pulse.socket \ No newline at end of file diff --git a/airootfs/etc/systemd/user/sockets.target.wants/pipewire.socket b/airootfs/etc/systemd/user/sockets.target.wants/pipewire.socket new file mode 120000 index 0000000..d871d81 --- /dev/null +++ b/airootfs/etc/systemd/user/sockets.target.wants/pipewire.socket @@ -0,0 +1 @@ +/usr/lib/systemd/user/pipewire.socket \ No newline at end of file diff --git a/airootfs/etc/xdg/reflector/reflector.conf b/airootfs/etc/xdg/reflector/reflector.conf deleted file mode 100644 index 7c830d2..0000000 --- a/airootfs/etc/xdg/reflector/reflector.conf +++ /dev/null @@ -1,8 +0,0 @@ -# Reflector configuration file for the systemd service. - ---save /etc/pacman.d/mirrorlist ---ipv4 ---ipv6 ---protocol https ---latest 20 ---sort rate From 5ef8e4feee792fe4e6035f4f6bfba3974bb56874 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:20:57 +0300 Subject: [PATCH 05/33] installer: SimulationOS Calamares configuration and launcher Replaces calamares-online.sh, which referenced /etc/version-tag and a simulationos-keyring that never existed, and which nothing invoked. The config lives at /usr/share/simulationos/calamares and is launched with 'calamares -c'. It cannot live in /etc/calamares: cachyos-calamares owns ~60 files under /etc/calamares/modules and declares no backup= array, and the airootfs overlay is copied BEFORE pacstrap, so that would be a hard file conflict. Verified in the Calamares source that -c overrides appDataDir and that module-config and branding lookup then resolve only inside it. Offline install via unpackfs from /run/archiso/bootmnt/arch/x86_64/airootfs.sfs (derived from install_dir and arch in profiledef.sh). Because unpackfs copies the live filesystem verbatim, the target inherits every live concession. simulationos-deloop strips them and, critically, LOCKS ROOT - the live medium ships root with an empty password, which unpackfs would otherwise carry onto every installed system. It must run before initcpio so the archiso initramfs hook is gone before the initramfs is rebuilt. --- airootfs/usr/local/bin/simulationos-install | 48 +++++++ .../applications/simulationos-install.desktop | 12 ++ .../branding/simulationos/branding.desc | 38 +++++ .../calamares/branding/simulationos/icon.svg | 15 ++ .../calamares/branding/simulationos/logo.svg | 15 ++ .../calamares/branding/simulationos/show.qml | 86 +++++++++++ .../calamares/modules/bootloader.conf | 22 +++ .../calamares/modules/finished.conf | 4 + .../simulationos/calamares/modules/fstab.conf | 14 ++ .../calamares/modules/grubcfg.conf | 9 ++ .../calamares/modules/hwclock.conf | 2 + .../calamares/modules/initcpio.conf | 5 + .../calamares/modules/initcpiocfg.conf | 3 + .../calamares/modules/keyboard.conf | 4 + .../calamares/modules/locale.conf | 4 + .../calamares/modules/localecfg.conf | 3 + .../calamares/modules/machineid.conf | 5 + .../simulationos/calamares/modules/mount.conf | 37 +++++ .../calamares/modules/networkcfg.conf | 4 + .../calamares/modules/packages.conf | 17 +++ .../calamares/modules/partition.conf | 24 ++++ .../calamares/modules/removeuser.conf | 2 + .../calamares/modules/services-systemd.conf | 30 ++++ .../modules/shellprocess_deloop.conf | 9 ++ .../calamares/modules/summary.conf | 2 + .../calamares/modules/umount.conf | 2 + .../calamares/modules/unpackfs.conf | 9 ++ .../simulationos/calamares/modules/users.conf | 40 ++++++ .../calamares/modules/welcome.conf | 19 +++ .../calamares/scripts/simulationos-deloop | 135 ++++++++++++++++++ .../simulationos/calamares/settings.conf | 64 +++++++++ 31 files changed, 683 insertions(+) create mode 100755 airootfs/usr/local/bin/simulationos-install create mode 100644 airootfs/usr/share/applications/simulationos-install.desktop create mode 100644 airootfs/usr/share/simulationos/calamares/branding/simulationos/branding.desc create mode 100644 airootfs/usr/share/simulationos/calamares/branding/simulationos/icon.svg create mode 100644 airootfs/usr/share/simulationos/calamares/branding/simulationos/logo.svg create mode 100644 airootfs/usr/share/simulationos/calamares/branding/simulationos/show.qml create mode 100644 airootfs/usr/share/simulationos/calamares/modules/bootloader.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/finished.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/fstab.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/grubcfg.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/hwclock.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/initcpio.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/initcpiocfg.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/keyboard.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/locale.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/localecfg.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/machineid.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/mount.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/networkcfg.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/packages.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/partition.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/removeuser.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/services-systemd.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/shellprocess_deloop.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/summary.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/umount.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/unpackfs.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/users.conf create mode 100644 airootfs/usr/share/simulationos/calamares/modules/welcome.conf create mode 100755 airootfs/usr/share/simulationos/calamares/scripts/simulationos-deloop create mode 100644 airootfs/usr/share/simulationos/calamares/settings.conf diff --git a/airootfs/usr/local/bin/simulationos-install b/airootfs/usr/local/bin/simulationos-install new file mode 100755 index 0000000..0f31103 --- /dev/null +++ b/airootfs/usr/local/bin/simulationos-install @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# +# SimulationOS graphical installer launcher. +# +# Starts Calamares against SimulationOS's own configuration tree. That tree +# lives at a SimulationOS-owned path rather than /etc/calamares because the +# cachyos-calamares package already owns /etc/calamares/modules/*.conf and has +# no backup= array, so shipping our own files there would be a hard pacstrap +# file conflict. Calamares' -c/--config flag overrides appDataDir, which +# relocates settings.conf, modules/ AND branding/ into that one directory. +# +set -euo pipefail + +CONFIG_DIR="/usr/share/simulationos/calamares" +LOG="${HOME:-/tmp}/simulationos-install.log" + +die() { printf 'simulationos-install: %s\n' "$1" >&2; exit 1; } + +[ -x /usr/bin/calamares ] || die "Calamares is not installed (/usr/bin/calamares missing)." +[ -r "$CONFIG_DIR/settings.conf" ] || die "SimulationOS installer config missing: $CONFIG_DIR/settings.conf" + +# Re-exec with privileges if we are not root yet. +if [ "$(id -u)" -ne 0 ]; then + if command -v pkexec >/dev/null 2>&1; then + exec pkexec env \ + XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \ + WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-}" \ + DISPLAY="${DISPLAY:-}" \ + XDG_CURRENT_DESKTOP="${XDG_CURRENT_DESKTOP:-Hyprland}" \ + QT_QPA_PLATFORM='wayland;xcb' \ + SIMOS_INSTALL_LOG="$LOG" \ + /usr/local/bin/simulationos-install + fi + die "Need root privileges and pkexec is unavailable. Try: sudo -E simulationos-install" +fi + +LOG="${SIMOS_INSTALL_LOG:-$LOG}" + +# Make sure the clock is sane before any signature check happens. +timedatectl set-ntp true 2>/dev/null || true + +{ + printf '===== SimulationOS installation started: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + printf '===== ISO version: %s\n' "$(sed -n 's/^DISTRIB_RELEASE=//p' /etc/simulationos-release 2>/dev/null || echo unknown)" + command -v inxi >/dev/null 2>&1 && inxi -Fxz 2>/dev/null || true +} >>"$LOG" 2>&1 + +exec calamares -c "$CONFIG_DIR" -D6 >>"$LOG" 2>&1 diff --git a/airootfs/usr/share/applications/simulationos-install.desktop b/airootfs/usr/share/applications/simulationos-install.desktop new file mode 100644 index 0000000..6777efc --- /dev/null +++ b/airootfs/usr/share/applications/simulationos-install.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=Install SimulationOS +GenericName=System Installer +Comment=Install SimulationOS to a disk +Exec=/usr/local/bin/simulationos-install +Icon=simulationos +Terminal=false +Categories=System;Settings; +Keywords=install;installer;calamares;simulationos; +StartupNotify=true diff --git a/airootfs/usr/share/simulationos/calamares/branding/simulationos/branding.desc b/airootfs/usr/share/simulationos/calamares/branding/simulationos/branding.desc new file mode 100644 index 0000000..839812c --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/branding/simulationos/branding.desc @@ -0,0 +1,38 @@ +--- +componentName: simulationos + +welcomeStyleCalamares: false +welcomeExpandingLogo: true +windowExpanding: normal +windowSize: 1024px,640px +windowPlacement: center + +sidebar: widget +navigation: widget + +strings: + productName: "SimulationOS" + shortProductName: "SimulationOS" + version: "Alpha" + shortVersion: "Alpha" + versionedName: "SimulationOS Alpha" + shortVersionedName: "SimulationOS Alpha" + bootloaderEntryName: "SimulationOS" + productUrl: "https://github.com/HaiderBassem/SimulationOS" + supportUrl: "https://github.com/HaiderBassem/SimulationOS" + knownIssuesUrl: "https://github.com/HaiderBassem/SimulationOS/issues" + releaseNotesUrl: "https://github.com/HaiderBassem/SimulationOS" + +images: + productLogo: "logo.svg" + productIcon: "icon.svg" + productWelcome: "logo.svg" + +slideshow: "show.qml" +slideshowAPI: 2 + +style: + sidebarBackground: "#14161a" + sidebarText: "#e6e6e6" + sidebarTextSelect: "#1793d1" + sidebarTextHighlight: "#33ccff" diff --git a/airootfs/usr/share/simulationos/calamares/branding/simulationos/icon.svg b/airootfs/usr/share/simulationos/calamares/branding/simulationos/icon.svg new file mode 100644 index 0000000..bf9acb5 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/branding/simulationos/icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/airootfs/usr/share/simulationos/calamares/branding/simulationos/logo.svg b/airootfs/usr/share/simulationos/calamares/branding/simulationos/logo.svg new file mode 100644 index 0000000..bf9acb5 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/branding/simulationos/logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/airootfs/usr/share/simulationos/calamares/branding/simulationos/show.qml b/airootfs/usr/share/simulationos/calamares/branding/simulationos/show.qml new file mode 100644 index 0000000..303c81e --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/branding/simulationos/show.qml @@ -0,0 +1,86 @@ +/* SimulationOS installer slideshow (Calamares slideshow API 2). */ +import QtQuick 2.5 +import calamares.slideshow 1.0 + +Presentation { + id: presentation + + function onActivate() { + presentation.startAutoAdvance(12000); + } + + function onLeave() { + presentation.stopAutoAdvance(); + } + + Slide { + Rectangle { + anchors.fill: parent + color: "#14161a" + Column { + anchors.centerIn: parent + spacing: 18 + Text { + text: "SimulationOS" + color: "#33ccff" + font.pixelSize: 40 + font.bold: true + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + text: "An Arch-based system built around Hyprland." + color: "#e6e6e6" + font.pixelSize: 17 + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + } + + Slide { + Rectangle { + anchors.fill: parent + color: "#14161a" + Column { + anchors.centerIn: parent + spacing: 14 + Text { + text: "Built on the CachyOS kernel" + color: "#33ccff" + font.pixelSize: 30 + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + text: "linux-cachyos, tuned for desktop responsiveness." + color: "#e6e6e6" + font.pixelSize: 16 + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + } + + Slide { + Rectangle { + anchors.fill: parent + color: "#14161a" + Column { + anchors.centerIn: parent + spacing: 14 + Text { + text: "Almost done" + color: "#33ccff" + font.pixelSize: 30 + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + text: "After the reboot, log in at the SimulationOS greeter\nand Hyprland will start automatically." + color: "#e6e6e6" + font.pixelSize: 16 + horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + } +} diff --git a/airootfs/usr/share/simulationos/calamares/modules/bootloader.conf b/airootfs/usr/share/simulationos/calamares/modules/bootloader.conf new file mode 100644 index 0000000..50530e3 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/bootloader.conf @@ -0,0 +1,22 @@ +--- +# SimulationOS installs GRUB on the target. GRUB covers BIOS and UEFI with a +# single code path, which keeps the Alpha's installed-system boot story simple. +# (The ISO itself uses syslinux + systemd-boot; that is a separate concern.) +efiBootLoader: "grub" + +kernel: "/vmlinuz-linux-cachyos" +img: "/initramfs-linux-cachyos.img" +fallback: "/initramfs-linux-cachyos-fallback.img" + +timeout: "5" +bootloaderEntryName: "SimulationOS" +kernelParams: [ "quiet", "loglevel=3" ] + +grubInstall: "grub-install" +grubMkconfig: "grub-mkconfig" +grubCfg: "/boot/grub/grub.cfg" +grubProbe: "grub-probe" +efiBootMgr: "efibootmgr" +efiBootloaderId: "SimulationOS" + +installEFIFallback: true diff --git a/airootfs/usr/share/simulationos/calamares/modules/finished.conf b/airootfs/usr/share/simulationos/calamares/modules/finished.conf new file mode 100644 index 0000000..d117c86 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/finished.conf @@ -0,0 +1,4 @@ +--- +restartNowMode: user-unchecked +restartNowCommand: "systemctl -i reboot" +notifyOnFinished: false diff --git a/airootfs/usr/share/simulationos/calamares/modules/fstab.conf b/airootfs/usr/share/simulationos/calamares/modules/fstab.conf new file mode 100644 index 0000000..687b169 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/fstab.conf @@ -0,0 +1,14 @@ +--- +mountOptions: + default: defaults,noatime + btrfs: defaults,noatime,compress=zstd + xfs: defaults,noatime + f2fs: defaults,noatime + +ssdExtraMountOptions: + ext4: discard + btrfs: discard=async + xfs: discard + +efiMountOptions: defaults,umask=0077 +crypttabOptions: luks diff --git a/airootfs/usr/share/simulationos/calamares/modules/grubcfg.conf b/airootfs/usr/share/simulationos/calamares/modules/grubcfg.conf new file mode 100644 index 0000000..d1e134e --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/grubcfg.conf @@ -0,0 +1,9 @@ +--- +overwrite: false +defaults: + GRUB_TIMEOUT: 5 + GRUB_TIMEOUT_STYLE: "menu" + GRUB_DISTRIBUTOR: "SimulationOS" + GRUB_CMDLINE_LINUX_DEFAULT: "quiet loglevel=3" + GRUB_CMDLINE_LINUX: "" + GRUB_ENABLE_CRYPTODISK: false diff --git a/airootfs/usr/share/simulationos/calamares/modules/hwclock.conf b/airootfs/usr/share/simulationos/calamares/modules/hwclock.conf new file mode 100644 index 0000000..9737b45 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/hwclock.conf @@ -0,0 +1,2 @@ +--- +# No options; writes the hardware clock according to the chosen timezone. diff --git a/airootfs/usr/share/simulationos/calamares/modules/initcpio.conf b/airootfs/usr/share/simulationos/calamares/modules/initcpio.conf new file mode 100644 index 0000000..31589df --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/initcpio.conf @@ -0,0 +1,5 @@ +--- +# Runs `mkinitcpio -P` in the target, regenerating +# /boot/initramfs-linux-cachyos.img (and -fallback) from the target's own +# /etc/mkinitcpio.conf. +kernel: "*" diff --git a/airootfs/usr/share/simulationos/calamares/modules/initcpiocfg.conf b/airootfs/usr/share/simulationos/calamares/modules/initcpiocfg.conf new file mode 100644 index 0000000..fcffd2f --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/initcpiocfg.conf @@ -0,0 +1,3 @@ +--- +# Writes a clean /etc/mkinitcpio.conf for the installed system. +# The archiso drop-in has already been removed by simulationos-deloop. diff --git a/airootfs/usr/share/simulationos/calamares/modules/keyboard.conf b/airootfs/usr/share/simulationos/calamares/modules/keyboard.conf new file mode 100644 index 0000000..ade14d1 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/keyboard.conf @@ -0,0 +1,4 @@ +--- +xOrgConfFileName: "/etc/X11/xorg.conf.d/00-keyboard.conf" +convertedKeymapPath: "/usr/share/kbd/keymaps/xkb" +writeEtcDefaultKeyboard: true diff --git a/airootfs/usr/share/simulationos/calamares/modules/locale.conf b/airootfs/usr/share/simulationos/calamares/modules/locale.conf new file mode 100644 index 0000000..c54e660 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/locale.conf @@ -0,0 +1,4 @@ +--- +# Offline installer: no GeoIP lookup, the user picks a region on the map. +region: "Europe" +zone: "London" diff --git a/airootfs/usr/share/simulationos/calamares/modules/localecfg.conf b/airootfs/usr/share/simulationos/calamares/modules/localecfg.conf new file mode 100644 index 0000000..eb4956f --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/localecfg.conf @@ -0,0 +1,3 @@ +--- +# Writes /etc/locale.gen + /etc/locale.conf in the target and runs locale-gen. +localeGenPath: "/etc/locale.gen" diff --git a/airootfs/usr/share/simulationos/calamares/modules/machineid.conf b/airootfs/usr/share/simulationos/calamares/modules/machineid.conf new file mode 100644 index 0000000..cd1e404 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/machineid.conf @@ -0,0 +1,5 @@ +--- +systemd: true +dbus: true +dbus-symlink: true +entropy-copy: false diff --git a/airootfs/usr/share/simulationos/calamares/modules/mount.conf b/airootfs/usr/share/simulationos/calamares/modules/mount.conf new file mode 100644 index 0000000..43d64c6 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/mount.conf @@ -0,0 +1,37 @@ +--- +extraMounts: + - device: proc + fs: proc + mountPoint: /proc + - device: sys + fs: sysfs + mountPoint: /sys + - device: /dev + mountPoint: /dev + options: [ bind ] + - device: tmpfs + fs: tmpfs + mountPoint: /run + - device: /run/udev + mountPoint: /run/udev + options: [ bind ] + - device: efivarfs + fs: efivarfs + mountPoint: /sys/firmware/efi/efivars + efi: true + +mountOptions: + default: defaults,noatime + btrfs: defaults,noatime,compress=zstd + xfs: defaults,noatime + f2fs: defaults,noatime + +btrfsSubvolumes: + - mountPoint: / + subvolume: /@ + - mountPoint: /home + subvolume: /@home + - mountPoint: /var/log + subvolume: /@log + - mountPoint: /var/cache/pacman/pkg + subvolume: /@pkg diff --git a/airootfs/usr/share/simulationos/calamares/modules/networkcfg.conf b/airootfs/usr/share/simulationos/calamares/modules/networkcfg.conf new file mode 100644 index 0000000..65ddb4f --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/networkcfg.conf @@ -0,0 +1,4 @@ +--- +# Copies NetworkManager system connections created in the live session into +# the installed system, so a Wi-Fi network joined during installation keeps +# working after the first reboot. diff --git a/airootfs/usr/share/simulationos/calamares/modules/packages.conf b/airootfs/usr/share/simulationos/calamares/modules/packages.conf new file mode 100644 index 0000000..56c81a6 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/packages.conf @@ -0,0 +1,17 @@ +--- +backend: pacman + +pacman: + num_retries: 1 + disable_download_timeout: true + needed_only: true + +update_db: false +update_system: false + +operations: + # The installer itself and the live-only partitioning GUI have no place on + # the installed system. try_remove never fails the installation. + - try_remove: + - cachyos-calamares + - gparted diff --git a/airootfs/usr/share/simulationos/calamares/modules/partition.conf b/airootfs/usr/share/simulationos/calamares/modules/partition.conf new file mode 100644 index 0000000..104884e --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/partition.conf @@ -0,0 +1,24 @@ +--- +efiSystemPartition: "/boot/efi" +efiSystemPartitionSize: 512M +efiSystemPartitionName: EFI + +userSwapChoices: + - none + - small + - suspend + - file + +drawNestedPartitions: false +alwaysShowPartitionLabels: true +allowManualPartitioning: true + +initialPartitioningChoice: none +initialSwapChoice: none + +defaultFileSystemType: "ext4" +availableFileSystemTypes: + - "ext4" + - "btrfs" + - "xfs" + - "f2fs" diff --git a/airootfs/usr/share/simulationos/calamares/modules/removeuser.conf b/airootfs/usr/share/simulationos/calamares/modules/removeuser.conf new file mode 100644 index 0000000..97ffbec --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/removeuser.conf @@ -0,0 +1,2 @@ +--- +username: liveuser diff --git a/airootfs/usr/share/simulationos/calamares/modules/services-systemd.conf b/airootfs/usr/share/simulationos/calamares/modules/services-systemd.conf new file mode 100644 index 0000000..58948c2 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/services-systemd.conf @@ -0,0 +1,30 @@ +--- +units: + - name: "NetworkManager.service" + action: "enable" + mandatory: true + - name: "sddm.service" + action: "enable" + mandatory: true + - name: "bluetooth.service" + action: "enable" + mandatory: false + - name: "systemd-timesyncd.service" + action: "enable" + mandatory: false + - name: "fstrim.timer" + action: "enable" + mandatory: false + # Live-medium services that must not run on an installed system. + - name: "pacman-init.service" + action: "disable" + mandatory: false + - name: "livecd-talk.service" + action: "disable" + mandatory: false + - name: "livecd-alsa-unmuter.service" + action: "disable" + mandatory: false + - name: "vboxservice.service" + action: "disable" + mandatory: false diff --git a/airootfs/usr/share/simulationos/calamares/modules/shellprocess_deloop.conf b/airootfs/usr/share/simulationos/calamares/modules/shellprocess_deloop.conf new file mode 100644 index 0000000..a93224f --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/shellprocess_deloop.conf @@ -0,0 +1,9 @@ +--- +dontChroot: false +timeout: 600 +verbose: true +script: + - "/usr/share/simulationos/calamares/scripts/simulationos-deloop" + # Runs only after the script above has fully exited, so the script never + # deletes the file it is executing from. + - "rm -rf /usr/share/simulationos" diff --git a/airootfs/usr/share/simulationos/calamares/modules/summary.conf b/airootfs/usr/share/simulationos/calamares/modules/summary.conf new file mode 100644 index 0000000..9e0fe43 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/summary.conf @@ -0,0 +1,2 @@ +--- +# Shows the list of actions before they are executed. diff --git a/airootfs/usr/share/simulationos/calamares/modules/umount.conf b/airootfs/usr/share/simulationos/calamares/modules/umount.conf new file mode 100644 index 0000000..eb9ce0a --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/umount.conf @@ -0,0 +1,2 @@ +--- +srcLog: "/var/log/Calamares.log" diff --git a/airootfs/usr/share/simulationos/calamares/modules/unpackfs.conf b/airootfs/usr/share/simulationos/calamares/modules/unpackfs.conf new file mode 100644 index 0000000..bd74df8 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/unpackfs.conf @@ -0,0 +1,9 @@ +--- +# Offline install: copy the live SquashFS onto the target. +# Path comes from archiso: the medium is mounted at /run/archiso/bootmnt and +# the image lives at //airootfs.sfs, with +# install_dir="arch" and arch="x86_64" from profiledef.sh. +unpack: + - source: "/run/archiso/bootmnt/arch/x86_64/airootfs.sfs" + sourcefs: "squashfs" + destination: "" diff --git a/airootfs/usr/share/simulationos/calamares/modules/users.conf b/airootfs/usr/share/simulationos/calamares/modules/users.conf new file mode 100644 index 0000000..753ac90 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/users.conf @@ -0,0 +1,40 @@ +--- +defaultGroups: + - name: users + must_exist: true + system: false + - wheel + - audio + - video + - network + - storage + - input + - lp + - scanner + - rfkill + +# The installed system gets a normal login screen; live autologin is not +# carried over. See also sddm.conf.d/20-simulationos-live-autologin.conf, +# which simulationos-deloop deletes from the target. +autologinGroup: autologin +doAutologin: false + +sudoersGroup: wheel +setRootPassword: false +doReusePassword: false + +availableShells: /bin/bash + +passwordRequirements: + nonempty: true + minLength: 6 + +user: + shell: /bin/bash + forbidden_names: + - root + - liveuser + +hostname: + location: EtcFile + writeHostsFile: true diff --git a/airootfs/usr/share/simulationos/calamares/modules/welcome.conf b/airootfs/usr/share/simulationos/calamares/modules/welcome.conf new file mode 100644 index 0000000..563a4d1 --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/modules/welcome.conf @@ -0,0 +1,19 @@ +--- +showSupportUrl: true +showKnownIssuesUrl: true +showReleaseNotesUrl: false +showDonateUrl: false + +requirements: + requiredStorage: 15 + requiredRam: 1.8 + check: + - storage + - ram + - power + - root + - screen + required: + - storage + - ram + - root diff --git a/airootfs/usr/share/simulationos/calamares/scripts/simulationos-deloop b/airootfs/usr/share/simulationos/calamares/scripts/simulationos-deloop new file mode 100755 index 0000000..5fee79d --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/scripts/simulationos-deloop @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# +# simulationos-deloop - turn an unpacked SimulationOS live filesystem into a +# proper installed system. +# +# Runs INSIDE the target chroot, from Calamares' shellprocess@deloop instance, +# after users/removeuser and BEFORE initcpiocfg/initcpio. +# +# Why this exists: the offline installer copies the live SquashFS verbatim, so +# the target initially inherits every live-medium concession - passwordless +# root, NOPASSWD sudo, permissive polkit, SDDM autologin, tty autologin and the +# archiso initramfs hooks. Each of those is removed here. +# +# Deliberately NOT using `set -e`: a missing file must never abort a running +# installation. Every step is individually guarded and logged. +# + +set -u + +log() { printf '[deloop] %s\n' "$*"; } + +drop() { + local path + for path in "$@"; do + if [ -e "$path" ] || [ -L "$path" ]; then + rm -rf -- "$path" && log "removed $path" || log "FAILED to remove $path" + fi + done +} + +log "starting live-configuration removal" + +# ---------------------------------------------------------------- 1. autologin +# SDDM autologin and tty1 autologin are live-medium only. +drop /etc/sddm.conf.d/20-simulationos-live-autologin.conf +drop /etc/systemd/system/getty@tty1.service.d/autologin.conf +rmdir /etc/systemd/system/getty@tty1.service.d 2>/dev/null + +# ------------------------------------------------------- 2. privilege policies +# Replace live NOPASSWD sudo with a normal password-prompting wheel rule. +drop /etc/sudoers.d/10-simulationos-live +drop /etc/sudoers.d/g_wheel +cat > /etc/sudoers.d/10-simulationos-wheel <<'EOSUDO' +## SimulationOS: members of the wheel group may use sudo after authenticating. +%wheel ALL=(ALL:ALL) ALL +EOSUDO +chmod 0440 /etc/sudoers.d/10-simulationos-wheel +chown 0:0 /etc/sudoers.d/10-simulationos-wheel +if command -v visudo >/dev/null 2>&1; then + if visudo -cf /etc/sudoers.d/10-simulationos-wheel >/dev/null 2>&1; then + log "installed password-prompting wheel sudo rule" + else + log "FAILED sudoers validation; removing the new rule to avoid a broken sudo" + rm -f /etc/sudoers.d/10-simulationos-wheel + fi +fi + +# The live polkit rule grants every wheel member every action without a +# password. That must not survive onto a real system. +drop /etc/polkit-1/rules.d/49-simulationos-live-nopasswd.rules + +# ------------------------------------------------------------- 3. root account +# The live medium ships root with an EMPTY password. Calamares is configured +# with setRootPassword:false (sudo-only model), so lock root explicitly. +if passwd --lock root >/dev/null 2>&1; then + log "locked the root account" +else + log "WARNING: could not lock root via passwd; patching /etc/shadow directly" + sed -i 's/^root::/root:!:/' /etc/shadow 2>/dev/null \ + && log "patched /etc/shadow" \ + || log "FAILED to patch /etc/shadow" +fi + +# ------------------------------------------------------------ 4. archiso layer +# This is the load-bearing one: leaving archiso.conf in place makes mkinitcpio +# build an archiso initramfs, and the installed system will not boot without +# the ISO present. +drop /etc/mkinitcpio.conf.d/archiso.conf +rmdir /etc/mkinitcpio.conf.d 2>/dev/null + +drop /etc/systemd/system/livecd-talk.service \ + /etc/systemd/system/livecd-alsa-unmuter.service \ + /etc/systemd/system/multi-user.target.wants/livecd-talk.service \ + /etc/systemd/system/sound.target.wants/livecd-alsa-unmuter.service \ + /etc/systemd/system/pacman-init.service \ + /etc/systemd/system/multi-user.target.wants/pacman-init.service \ + /etc/systemd/system/etc-pacman.d-gnupg.mount \ + /usr/local/bin/livecd-sound \ + /usr/local/share/livecd-sound \ + /root/.automated_script.sh \ + /root/.zlogin + +# archiso masks the GPT auto-generator; an installed system wants it back. +if [ -L /etc/systemd/system-generators/systemd-gpt-auto-generator ]; then + drop /etc/systemd/system-generators/systemd-gpt-auto-generator + rmdir /etc/systemd/system-generators 2>/dev/null +fi + +# Live-only systemd tuning: volatile journal and "never suspend". +drop /etc/systemd/journald.conf.d/volatile-storage.conf +rmdir /etc/systemd/journald.conf.d 2>/dev/null +drop /etc/systemd/logind.conf.d/do-not-suspend.conf +rmdir /etc/systemd/logind.conf.d 2>/dev/null + +# sshd is not enabled on the live medium either, but clear any leftover +# enablement and the upstream archiso drop-in if an older build left one. +drop /etc/systemd/system/multi-user.target.wants/sshd.service \ + /etc/ssh/sshd_config.d/10-archiso.conf + +# ------------------------------------------------------------- 5. the live user +# removeuser has already deleted the account; clear anything left behind. +drop /home/liveuser +for grp in nopasswdlogin autologin; do + if command -v gpasswd >/dev/null 2>&1; then + gpasswd -d liveuser "$grp" >/dev/null 2>&1 + fi +done + +# --------------------------------------------------------------- 6. installer +# The installer must not be offered from an installed system. The +# cachyos-calamares package itself is removed by the `packages` module; +# /usr/share/simulationos is removed by the command after this script. +drop /usr/local/bin/simulationos-install \ + /usr/share/applications/simulationos-install.desktop + +# ------------------------------------------------------------------ 7. banners +# Replace the live console banner with an installed-system one. +cat > /etc/motd <<'EOMOTD' + + SimulationOS + +EOMOTD + +log "finished live-configuration removal" +exit 0 diff --git a/airootfs/usr/share/simulationos/calamares/settings.conf b/airootfs/usr/share/simulationos/calamares/settings.conf new file mode 100644 index 0000000..1d4244b --- /dev/null +++ b/airootfs/usr/share/simulationos/calamares/settings.conf @@ -0,0 +1,64 @@ +--- +# SimulationOS Calamares configuration. +# +# Launched as: calamares -c /usr/share/simulationos/calamares +# The -c flag overrides Calamares' appDataDir, which relocates settings.conf, +# modules/ and branding/ into this directory. This is why SimulationOS does not +# ship anything under /etc/calamares: cachyos-calamares already owns +# /etc/calamares/modules/*.conf and declares no backup= array, so an airootfs +# overlay there would be a hard pacstrap file conflict. +# +# Installation model: OFFLINE. The live SquashFS is unpacked onto the target, +# then live-only configuration is stripped again by the "deloop" shell process. + +modules-search: [ local ] + +instances: +- id: deloop + module: shellprocess + config: shellprocess_deloop.conf + +sequence: +- show: + - welcome + - locale + - keyboard + - partition + - users + - summary +- exec: + - partition + - mount + - unpackfs + - machineid + - fstab + - locale + - keyboard + - localecfg + - users + - networkcfg + - hwclock + - services-systemd + - packages + - removeuser + # deloop MUST run before initcpio: it deletes + # /etc/mkinitcpio.conf.d/archiso.conf, which would otherwise force archiso + # hooks into the installed system's initramfs and make it unbootable + # without the ISO. + - shellprocess@deloop + - initcpiocfg + - initcpio + - grubcfg + - bootloader + - umount +- show: + - finished + +branding: simulationos + +prompt-install: true +dont-chroot: false +oem-setup: false +disable-cancel: false +disable-cancel-during-exec: true +quit-at-end: false From 482851f472b5d66da04e1903cd1cdc36a6e2f84e Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:21:11 +0300 Subject: [PATCH 06/33] branding: SimulationOS identity; remove inherited dead code Identity: os-release (ID=simulationos), issue, motd, hostname, simulationos-release. /etc/hosts previously contained ONLY a hardcoded GitHub IP with no localhost entries. Removed six scripts that nothing invoked and that all depend on CachyOS' chwd, which was never in the package list: dmcheck, prepare-live-desktop.sh, remove-nvidia, removeun, removeun-online, calamares-online.sh. Also removed the chwd-dependent nvidia-module-loader and its modprobe drop-in, the broadcom-wl drop-in (package gone from Arch), choose-mirror (its service was never shipped), Installation_guide, pacman-more.conf, the hand-written CachyOS-forcing mirrorlist (now provided by pacman-mirrorlist + the upstream uncomment hook), and the reflector config for a package that is not installed. Shipping half-working hardware detection is worse than shipping none: NVIDIA falls back to nouveau for the Alpha. --- airootfs/etc/hostname | 2 +- airootfs/etc/hosts | 7 +- airootfs/etc/issue | 3 + airootfs/etc/modprobe.d/broadcom-wl.conf | 7 - airootfs/etc/modprobe.d/nvidia-loader.conf | 6 - airootfs/etc/motd | 23 +-- airootfs/etc/os-release | 9 +- airootfs/etc/pacman-more.conf | 134 ------------------ airootfs/etc/simulationos-release | 3 +- airootfs/usr/local/bin/Installation_guide | 5 - airootfs/usr/local/bin/calamares-online.sh | 95 ------------- airootfs/usr/local/bin/choose-mirror | 30 ---- airootfs/usr/local/bin/dmcheck | 37 ----- airootfs/usr/local/bin/nvidia-module-loader | 35 ----- .../usr/local/bin/prepare-live-desktop.sh | 25 ---- airootfs/usr/local/bin/remove-nvidia | 48 ------- airootfs/usr/local/bin/removeun | 58 -------- airootfs/usr/local/bin/removeun-online | 55 ------- 18 files changed, 32 insertions(+), 550 deletions(-) create mode 100644 airootfs/etc/issue delete mode 100644 airootfs/etc/modprobe.d/broadcom-wl.conf delete mode 100644 airootfs/etc/modprobe.d/nvidia-loader.conf delete mode 100644 airootfs/etc/pacman-more.conf delete mode 100755 airootfs/usr/local/bin/Installation_guide delete mode 100644 airootfs/usr/local/bin/calamares-online.sh delete mode 100755 airootfs/usr/local/bin/choose-mirror delete mode 100644 airootfs/usr/local/bin/dmcheck delete mode 100644 airootfs/usr/local/bin/nvidia-module-loader delete mode 100644 airootfs/usr/local/bin/prepare-live-desktop.sh delete mode 100644 airootfs/usr/local/bin/remove-nvidia delete mode 100644 airootfs/usr/local/bin/removeun delete mode 100644 airootfs/usr/local/bin/removeun-online diff --git a/airootfs/etc/hostname b/airootfs/etc/hostname index ea2d6f7..056011c 100644 --- a/airootfs/etc/hostname +++ b/airootfs/etc/hostname @@ -1 +1 @@ -SimulationOS +simulationos diff --git a/airootfs/etc/hosts b/airootfs/etc/hosts index ed7bc42..26bf5c3 100644 --- a/airootfs/etc/hosts +++ b/airootfs/etc/hosts @@ -1 +1,6 @@ -185.199.108.133 raw.githubusercontent.com +# Static table lookup for hostnames. +# See hosts(5) for details. + +127.0.0.1 localhost +::1 localhost +127.0.1.1 simulationos.localdomain simulationos diff --git a/airootfs/etc/issue b/airootfs/etc/issue new file mode 100644 index 0000000..1ee6d04 --- /dev/null +++ b/airootfs/etc/issue @@ -0,0 +1,3 @@ + +\e[38;2;23;147;209mSimulationOS Alpha\e[0m \s \r (\l) + diff --git a/airootfs/etc/modprobe.d/broadcom-wl.conf b/airootfs/etc/modprobe.d/broadcom-wl.conf deleted file mode 100644 index 0eae70c..0000000 --- a/airootfs/etc/modprobe.d/broadcom-wl.conf +++ /dev/null @@ -1,7 +0,0 @@ -# The broadcom-wl package requires some modules to be disabled in order to use -# wl. Since the ISO image needs to cover many hardware cases, this file -# overrides the default blacklist in /usr/lib/modprobe.d/ -# -# If you need to use wl, you may need to delete this file, then `rmmod` any -# already-loaded modules that are now blacklisted before proceeding to modprobe -# wl itself. diff --git a/airootfs/etc/modprobe.d/nvidia-loader.conf b/airootfs/etc/modprobe.d/nvidia-loader.conf deleted file mode 100644 index 9b7e518..0000000 --- a/airootfs/etc/modprobe.d/nvidia-loader.conf +++ /dev/null @@ -1,6 +0,0 @@ -# Forwards all NVIDIA module load requests to a "smart" loader that selects -# correct version of the module depending on used hardware. -install nvidia /usr/local/bin/nvidia-module-loader -install nvidia_drm /usr/local/bin/nvidia-module-loader -install nvidia_uvm /usr/local/bin/nvidia-module-loader -install nvidia_modeset /usr/local/bin/nvidia-module-loader diff --git a/airootfs/etc/motd b/airootfs/etc/motd index 4d9eda1..6da0b68 100644 --- a/airootfs/etc/motd +++ b/airootfs/etc/motd @@ -1,11 +1,18 @@ -To install Arch Linux follow the installation guide: -https://wiki.archlinux.org/title/Installation_guide -For Wi-Fi, authenticate to the wireless network using the iwctl utility. -For mobile broadband (WWAN) modems, connect with the mmcli utility. -Ethernet, WLAN and WWAN interfaces using DHCP should work automatically. + SimulationOS Alpha - live environment -After connecting to the internet, the installation guide can be accessed -via the convenience script Installation_guide. + The graphical desktop starts automatically. If you are reading this on a + text console, the display manager may have failed; check: + + systemctl status display-manager + journalctl -b -u sddm + + To install SimulationOS to a disk, use "Install SimulationOS" in the + application launcher, or run: + + simulationos-install + + Network: nmtui / nmcli device wifi list + Audio: wpctl status + Logs: journalctl -b -                                           diff --git a/airootfs/etc/os-release b/airootfs/etc/os-release index b3f7ad0..20b6d7f 100644 --- a/airootfs/etc/os-release +++ b/airootfs/etc/os-release @@ -1,10 +1,11 @@ NAME="SimulationOS" -PRETTY_NAME="SimulationOS" -ID=arch +PRETTY_NAME="SimulationOS Alpha" +ID=simulationos +ID_LIKE=arch BUILD_ID=rolling ANSI_COLOR="38;2;23;147;209" HOME_URL="https://github.com/HaiderBassem/SimulationOS" DOCUMENTATION_URL="https://github.com/HaiderBassem/SimulationOS/blob/main/README.md" -SUPPORT_URL="https://github.com/HaiderBassem/SimulationOS" +SUPPORT_URL="https://github.com/HaiderBassem/SimulationOS/issues" BUG_REPORT_URL="https://github.com/HaiderBassem/SimulationOS/issues" -LOGO=SimulationOS \ No newline at end of file +LOGO=simulationos diff --git a/airootfs/etc/pacman-more.conf b/airootfs/etc/pacman-more.conf deleted file mode 100644 index 6292620..0000000 --- a/airootfs/etc/pacman-more.conf +++ /dev/null @@ -1,134 +0,0 @@ -# CachyOS's configration -# /etc/pacman.conf -# -# See the pacman.conf(5) manpage for option and repository directives - -# -# GENERAL OPTIONS -# -[options] -# The following paths are commented out with their default values listed. -# If you wish to use different paths, uncomment and update the paths. -#RootDir = / -#DBPath = /var/lib/pacman/ -#CacheDir = /var/cache/pacman/pkg/ -#LogFile = /var/log/pacman.log -#GPGDir = /etc/pacman.d/gnupg/ -#HookDir = /etc/pacman.d/hooks/ -HoldPkg = pacman glibc -#XferCommand = /usr/bin/curl -L -C - -f -o %o %u -#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u -#CleanMethod = KeepInstalled -#UseDelta = 0.7 -Architecture = auto - -# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup -#IgnorePkg = -#IgnoreGroup = - -#NoUpgrade = -#NoExtract = - -# Misc options -#UseSyslog -Color -ILoveCandy -#NoProgressBar -#CheckSpace -VerbosePkgLists -DisableDownloadTimeout -ParallelDownloads = 10 -DownloadUser = alpm -#DisableSandbox - -# By default, pacman accepts packages signed by keys that its local keyring -# trusts (see pacman-key and its man page), as well as unsigned packages. -SigLevel = Required DatabaseOptional -LocalFileSigLevel = Optional -#RemoteFileSigLevel = Required - -# NOTE: You must run `pacman-key --init` before first using pacman; the local -# keyring can then be populated with the keys of all official Arch Linux -# packagers with `pacman-key --populate archlinux`. - -# -# REPOSITORIES -# - can be defined here or included from another file -# - pacman will search repositories in the order defined here -# - local/custom mirrors can be added here or in separate files -# - repositories listed first will take precedence when packages -# have identical names, regardless of version number -# - URLs will have $repo replaced by the name of the current repo -# - URLs will have $arch replaced by the name of the architecture -# -# Repository entries are of the format: -# [repo-name] -# Server = ServerName -# Include = IncludePath -# -# The header [repo-name] is crucial - it must be present and -# uncommented to enable the repo. -# - -# cachyos repos - -#[cachyos-znver4] -#Include = /etc/pacman.d/cachyos-v4-mirrorlist - -#[cachyos-core-znver4] -#Include = /etc/pacman.d/cachyos-v4-mirrorlist - -#[cachyos-extra-znver4] -#Include = /etc/pacman.d/cachyos-v4-mirrorlist - -#[cachyos-v4] -#Include = /etc/pacman.d/cachyos-v4-mirrorlist - -#[cachyos-core-v4] -#Include = /etc/pacman.d/cachyos-v4-mirrorlist - -#[cachyos-extra-v4] -#Include = /etc/pacman.d/cachyos-v4-mirrorlist - -#[cachyos-v3] -#Include = /etc/pacman.d/cachyos-v3-mirrorlist - -#[cachyos-core-v3] -#Include = /etc/pacman.d/cachyos-v3-mirrorlist - -#[cachyos-extra-v3] -#Include = /etc/pacman.d/cachyos-v3-mirrorlist - -[cachyos] -Include = /etc/pacman.d/cachyos-mirrorlist - -# The testing repositories are disabled by default. To enable, uncomment the -# repo name header and Include lines. You can add preferred servers immediately -# after the header, and they will be used before the default mirrors. - -#[core-testing] -#Include = /etc/pacman.d/mirrorlist - -[core] -Include = /etc/pacman.d/mirrorlist - -#[extra-testing] -#Include = /etc/pacman.d/mirrorlist - -[extra] -Include = /etc/pacman.d/mirrorlist - -# If you want to run 32 bit applications on your x86_64 system, -# enable the multilib repositories as required here. - -#[multilib-testing] -#Include = /etc/pacman.d/mirrorlist - -[multilib] -Include = /etc/pacman.d/mirrorlist - -# An example of a custom package repository. See the pacman manpage for -# tips on creating your own repositories. -#[custom] -#SigLevel = Optional TrustAll -#Server = file:///home/custompkgs diff --git a/airootfs/etc/simulationos-release b/airootfs/etc/simulationos-release index 3b17094..25af5aa 100644 --- a/airootfs/etc/simulationos-release +++ b/airootfs/etc/simulationos-release @@ -1,4 +1,5 @@ LSB_VERSION=1.0 DISTRIB_ID=SimulationOS DISTRIB_RELEASE=rolling -DISTRIB_DESCRIPTION="SimulationOS Linux" +DISTRIB_CODENAME=alpha +DISTRIB_DESCRIPTION="SimulationOS Alpha" diff --git a/airootfs/usr/local/bin/Installation_guide b/airootfs/usr/local/bin/Installation_guide deleted file mode 100755 index 58b97b0..0000000 --- a/airootfs/usr/local/bin/Installation_guide +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -# -# SPDX-License-Identifier: GPL-3.0-or-later - -exec xdg-open 'https://wiki.archlinux.org/title/Installation_guide' diff --git a/airootfs/usr/local/bin/calamares-online.sh b/airootfs/usr/local/bin/calamares-online.sh deleted file mode 100644 index af123d8..0000000 --- a/airootfs/usr/local/bin/calamares-online.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/bash - -Main() { - # Remove current keyring first, to complete initiate it - sudo rm -rf /etc/pacman.d/gnupg - # We are using this, because archlinux is signing the keyring often with a newly created keyring - # This results into a failed installation for the user. - # Installing archlinux-keyring fails due not being correctly signed - # Mitigate this by installing the latest archlinux-keyring on the ISO, before starting the installation - # The issue could also happen, when the installation does rank the mirrors and then a "faulty" mirror gets used - sudo pacman -Sy --noconfirm archlinux-keyring simulationos-keyring - # Also populate the keys, before starting the Installer, to avoid above issue - sudo pacman-key --init - sudo pacman-key --populate archlinux simulationos - # Also use timedatectl to sync the time with the hardware clock - # There has been a bunch of reports, that the keyring was created in the future - # Syncing appears to fix it - timedatectl set-ntp true - - local progname="$(basename "$0")" - local log="/home/liveuser/simulationos-install.log" - local mode="online" # TODO: keep this line for now - - local _efi_check_dir="/sys/firmware/efi" - local _exitcode=2 # by default use grub - - local SYSTEM="SimulationOS" - local BOOTLOADER="" - if [ -d "${_efi_check_dir}" ]; then - _exitcode=$(yad --width 300 --title "Bootloader" \ - --image=gnome-shutdown \ - --button="Grub:2" \ - --button="Systemd-boot(Default):3" \ - --button="Refind:4" \ - --button="AI SDK / Refind:5" \ - --button="Limine:6" \ - --text "Choose Bootloader/Edition:" ; echo $?) - else - _exitcode=$(yad --width 300 --title "Bootloader" \ - --image=gnome-shutdown \ - --button="Grub:2" \ - --button="Limine:6" \ - --text "Choose Bootloader/Edition:" ; echo $?) - fi - - local ISO_VERSION="$(cat /etc/version-tag)" - echo "USING ISO VERSION: ${ISO_VERSION}" - - if [[ "${_exitcode}" -eq 2 ]]; then - BOOTLOADER="GRUB" - echo "USING GRUB!" - # Remove cachyos calamares packages if they exist (optional) - sudo pacman -Rns --noconfirm cachyos-calamares-qt6-next-systemd cachyos-calamares-qt6-next-grub cachyos-calamares-qt6-next-refind cachyos-calamares-qt6-next-limine || true - # Install your calamares grub package or equivalent - # sudo pacman -Sy simulationos-calamares-grub - elif [[ "${_exitcode}" -eq 3 ]]; then - BOOTLOADER="SYSTEMD-BOOT" - echo "USING SYSTEMD-BOOT!" - sudo pacman -Rns --noconfirm cachyos-calamares-qt6-next-grub cachyos-calamares-qt6-next-refind cachyos-calamares-qt6-next-limine || true - # sudo pacman -Sy simulationos-calamares-systemdboot - elif [[ "${_exitcode}" -eq 4 ]]; then - BOOTLOADER="REFIND" - echo "USING REFIND!" - sudo pacman -Rns --noconfirm cachyos-calamares-qt6-next-grub cachyos-calamares-qt6-next-systemd cachyos-calamares-qt6-next-limine || true - # sudo pacman -Sy simulationos-calamares-refind - elif [[ "${_exitcode}" -eq 5 ]]; then - BOOTLOADER="AI-SDK/Refind" - echo "USING AI SDK and Refind!" - sudo pacman -Rns --noconfirm cachyos-calamares-qt6-next-grub cachyos-calamares-qt6-next-systemd cachyos-calamares-qt6-next-refind cachyos-calamares-qt6-next-limine || true - # sudo pacman -Sy simulationos-calamares-ai - elif [[ "${_exitcode}" -eq 6 ]]; then - BOOTLOADER="Limine" - echo "USING Limine" - sudo pacman -Rns --noconfirm cachyos-calamares-qt6-next-grub cachyos-calamares-qt6-next-systemd cachyos-calamares-qt6-next-refind || true - # sudo pacman -Sy simulationos-calamares-limine - else - exit - fi - - # Get Hardware Informations - inxi -F > $log - - cat <> $log -########## $log by $progname -########## Started (UTC): $(date -u "+%x %X") -########## ISO version: $ISO_VERSION -########## System: $SYSTEM -########## Bootloader: $BOOTLOADER -EOF - - sudo cp "/usr/share/calamares/settings_${mode}.conf" /etc/calamares/settings.conf - sudo -E dbus-launch calamares -D6 >> $log & -} - -Main "$@" diff --git a/airootfs/usr/local/bin/choose-mirror b/airootfs/usr/local/bin/choose-mirror deleted file mode 100755 index 3d26fad..0000000 --- a/airootfs/usr/local/bin/choose-mirror +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# -# SPDX-License-Identifier: GPL-3.0-or-later - -get_cmdline() { - local param - for param in $(/etc/pacman.d/mirrorlist < /dev/null ; then - ln -sf /usr/lib/systemd/system/ly.service /etc/systemd/system/display-manager.service -fi -package=sddm -if pacman -Qs $package > /dev/null ; then - ln -sf /usr/lib/systemd/system/sddm.service /etc/systemd/system/display-manager.service -fi -package=gdm -if pacman -Qs $package > /dev/null ; then - ln -sf /usr/lib/systemd/system/gdm.service /etc/systemd/system/display-manager.service -fi -package=lxdm -if pacman -Qs $package > /dev/null ; then - ln -sf /usr/lib/systemd/system/lxdm.service /etc/systemd/system/display-manager.service -fi -package=lightdm -if pacman -Qs $package > /dev/null ; then - ln -sf /usr/lib/systemd/system/lightdm.service /etc/systemd/system/display-manager.service -fi diff --git a/airootfs/usr/local/bin/nvidia-module-loader b/airootfs/usr/local/bin/nvidia-module-loader deleted file mode 100644 index d1558af..0000000 --- a/airootfs/usr/local/bin/nvidia-module-loader +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -# Quick pass if one of the modules is already loaded. -if [[ -e /sys/module/nvidia || -e /sys/module/nouveau ]]; then - exit 0 -fi - -# Using "install" in modprobe seems to result in no dependency resolving for -# modules, so we have to manually set the order in which all modules are loaded -# at once. -load_nvidia_modules() { - local pkgname="$1" - - # Load some basic dependencies of nvidia modules - modprobe video - modprobe drm_ttm_helper - modprobe drm_kms_helper - - for module in nvidia nvidia-modeset nvidia-drm nvidia-uvm; do - modprobe --ignore-install "/usr/share/modules/${pkgname}/${module}.ko.zst" - done -} - -# Load compatible version of NVIDIA modules for current hardware -if chwd --list | awk '{ if($2 == "nouveau" || $2 == "nvidia-dkms-390xx" || $2 == "nvidia-dkms-470xx") f=1 } END {exit (f == 1) ? 0 : 1 }'; then - modprobe nouveau -elif chwd --list | awk '{ if($2 == "nvidia-dkms") f=1 } END {exit (f == 1) ? 0 : 1 }'; then - load_nvidia_modules linux-cachyos-nvidia -elif chwd --list | awk '{ if($2 == "nvidia-open-dkms") f=1 } END {exit (f == 1) ? 0 : 1 }'; then - load_nvidia_modules linux-cachyos-nvidia-open -fi - -# Otherwise, do a fallback to nouveau anyway -if [[ ! -e /sys/module/nouveau && ! -e /sys/module/nvidia ]]; then - modprobe nouveau -fi diff --git a/airootfs/usr/local/bin/prepare-live-desktop.sh b/airootfs/usr/local/bin/prepare-live-desktop.sh deleted file mode 100644 index 2b35714..0000000 --- a/airootfs/usr/local/bin/prepare-live-desktop.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -# borrowed from manjaro livecd - -xdg=$(xdg-user-dir DESKTOP) -src='/usr/share/applications' - -## We don't need .desktop on desktop, -## actually we should remove .desktop or fix the .desktop as it is running with pkexec, -## and using current calamares config (not even running with script to provide logs). -#if [[ -f /usr/bin/calamares ]]; then -# install -Dm755 $src/calamares.desktop \ -# $xdg/calamares.desktop -#fi - -# workaround for glib trash bug (https://bugzilla.gnome.org/show_bug.cgi?id=748248) -userid=$(id -u $USER) -if [ ! -d "/.Trash-$userid" ]; then - sudo mkdir -p /.Trash-$userid/{expunged,files,info} - sudo chown -R $userid /.Trash-$userid -fi - -# mark launchers trusted for XFCE 4.18 -for f in $(ls $xdg/*desktop); do - gio set -t string $f metadata::xfce-exe-checksum "$(sha256sum $f | awk '{print $1}')" -done; diff --git a/airootfs/usr/local/bin/remove-nvidia b/airootfs/usr/local/bin/remove-nvidia deleted file mode 100644 index 88f79b4..0000000 --- a/airootfs/usr/local/bin/remove-nvidia +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -_remove_pacman_package() { - local _pkgname="$1" - pacman -Rsnc "$_pkgname" --noconfirm || true -} - -# remove pkgs installed for VMs -_clean_vm_packages() { - - #remove virtualbox - if pacman -Qi virtualbox-guest-utils &> /dev/null; then - systemctl disable vboxservice.service - _remove_pacman_package virtualbox-guest-utils - fi - - if pacman -Qi virtualbox-guest-utils-nox &> /dev/null; then - systemctl disable vboxservice.service - _remove_pacman_package virtualbox-guest-utils-nox - fi - - #remove vmware - if [ -f /etc/xdg/autostart/vmware-user.desktop ]; then - rm /etc/xdg/autostart/vmware-user.desktop - fi - - if pacman -Qi open-vm-tools &> /dev/null; then - systemctl disable vmtoolsd.service - _remove_pacman_package open-vm-tools - fi - - if [ -f /etc/systemd/system/multi-user.target.wants/vmtoolsd.service ]; then - rm /etc/systemd/system/multi-user.target.wants/vmtoolsd.service - fi - - #remove qemu - if pacman -Qi qemu-guest-agent &> /dev/null; then - systemctl disable qemu-guest-agent.service - _remove_pacman_package qemu-guest-agent - fi -} - -_check_not_running_vm="$(systemd-detect-virt | grep -q 'none'; echo $?)" -if [[ "${_check_not_running_vm}" -eq 0 ]]; then - _clean_vm_packages -fi - -# vim:set ft=bash sw=2 sts=2 et: diff --git a/airootfs/usr/local/bin/removeun b/airootfs/usr/local/bin/removeun deleted file mode 100644 index e68b678..0000000 --- a/airootfs/usr/local/bin/removeun +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -#set -e - -_clean_files() { -local _files_to_remove=( - /usr/local/bin/choose-mirror - /usr/local/bin/prepare-live-desktop.sh - /usr/local/bin/removeun-online - /usr/local/share/livecd-sound -) - - local xx - for xx in "${_files_to_remove[@]}"; do rm -rf "$xx" || true; done -} - -_clean_packages() { -local _packages_to_remove=( - gparted - grsync - cachyos-calamares-grub - cachyos-calamares-systemd - cachyos-calamares-refind - cachyos-calamares - cachyos-calamares-config - edk2-shell - boost-libs - doxygen - expect - gpart - tcpdump - arch-install-scripts - squashfs-tools - extra-cmake-modules - cmake - elinks - yaml-cpp - syslinux - clonezilla - memtest86+ - mkinitcpio-archiso -) - - local _check_nvidia_card="$(chwd --is_nvidia_card | grep -q 'NVIDIA card found!'; echo $?)" - if [[ "${_check_nvidia_card}" -ne 0 ]]; then - echo "No NVIDIA card detected. Removing nvidia drivers" - _packages_to_remove+=(linux-cachyos-nvidia-open nvidia-utils egl-wayland) - fi - - local xx - # @ does one by one to avoid errors in the entire process - # taken from Erik Dubois script - for xx in "${_packages_to_remove[@]}"; do pacman -Rsnc "$xx" --noconfirm; done -} - -_clean_packages -_clean_files - -# vim:set ft=bash sw=2 sts=2 et: diff --git a/airootfs/usr/local/bin/removeun-online b/airootfs/usr/local/bin/removeun-online deleted file mode 100644 index a1df016..0000000 --- a/airootfs/usr/local/bin/removeun-online +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -_clean_packages() { - local _leave_these_packages="base\nbase-devel\ncachyos-keyring\ncachyos-hello\ngrub\nefibootmgr\noctopi\nca-certificates\n" - local _leave_these_basepackages="filesystem\ngcc-libs\nglibc\nglib2\nbash\ncoreutils\nfile\nfindutils\ngawk\ngrep\nprocps-ng\nsed\ntar\ngettext\npciutils\npsmisc\nshadow\nutil-linux\nbzip2\ngzip\nxz\nlicenses\npacman\nsystemd\nsystemd-sysvcompat\niputils\niproute2\n" - local _packages_to_remove=($(comm -23 <(pacman -Qq | sort) <({ pacman -Qqg base-devel; pacman -Qqg cachyos; printf $_leave_these_packages; printf $_leave_these_basepackages; } | sort -u))) - local xx - # @ does one by one to avoid errors in the entire process - # * can be used to treat all packages in one command - for xx in "${_packages_to_remove[@]}"; do pacman -Rs "$xx" --noconfirm; done - - # making sure they are removed -# local _toberemoved_str=$'bash-completion\nxorg-xinit\nxorg-xkill\nxorg-xinput\nxorg-xrandr\nxorg-xrdb\nmkinitcpio-openswap\nalacritty\nkonsole\nkwin-scripts-forceblur\nnetworkmanager-vpnc\nnetworkmanager-qt\nnetworkmanager-openvpn\nwireguard-tools\nopenconnect\nopenvpn\nqt5-xmlpatterns\ndoxygen\ntcpdump\ndmidecode\nkparts\npolkit-qt5\nxorg-server\nxorg-apps\nqt5-tools\nzfs-utils\nlinux-cachyos-zfs\nwget\nvirtualbox-guest-utils\nxf86-video-vesa\nxf86-video-amdgpu\nxf86-video-intel\nnvidia-dkms\nnvidia-utils\nlightdm\nlightdm-gtk-greeter\nlightdm-webkit2-greeter\nbase-devel\nlinux-cachyos-headers\nplasma-browser-integration\nplasma-desktop\nplasma-firewall\nplasma-framework\nplasma-integration\nplasma-nm\nplasma-pa\nplasma-systemmonitor\nplasma-thunderbolt\nplasma-workspace\ndiscover\npackagekit-qt5\nxed\nparole\nristretto\ndolphin\ndolphin-plugins\ncapitaine-cursors\ngtk-engine-murrine\ngnome-themes-extra\nadwaita-icon-theme\nfile-roller\nlibwnck3\nnetwork-manager-applet\ngalculator\ncachy-browser\nkvantum-qt5\nqt5ct\nneofetch\nopen-vm-tools\nxf86-input-vmmouse\nxf86-video-vmware\ncachyos-kde-settings\ncachyos-picom-config\ncachyos-rate-mirrors\ncachyos-kernel-manager\nchwd\noctopi\nreflector-simple\nmeld\nparu\nnordic-theme-git\nlightly-git\ncachyos-nord-kde-theme-git\nchar-white' -# local _toberemoved_packages -# readarray -t _toberemoved_packages <<< "$_toberemoved_str" -# local xxx -# for xxx in "${_toberemoved_packages[@]}"; do pacman -Rs "$xxx" --noconfirm; done - - rm /etc/sddm.conf || true -} - -_pacman_fix() { - -pacman-key --init -pacman-key --populate cachyos -} - -_try_v3() { - /lib/ld-linux-x86-64.so.2 --help | grep "x86-64-v3 (supported, searched)" > /dev/null - - local pacman_conf="/etc/pacman.conf" - local pacman_conf_cachyos="/etc/pacman-more.conf" - local pacman_conf_path_backup="/etc/pacman.conf.bak" - - if [ $? -eq 0 ]; then - echo "x86-64-v3 is supported" - - sed -i 's/Architecture = auto/#Architecture = auto/' $pacman_conf_cachyos - sed -i 's/#//g' $pacman_conf_cachyos - - echo "backup old config" - mv $pacman_conf $pacman_conf_path_backup - - echo "CachyOS -v3 Repo changed" - mv $pacman_conf_cachyos $pacman_conf - - else - echo "x86-64-v3 is not supported" - fi - -} - -_clean_packages -_try_v3 -_pacman_fix From 4d269d3972c11a198bef402dc3dd892f8f32f7d2 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:21:26 +0300 Subject: [PATCH 07/33] tooling: profile validator, build, clean and QEMU test scripts validate-profile.sh catches the class of breakage that otherwise only appears at boot: kernel-name drift between packages.x86_64 and the boot entries, Hyprland launching binaries nobody ships, developer paths, dangling systemd symlinks, missing Calamares module configs, a deloop script that stopped removing the archiso initramfs hook, more than one wallpaper daemon, and shell syntax errors. Runs on bash 3.2 without pacman, so it works on macOS. Verified by fault injection: 10 deliberate regressions introduced into a scratch copy, 10 caught. build-iso.sh refuses to run on a non-Linux or non-x86_64 host, without root, without archiso, or without the CachyOS key trusted - each with the exact remediation - rather than producing a wrong-architecture or unsigned build. It never disables signature verification. --- scripts/build-iso.sh | 112 ++++++++ scripts/clean-build.sh | 52 ++++ scripts/test-iso.sh | 133 ++++++++++ scripts/validate-profile.sh | 492 ++++++++++++++++++++++++++++++++++++ 4 files changed, 789 insertions(+) create mode 100755 scripts/build-iso.sh create mode 100755 scripts/clean-build.sh create mode 100755 scripts/test-iso.sh create mode 100755 scripts/validate-profile.sh diff --git a/scripts/build-iso.sh b/scripts/build-iso.sh new file mode 100755 index 0000000..f98d022 --- /dev/null +++ b/scripts/build-iso.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# +# build-iso.sh - build the SimulationOS ISO with mkarchiso. +# +# Refuses to run in an environment that cannot produce a correct x86_64 ISO +# rather than emitting a broken or wrong-architecture image. +# +set -euo pipefail + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WORK="${SIMOS_WORK_DIR:-$REPO/work}" +OUT="${SIMOS_OUT_DIR:-$REPO/out}" + +# CachyOS signing key, per CachyOS' own cachyos-repo.sh. +CACHYOS_KEY="F3B607488DB35A47" +CACHYOS_MIRROR="https://mirror.cachyos.org/repo/x86_64/cachyos" + +red() { printf '\033[31m%s\033[0m\n' "$*" >&2; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +info() { printf '\033[36m==>\033[0m %s\n' "$*"; } +die() { red "ERROR: $*"; exit 1; } + +# ------------------------------------------------------------------ 1. host +info "Checking build environment" + +[ "$(uname -s)" = "Linux" ] || die "mkarchiso only runs on Linux. This host is $(uname -s). + Build SimulationOS on an Arch Linux x86_64 machine, VM, or container. + See README.md -> Building for a container recipe." + +if [ "$(uname -m)" != "x86_64" ]; then + die "This host is $(uname -m), but SimulationOS targets x86_64. + Building here would produce packages for the wrong architecture. + Use an x86_64 Arch host or an x86_64 container." +fi + +[ "$(id -u)" -eq 0 ] || die "mkarchiso must run as root. Try: sudo $0 $*" + +command -v pacman >/dev/null || die "pacman not found: this is not an Arch-based host." +command -v mkarchiso >/dev/null || die "mkarchiso not found. Install it with: pacman -S archiso" + +# --------------------------------------------------- 2. CachyOS prerequisites +info "Checking CachyOS repository prerequisites" + +if ! pacman -Q cachyos-keyring >/dev/null 2>&1; then + red "The cachyos-keyring package is not installed on this build host." + red "" + red "SimulationOS installs linux-cachyos, cachyos-calamares, ckbcomp and" + red "mkinitcpio-openswap from the CachyOS repository. pacstrap verifies those" + red "signatures against the BUILD HOST keyring, so the key must be trusted here." + red "Package signature verification is never disabled to work around this." + red "" + red "Provision it with:" + red " pacman-key --recv-keys $CACHYOS_KEY --keyserver keyserver.ubuntu.com" + red " pacman-key --lsign-key $CACHYOS_KEY" + red " pacman -U $CACHYOS_MIRROR/cachyos-keyring-20240331-1-any.pkg.tar.zst" + exit 1 +fi + +if ! pacman-key --list-keys "$CACHYOS_KEY" >/dev/null 2>&1; then + die "cachyos-keyring is installed but key $CACHYOS_KEY is not in the pacman keyring. + Run: pacman-key --populate cachyos" +fi +green " CachyOS keyring present and key $CACHYOS_KEY is trusted" + +# The profile's pacman.conf pins explicit CachyOS Server= URLs, so no +# /etc/pacman.d/cachyos-mirrorlist is needed on the host. Confirm the repo +# actually resolves before spending an hour on a build. +info "Verifying the CachyOS repository resolves" +if ! pacman -Sp --config "$REPO/pacman.conf" linux-cachyos >/dev/null 2>&1; then + die "Cannot resolve 'linux-cachyos' through $REPO/pacman.conf. + Check network access to $CACHYOS_MIRROR and that the [cachyos] section is intact." +fi +green " linux-cachyos resolves: $(pacman -Sp --config "$REPO/pacman.conf" linux-cachyos 2>/dev/null | head -1)" + +# ------------------------------------------------------------ 3. static checks +info "Running profile validation" +if ! "$REPO/scripts/validate-profile.sh"; then + die "validate-profile.sh failed. Fix the errors above before building." +fi + +# ------------------------------------------------------------------ 4. workdir +if [ -d "$WORK" ]; then + if [ "${SIMOS_REUSE_WORK:-0}" = "1" ]; then + info "Reusing existing work directory $WORK (SIMOS_REUSE_WORK=1)" + else + info "Clearing stale work directory $WORK" + "$REPO/scripts/clean-build.sh" --keep-iso + fi +fi +mkdir -p -- "$WORK" "$OUT" + +# -------------------------------------------------------------------- 5. build +info "Running mkarchiso (this takes a while and needs ~20 GB free)" +set -x +mkarchiso -v -w "$WORK" -o "$OUT" "$REPO" +set +x + +# ------------------------------------------------------------- 6/7. artifact +ISO="$(ls -1t "$OUT"/simulationos-*.iso 2>/dev/null | head -1)" +[ -n "$ISO" ] || die "mkarchiso finished but no ISO was produced in $OUT" + +info "Generating SHA256 checksum" +( cd "$OUT" && sha256sum "$(basename "$ISO")" > "$(basename "$ISO").sha256" ) + +green "" +green "Build complete" +green " ISO: $ISO" +green " Size: $(du -h "$ISO" | cut -f1)" +green " SHA256: $(cut -d' ' -f1 < "$ISO.sha256")" +green " Checksum: $ISO.sha256" +green "" +green "Boot it with: ./scripts/test-iso.sh \"$ISO\"" diff --git a/scripts/clean-build.sh b/scripts/clean-build.sh new file mode 100755 index 0000000..34e7293 --- /dev/null +++ b/scripts/clean-build.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# +# clean-build.sh - remove SimulationOS build artefacts. +# +# Only ever touches ./work and ./out inside the repository; it refuses to +# delete anything outside it. +# +set -euo pipefail + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WORK="${SIMOS_WORK_DIR:-$REPO/work}" +OUT="${SIMOS_OUT_DIR:-$REPO/out}" + +KEEP_ISO=0 +[ "${1:-}" = "--keep-iso" ] && KEEP_ISO=1 + +info() { printf '\033[36m==>\033[0m %s\n' "$*"; } + +# Guard rail: never operate outside the repository, however the env vars are set. +for target in "$WORK" "$OUT"; do + case "$target" in + "$REPO"/*) ;; + *) printf 'refusing to delete %s: outside the repository\n' "$target" >&2; exit 1 ;; + esac +done + +# mkarchiso leaves bind mounts behind if a build was interrupted; unmount them +# before deleting, otherwise the delete can reach into a mounted filesystem. +if [ -d "$WORK" ] && command -v findmnt >/dev/null 2>&1; then + findmnt -rno TARGET --submounts "$WORK" 2>/dev/null | sort -r | while read -r mp; do + info "Unmounting leftover mount $mp" + umount -l -- "$mp" 2>/dev/null || true + done +fi + +if [ -d "$WORK" ]; then + info "Removing work directory $WORK ($(du -sh "$WORK" 2>/dev/null | cut -f1))" + rm -rf -- "$WORK" +else + info "No work directory to remove" +fi + +if [ "$KEEP_ISO" -eq 1 ]; then + info "Keeping $OUT (--keep-iso)" +elif [ -d "$OUT" ]; then + info "Removing output directory $OUT" + rm -rf -- "$OUT" +else + info "No output directory to remove" +fi + +info "Clean" diff --git a/scripts/test-iso.sh b/scripts/test-iso.sh new file mode 100755 index 0000000..80fc2b3 --- /dev/null +++ b/scripts/test-iso.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# +# test-iso.sh - boot a SimulationOS ISO in QEMU. +# +# Defaults to UEFI (OVMF) because that is the primary target; --bios exercises +# the legacy syslinux path. --install attaches a blank disk so a full +# installation can be performed, and --boot-disk then boots ONLY that disk, +# which is the real acceptance test for the installed system. +# +set -euo pipefail + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="${SIMOS_OUT_DIR:-$REPO/out}" + +FIRMWARE="uefi" +ISO="" +DISK="$OUT/simulationos-test.qcow2" +DISK_SIZE="${SIMOS_TEST_DISK_SIZE:-30G}" +ATTACH_DISK=0 +BOOT_DISK_ONLY=0 +RAM="${SIMOS_TEST_RAM:-4096}" +CPUS="${SIMOS_TEST_CPUS:-2}" + +usage() { + cat <<'EOF' +usage: test-iso.sh [options] [path/to.iso] + + --uefi boot via OVMF (default) + --bios boot via legacy BIOS / syslinux + --install attach a blank virtual disk for an installation run + --boot-disk boot ONLY the virtual disk (no ISO attached) - verifies + the installed system boots independently of the medium + --ram MB guest memory (default 4096) + --cpus N guest vCPUs (default 2) + +If no ISO path is given, the newest out/simulationos-*.iso is used. + +Typical acceptance run: + ./scripts/test-iso.sh --install # boot ISO, install to the blank disk + ./scripts/test-iso.sh --boot-disk # reboot from the disk alone +EOF +} + +while [ $# -gt 0 ]; do + case "$1" in + --uefi) FIRMWARE="uefi" ;; + --bios) FIRMWARE="bios" ;; + --install) ATTACH_DISK=1 ;; + --boot-disk) BOOT_DISK_ONLY=1; ATTACH_DISK=1 ;; + --ram) RAM="$2"; shift ;; + --cpus) CPUS="$2"; shift ;; + -h|--help) usage; exit 0 ;; + *) ISO="$1" ;; + esac + shift +done + +die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; } + +command -v qemu-system-x86_64 >/dev/null \ + || die "qemu-system-x86_64 not found. Install qemu-full (Arch) or qemu-system-x86 (Debian/Fedora)." + +if [ "$BOOT_DISK_ONLY" -eq 0 ]; then + [ -n "$ISO" ] || ISO="$(ls -1t "$OUT"/simulationos-*.iso 2>/dev/null | head -1 || true)" + [ -n "$ISO" ] || die "No ISO given and none found in $OUT. Run scripts/build-iso.sh first." + [ -f "$ISO" ] || die "ISO not found: $ISO" +fi + +# virtio-vga-gl + gl=on gives Hyprland a GPU to render on. Without it the +# session falls back to llvmpipe, which works but is slow. +# shellcheck disable=SC2054 # commas are part of QEMU option values, not separators +QEMU=(qemu-system-x86_64 + -machine q35,accel=kvm:tcg + -cpu max + -smp "$CPUS" + -m "$RAM" + -device virtio-vga-gl + -display gtk,gl=on + -device intel-hda -device hda-duplex + -device virtio-net-pci,netdev=n0 -netdev user,id=n0 + -device qemu-xhci -device usb-tablet) + +if [ "$FIRMWARE" = "uefi" ]; then + OVMF_CODE="" + for c in /usr/share/edk2/x64/OVMF_CODE.4m.fd \ + /usr/share/edk2/x64/OVMF_CODE.fd \ + /usr/share/edk2-ovmf/x64/OVMF_CODE.fd \ + /usr/share/OVMF/OVMF_CODE_4M.fd \ + /usr/share/OVMF/OVMF_CODE.fd; do + [ -f "$c" ] && { OVMF_CODE="$c"; break; } + done + [ -n "$OVMF_CODE" ] \ + || die "OVMF firmware not found. Install 'edk2-ovmf' (Arch) or 'ovmf' (Debian/Fedora), or use --bios." + + OVMF_VARS_SRC="" + for v in /usr/share/edk2/x64/OVMF_VARS.4m.fd \ + /usr/share/edk2/x64/OVMF_VARS.fd \ + /usr/share/edk2-ovmf/x64/OVMF_VARS.fd \ + /usr/share/OVMF/OVMF_VARS_4M.fd \ + /usr/share/OVMF/OVMF_VARS.fd; do + [ -f "$v" ] && { OVMF_VARS_SRC="$v"; break; } + done + [ -n "$OVMF_VARS_SRC" ] || die "OVMF variable template not found alongside $OVMF_CODE." + + mkdir -p "$OUT" + OVMF_VARS="$OUT/OVMF_VARS.simulationos.fd" + # A per-test writable copy, so UEFI boot entries written by the installer + # persist across runs (needed for the --boot-disk acceptance test). + [ -f "$OVMF_VARS" ] || cp "$OVMF_VARS_SRC" "$OVMF_VARS" + + QEMU+=(-drive "if=pflash,format=raw,unit=0,readonly=on,file=$OVMF_CODE" + -drive "if=pflash,format=raw,unit=1,file=$OVMF_VARS") +fi + +if [ "$ATTACH_DISK" -eq 1 ]; then + if [ ! -f "$DISK" ]; then + printf '==> Creating blank test disk %s (%s)\n' "$DISK" "$DISK_SIZE" + mkdir -p "$OUT" + qemu-img create -f qcow2 "$DISK" "$DISK_SIZE" >/dev/null + fi + QEMU+=(-drive "file=$DISK,if=virtio,format=qcow2") +fi + +if [ "$BOOT_DISK_ONLY" -eq 1 ]; then + printf '==> Booting ONLY the installed disk, no ISO attached\n' + QEMU+=(-boot order=c) +else + QEMU+=(-drive "file=$ISO,media=cdrom,readonly=on" -boot order=d) + printf '==> Booting %s (%s)\n' "$(basename "$ISO")" "$FIRMWARE" +fi + +printf '==> %s\n' "${QEMU[*]}" +exec "${QEMU[@]}" diff --git a/scripts/validate-profile.sh b/scripts/validate-profile.sh new file mode 100755 index 0000000..cd40c5d --- /dev/null +++ b/scripts/validate-profile.sh @@ -0,0 +1,492 @@ +#!/usr/bin/env bash +# +# validate-profile.sh - static consistency checks for the SimulationOS profile. +# +# Catches the class of breakage that only shows up hours into a build or, worse, +# at boot: boot entries pointing at a kernel that is not installed, desktop +# configs launching binaries nobody ships, personal paths, dangling symlinks, +# Calamares referring to modules or scripts that do not exist. +# +# Runs anywhere with bash 3.2+ (macOS included) - it never needs pacman. +# Exit status: 0 = clean, 1 = errors found. +# +set -u + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO" || exit 1 + +ERRORS=0 +WARNINGS=0 +CHECKS=0 + +red() { printf '\033[31m%s\033[0m\n' "$*"; } +yellow(){ printf '\033[33m%s\033[0m\n' "$*"; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } + +err() { ERRORS=$((ERRORS+1)); red " ERROR $*"; } +warn() { WARNINGS=$((WARNINGS+1)); yellow " WARN $*"; } +ok() { CHECKS=$((CHECKS+1)); } +section() { printf '\n== %s\n' "$*"; } + +# Packages the profile installs (comments and blanks stripped). +PKGLIST="$(grep -vE '^[[:space:]]*(#|$)' packages.x86_64 2>/dev/null)" +has_pkg() { printf '%s\n' "$PKGLIST" | grep -qx -- "$1"; } + +# --------------------------------------------------------------------------- +section "profiledef.sh" +# --------------------------------------------------------------------------- +if [ ! -f profiledef.sh ]; then + err "profiledef.sh is missing" +else + bash -n profiledef.sh 2>/dev/null || err "profiledef.sh has a syntax error" + ok + + # Evaluate declarations only - comments legitimately mention bootmode names. + PROFILE_CODE="$(sed 's/[[:space:]]*#.*$//' profiledef.sh)" + + # Deprecated / removed archiso boot mode names. + for bad in bios.syslinux.mbr bios.syslinux.eltorito \ + uefi-x64.systemd-boot.esp uefi-x64.systemd-boot.eltorito \ + uefi-ia32.systemd-boot.esp uefi-ia32.systemd-boot.eltorito \ + uefi-x64.grub.esp uefi-x64.grub.eltorito \ + uefi-ia32.grub.esp uefi-ia32.grub.eltorito; do + if printf '%s\n' "$PROFILE_CODE" | grep -q "'$bad'"; then + err "profiledef.sh uses deprecated bootmode '$bad'" + fi + done + ok + + # mkarchiso rejects these two together. + if printf '%s\n' "$PROFILE_CODE" | grep -q "'uefi.systemd-boot'" \ + && printf '%s\n' "$PROFILE_CODE" | grep -q "'uefi.grub'"; then + err "bootmodes uefi.systemd-boot and uefi.grub cannot be combined" + fi + ok + + # Variables mkarchiso does not consume. + for fake in iso_icon profiles_description install_dir_disktop kernel_parameters; do + printf '%s\n' "$PROFILE_CODE" | grep -qE "^[[:space:]]*${fake}=" \ + && err "profiledef.sh sets '$fake', which mkarchiso does not read" + done + ok + + # grub/grub.cfg is only consumed by the uefi.grub bootmode. + if [ -f grub/grub.cfg ] && ! printf '%s\n' "$PROFILE_CODE" | grep -q "'uefi.grub'"; then + err "grub/grub.cfg exists but 'uefi.grub' is not a bootmode (dead file)" + fi + ok + + # file_permissions entries must point at files that exist. + sed -n 's/^[[:space:]]*\["\([^"]*\)"\].*/\1/p' profiledef.sh | while read -r fp; do + [ -z "$fp" ] && continue + if [ ! -e "airootfs${fp}" ] && [ ! -L "airootfs${fp}" ]; then + red " ERROR file_permissions references missing path: airootfs${fp}" + echo x >>"$REPO/.validate_fperm_fail" + fi + done + if [ -f .validate_fperm_fail ]; then + ERRORS=$((ERRORS + $(wc -l < .validate_fperm_fail))) + rm -f .validate_fperm_fail + fi + ok +fi + +# --------------------------------------------------------------------------- +section "kernel / boot consistency" +# --------------------------------------------------------------------------- +# Derive the kernel pkgbase from the package list, then require that every +# boot entry references exactly that kernel's files. +KPKG="" +for cand in $(printf '%s\n' "$PKGLIST" \ + | grep -E '^linux(-[a-z0-9]+)*$' \ + | grep -vE '^linux-(firmware|api-headers|atm)' \ + | grep -vE -- '-headers$'); do + KPKG="$cand" +done +if [ -z "$KPKG" ]; then + err "no kernel package (linux*) found in packages.x86_64" +else + green " kernel package: $KPKG" + VMLINUZ="vmlinuz-${KPKG}" + INITRAMFS="initramfs-${KPKG}.img" + + BOOTFILES="efiboot/loader/entries/*.conf syslinux/archiso_sys-linux.cfg syslinux/archiso_pxe-linux.cfg grub/loopback.cfg" + for f in $BOOTFILES; do + [ -f "$f" ] || continue + # Any vmlinuz-* reference that is not our kernel is a mismatch. + bad="$(grep -oE 'vmlinuz-[A-Za-z0-9._-]+' "$f" | grep -v "^${VMLINUZ}$" | sort -u)" + if [ -n "$bad" ]; then + err "$f references kernel image(s) [$(echo "$bad" | tr '\n' ' ')] but the profile installs $VMLINUZ" + fi + bad="$(grep -oE 'initramfs-[A-Za-z0-9._-]+\.img' "$f" | grep -vE "^initramfs-${KPKG}(-fallback)?\.img$" | sort -u)" + if [ -n "$bad" ]; then + err "$f references initramfs [$(echo "$bad" | tr '\n' ' ')] but the profile installs $INITRAMFS" + fi + done + ok + + # Custom mkinitcpio presets are a classic source of kernel-name drift. + if [ -d airootfs/etc/mkinitcpio.d ]; then + err "airootfs/etc/mkinitcpio.d exists; upstream archiso removed custom presets (the auto-generated ${KPKG}.preset is correct)" + fi + ok + + # systemd-boot default entry must exist. + if [ -f efiboot/loader/loader.conf ]; then + DEF="$(sed -n 's/^default[[:space:]]\+//p' efiboot/loader/loader.conf | head -1)" + if [ -n "$DEF" ] && [ ! -f "efiboot/loader/entries/$DEF" ]; then + err "loader.conf default entry '$DEF' does not exist in efiboot/loader/entries/" + fi + fi + ok + + # archiso hard requirements. + has_pkg mkinitcpio || err "packages.x86_64 is missing mandatory package 'mkinitcpio'" + has_pkg mkinitcpio-archiso || err "packages.x86_64 is missing mandatory package 'mkinitcpio-archiso'" + ok +fi + +# --------------------------------------------------------------------------- +section "packages.x86_64" +# --------------------------------------------------------------------------- +DUPES="$(printf '%s\n' "$PKGLIST" | sort | uniq -d)" +[ -n "$DUPES" ] && err "duplicate package entries: $(echo "$DUPES" | tr '\n' ' ')" +ok + +# Packages known to have been dropped from the Arch repositories. +for gone in broadcom-wl rofi-wayland libva-mesa-driver ckbcomp-nonfree; do + has_pkg "$gone" && err "packages.x86_64 lists '$gone', which no longer exists in the Arch repositories" +done +ok + +# A graphical profile needs these to reach a desktop at all. +for need in hyprland sddm networkmanager pipewire wireplumber xdg-desktop-portal-hyprland polkit; do + has_pkg "$need" || err "packages.x86_64 is missing '$need', required for the graphical live session" +done +ok + +# --------------------------------------------------------------------------- +section "developer-specific and stale paths" +# --------------------------------------------------------------------------- +SCAN="$(find airootfs syslinux efiboot grub -type f 2>/dev/null)" +for pat in '/mnt/m\.2' '/mnt/[a-z0-9_]*ssd' '/Users/' '/home/[a-z]*/\.config' 'ml4w' 'grimblast' 'auto-cpufreq' 'trash-put'; do + for f in $SCAN; do + # Strip comments first: naming a tool in prose is not a dependency. + if sed -e 's/[[:space:]]*#.*$//' -e 's|[[:space:]]*//.*$||' "$f" 2>/dev/null \ + | grep -qE "$pat"; then + err "developer-specific reference '$pat' in: $f" + fi + done +done +ok + +# Stale upstream branding in user-visible boot menus. +HITS="$(grep -rl 'Arch Linux install medium' efiboot syslinux grub 2>/dev/null)" +[ -n "$HITS" ] && err "stale Arch branding in boot menu: $(echo "$HITS" | tr '\n' ' ')" +HITS="$(grep -rl 'CachyOS' airootfs/etc/os-release airootfs/etc/issue airootfs/etc/motd \ + airootfs/etc/simulationos-release efiboot syslinux grub 2>/dev/null)" +[ -n "$HITS" ] && err "CachyOS branding on a SimulationOS identity surface: $(echo "$HITS" | tr '\n' ' ')" +ok + +# A black screen on first boot is indistinguishable from a broken session. +if grep -rq 'brightnessctl set 0%' airootfs 2>/dev/null; then + err "'brightnessctl set 0%' would blank the display on first login" +fi +ok + +# --------------------------------------------------------------------------- +section "Hyprland session" +# --------------------------------------------------------------------------- +HYPR=airootfs/etc/skel/.config/hypr/hyprland.conf +if [ ! -f "$HYPR" ]; then + err "$HYPR is missing" +else + # Every sourced file must exist. + grep -E '^[[:space:]]*source[[:space:]]*=' "$HYPR" | sed 's/.*=[[:space:]]*//' | while read -r src; do + rel="$(printf '%s' "$src" | sed 's|^~/.config/|airootfs/etc/skel/.config/|')" + if [ ! -f "$rel" ]; then + red " ERROR hyprland.conf sources missing file: $src" + echo x >>"$REPO/.validate_src_fail" + fi + done + if [ -f .validate_src_fail ]; then + ERRORS=$((ERRORS + $(wc -l < .validate_src_fail))); rm -f .validate_src_fail + fi + ok + + # Every exec-once / exec target must be a shipped package binary or a + # script we ship in airootfs/usr/local/bin. + CMDS="$(grep -E '^[[:space:]]*(exec-once|exec)[[:space:]]*=' "$HYPR" \ + | sed 's/.*=[[:space:]]*//' | awk '{print $1}' | sort -u)" + # Map binary -> providing package for the ones that differ in name. + for cmd in $CMDS; do + case "$cmd" in + dbus-update-activation-environment|systemctl) continue ;; # from dbus/systemd in base + esac + if [ -f "airootfs/usr/local/bin/$cmd" ]; then continue; fi + prov="$cmd" + case "$cmd" in + nm-applet) prov=network-manager-applet ;; + blueman-applet) prov=blueman ;; + wl-paste) prov=wl-clipboard ;; + wpctl) prov=wireplumber ;; + esac + has_pkg "$prov" || err "hyprland.conf runs '$cmd' but package '$prov' is not in packages.x86_64" + done + ok + + # Keybind targets that are our own scripts must exist. + for s in simos-screenshot simos-powermenu simos-clipboard; do + if grep -q "$s" "$HYPR"; then + [ -x "airootfs/usr/local/bin/$s" ] \ + || err "hyprland.conf binds '$s' but airootfs/usr/local/bin/$s is missing or not executable" + fi + done + ok +fi + +# Wallpaper referenced by hyprpaper must exist. +HP=airootfs/etc/skel/.config/hypr/hyprpaper.conf +if [ -f "$HP" ]; then + grep -E '^[[:space:]]*(preload|wallpaper)[[:space:]]*=' "$HP" \ + | sed 's/.*[=,][[:space:]]*//' | grep '^/' | sort -u | while read -r img; do + if [ ! -f "airootfs${img}" ]; then + red " ERROR hyprpaper references missing image: $img" + echo x >>"$REPO/.validate_wp_fail" + fi + done + if [ -f .validate_wp_fail ]; then + ERRORS=$((ERRORS + $(wc -l < .validate_wp_fail))); rm -f .validate_wp_fail + fi +fi +ok + +# Exactly one wallpaper daemon. +WPD=0 +for d in hyprpaper swww swaybg wpaperd; do + grep -rqE "exec-once[[:space:]]*=[[:space:]]*$d" airootfs/etc/skel/.config/hypr 2>/dev/null && WPD=$((WPD+1)) +done +[ "$WPD" -gt 1 ] && err "more than one wallpaper daemon is started from the Hyprland config" +ok + +# --------------------------------------------------------------------------- +section "display manager / session" +# --------------------------------------------------------------------------- +DM=airootfs/etc/systemd/system/display-manager.service +if [ -L "$DM" ]; then + TGT="$(readlink "$DM")" + case "$TGT" in + */sddm.service) has_pkg sddm || err "display-manager.service points at sddm but sddm is not installed" ;; + */gdm.service) has_pkg gdm || err "display-manager.service points at gdm but gdm is not installed" ;; + *) warn "display-manager.service points at an unrecognised unit: $TGT" ;; + esac +else + err "airootfs/etc/systemd/system/display-manager.service is missing (no display manager will start)" +fi +ok + +if [ -L airootfs/etc/systemd/system/default.target ]; then + case "$(readlink airootfs/etc/systemd/system/default.target)" in + */graphical.target) ;; + *) err "default.target does not point at graphical.target" ;; + esac +else + err "airootfs/etc/systemd/system/default.target is missing" +fi +ok + +# Dangling systemd .wants symlinks inside airootfs must at least name a unit +# path that a shipped package could provide. +find airootfs/etc/systemd -type l 2>/dev/null | while read -r link; do + tgt="$(readlink "$link")" + case "$tgt" in + /usr/lib/systemd/*|/dev/null) + ;; + /etc/systemd/system/*) + # absolute link into our own overlay - the unit must be shipped + [ -f "airootfs${tgt}" ] || { red " ERROR $link -> $tgt (unit not shipped)"; echo x >>"$REPO/.validate_sl_fail"; } + ;; + /*) + red " ERROR systemd symlink escapes to an unexpected path: $link -> $tgt" + echo x >>"$REPO/.validate_sl_fail" + ;; + *) + # relative link - must resolve inside airootfs + [ -e "$(dirname "$link")/$tgt" ] || { red " ERROR dangling systemd symlink $link -> $tgt"; echo x >>"$REPO/.validate_sl_fail"; } + ;; + esac +done +if [ -f .validate_sl_fail ]; then + ERRORS=$((ERRORS + $(wc -l < .validate_sl_fail))); rm -f .validate_sl_fail +fi +ok + +# Only one network stack may be enabled. +NMW=airootfs/etc/systemd/system/multi-user.target.wants/NetworkManager.service +NDW=airootfs/etc/systemd/system/multi-user.target.wants/systemd-networkd.service +if { [ -e "$NDW" ] || [ -L "$NDW" ]; } && { [ -e "$NMW" ] || [ -L "$NMW" ]; }; then + err "both systemd-networkd and NetworkManager are enabled" +fi +{ [ -e "$NMW" ] || [ -L "$NMW" ]; } \ + || err "NetworkManager.service is not enabled in the live environment" +ok + +# --------------------------------------------------------------------------- +section "Calamares installer" +# --------------------------------------------------------------------------- +CALDIR=airootfs/usr/share/simulationos/calamares +if [ ! -f "$CALDIR/settings.conf" ]; then + err "$CALDIR/settings.conf is missing" +else + # Modules that legitimately need no .conf. + CONFIGLESS="umount" + MODS="$(sed -n '/^sequence:/,/^branding:/p' "$CALDIR/settings.conf" \ + | grep -E '^[[:space:]]*-[[:space:]]*[a-z]' \ + | sed -e 's/^[[:space:]]*-[[:space:]]*//' -e 's/:[[:space:]]*$//' \ + | grep -vE '^(show|exec)$' | sort -u)" + for m in $MODS; do + base="${m%%@*}" + inst="${m##*@}" + if [ "$base" != "$m" ]; then + # instance form module@id -> settings.conf must declare it + grep -q "id:[[:space:]]*$inst" "$CALDIR/settings.conf" \ + || err "settings.conf uses '$m' but declares no instance with id '$inst'" + cfg="$(grep -A2 "id:[[:space:]]*$inst" "$CALDIR/settings.conf" | sed -n 's/.*config:[[:space:]]*//p' | head -1)" + [ -n "$cfg" ] && [ ! -f "$CALDIR/modules/$cfg" ] \ + && err "instance '$inst' references missing config $CALDIR/modules/$cfg" + else + if ! printf '%s\n' "$CONFIGLESS" | grep -qx "$base"; then + [ -f "$CALDIR/modules/$base.conf" ] \ + || err "Calamares module '$base' has no config at $CALDIR/modules/$base.conf" + fi + fi + done + ok + + # Branding must resolve. + BR="$(sed -n 's/^branding:[[:space:]]*//p' "$CALDIR/settings.conf" | head -1)" + if [ -n "$BR" ]; then + [ -f "$CALDIR/branding/$BR/branding.desc" ] \ + || err "branding '$BR' has no branding.desc at $CALDIR/branding/$BR/" + if [ -f "$CALDIR/branding/$BR/branding.desc" ]; then + sed -n 's/^[[:space:]]*\(productLogo\|productIcon\|productWelcome\|slideshow\):[[:space:]]*"\{0,1\}\([^"]*\)"\{0,1\}[[:space:]]*$/\2/p' \ + "$CALDIR/branding/$BR/branding.desc" | while read -r img; do + [ -z "$img" ] && continue + [ -f "$CALDIR/branding/$BR/$img" ] || { red " ERROR branding asset missing: $img"; echo x >>"$REPO/.validate_br_fail"; } + done + if [ -f .validate_br_fail ]; then + ERRORS=$((ERRORS + $(wc -l < .validate_br_fail))); rm -f .validate_br_fail + fi + fi + fi + ok + + # Scripts referenced by shellprocess configs must exist and be executable. + for sp in "$CALDIR"/modules/shellprocess*.conf; do + [ -f "$sp" ] || continue + grep -oE '"/usr/share/simulationos[^"]*"' "$sp" | tr -d '"' | while read -r s; do + [ -x "airootfs$s" ] || { red " ERROR $sp references missing/non-executable $s"; echo x >>"$REPO/.validate_sp_fail"; } + done + done + if [ -f .validate_sp_fail ]; then + ERRORS=$((ERRORS + $(wc -l < .validate_sp_fail))); rm -f .validate_sp_fail + fi + ok + + # The unpackfs source path must match install_dir/arch from profiledef.sh. + IDIR="$(sed -n 's/^install_dir="\(.*\)"/\1/p' profiledef.sh | head -1)" + IARCH="$(sed -n 's/^arch="\(.*\)"/\1/p' profiledef.sh | head -1)" + if [ -f "$CALDIR/modules/unpackfs.conf" ] && [ -n "$IDIR" ] && [ -n "$IARCH" ]; then + EXPECT="/run/archiso/bootmnt/${IDIR}/${IARCH}/airootfs.sfs" + grep -q "$EXPECT" "$CALDIR/modules/unpackfs.conf" \ + || err "unpackfs.conf source does not match profiledef (expected $EXPECT)" + fi + ok + + # The installer must actually be installable and launchable. + has_pkg cachyos-calamares || has_pkg calamares \ + || err "no Calamares package in packages.x86_64, but an installer config exists" + [ -x airootfs/usr/local/bin/simulationos-install ] \ + || err "airootfs/usr/local/bin/simulationos-install is missing or not executable" + [ -f airootfs/usr/share/applications/simulationos-install.desktop ] \ + || err "installer .desktop launcher is missing" + if [ -f airootfs/usr/share/applications/simulationos-install.desktop ]; then + EXEC="$(sed -n 's/^Exec=//p' airootfs/usr/share/applications/simulationos-install.desktop | awk '{print $1}')" + [ -x "airootfs$EXEC" ] || err ".desktop Exec points at missing $EXEC" + ICON="$(sed -n 's/^Icon=//p' airootfs/usr/share/applications/simulationos-install.desktop)" + if [ -n "$ICON" ] && [ "${ICON#/}" = "$ICON" ]; then + ls airootfs/usr/share/pixmaps/"$ICON".* >/dev/null 2>&1 \ + || warn ".desktop Icon='$ICON' not found in airootfs/usr/share/pixmaps/" + fi + fi + ok + + # The live medium must not leak into the installed system. + DELOOP="$CALDIR/scripts/simulationos-deloop" + if [ -f "$DELOOP" ]; then + for must in 'mkinitcpio.conf.d/archiso.conf' 'live-autologin' '10-simulationos-live' 'passwd --lock root' '/home/liveuser'; do + grep -q -- "$must" "$DELOOP" \ + || err "simulationos-deloop does not handle '$must'" + done + fi + grep -q 'username:[[:space:]]*liveuser' "$CALDIR/modules/removeuser.conf" 2>/dev/null \ + || err "removeuser.conf does not remove 'liveuser'" + grep -qE '^[[:space:]]*doAutologin:[[:space:]]*false' "$CALDIR/modules/users.conf" 2>/dev/null \ + || err "users.conf does not set doAutologin: false (live autologin would be inherited)" + ok +fi + +# --------------------------------------------------------------------------- +section "shell scripts" +# --------------------------------------------------------------------------- +SCRIPTS="$(find airootfs/usr/local/bin airootfs/usr/share/simulationos -type f 2>/dev/null; ls scripts/*.sh 2>/dev/null)" +for s in $SCRIPTS; do + head -1 "$s" | grep -q '^#!' || continue + if ! bash -n "$s" 2>/dev/null; then + err "shell syntax error in $s" + fi + [ -x "$s" ] || err "$s has a shebang but is not executable" +done +ok + +if command -v shellcheck >/dev/null 2>&1; then + for s in $SCRIPTS; do + head -1 "$s" | grep -qE '^#!.*(bash|sh)' || continue + out="$(shellcheck -S error -f gcc "$s" 2>/dev/null)" + [ -n "$out" ] && err "shellcheck errors in $s:"$'\n'"$out" + done + ok +else + warn "shellcheck not installed; skipping lint" +fi + +# --------------------------------------------------------------------------- +section "broken symlinks inside airootfs" +# --------------------------------------------------------------------------- +# Only symlinks that point *within* airootfs can be checked offline; absolute +# links into /usr are resolved on the target at build time. +find airootfs -type l 2>/dev/null | while read -r l; do + t="$(readlink "$l")" + case "$t" in + /*) continue ;; + esac + d="$(dirname "$l")" + [ -e "$d/$t" ] || { red " ERROR broken relative symlink: $l -> $t"; echo x >>"$REPO/.validate_bs_fail"; } +done +if [ -f .validate_bs_fail ]; then + ERRORS=$((ERRORS + $(wc -l < .validate_bs_fail))); rm -f .validate_bs_fail +fi +ok + +# --------------------------------------------------------------------------- +printf '\n' +if [ "$ERRORS" -gt 0 ]; then + red "FAILED: $ERRORS error(s), $WARNINGS warning(s)" + exit 1 +fi +if [ "$WARNINGS" -gt 0 ]; then + yellow "PASSED with $WARNINGS warning(s)" + exit 0 +fi +green "PASSED: profile is internally consistent" +exit 0 From ebd7a456fda7a9201e5b1e1c7e891c012b360a83 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:21:26 +0300 Subject: [PATCH 08/33] docs: rewrite README and add architecture rationale Records, for each significant decision, which behaviour is upstream archiso, which is CachyOS-specific and which is a SimulationOS decision, plus the verification status of every claim - explicitly separating what was statically verified from what has never been booted. --- README.md | 284 +++++++++++++++++++++++++++++- docs/architecture.md | 406 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 689 insertions(+), 1 deletion(-) create mode 100644 docs/architecture.md diff --git a/README.md b/README.md index f62da2b..12d7180 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,284 @@ # SimulationOS -SimulationOS Arch-Based distribution offering an ideal working environment especially for programming, Networking and Gaming! + +An Arch-based Linux distribution built around Hyprland, shipped as a live ISO +with a graphical installer. + +SimulationOS is an **archiso profile**: this repository is not an operating +system you run, it is the build recipe that `mkarchiso` turns into + +``` +out/simulationos--x86_64.iso +``` + +> **Status: Alpha.** The profile builds a complete live desktop and installs +> itself. See [Alpha limitations](#alpha-limitations) for what is not done yet, +> and [Verification status](#verification-status) for what has actually been +> tested versus only statically checked. + +--- + +## Architecture + +``` +Arch Linux base distribution, [core] [extra] [multilib] + | +ArchISO profile format, initramfs hooks, ISO assembly + | +CachyOS repositories only for what is exclusive to them + | +linux-cachyos the kernel SimulationOS ships + | +SimulationOS live layer airootfs/ overlay: identity, live user, services + | +Hyprland Wayland session, started by SDDM + | +Calamares graphical installer, SimulationOS configuration +``` + +Three sources are deliberately kept distinct: + +| Layer | Role | +|---|---| +| **ArchISO** | The architectural baseline. Boot modes, `profiledef.sh` semantics, initramfs hooks and the `/etc/skel` mechanism follow upstream exactly. | +| **CachyOS** | A *component supplier*, not a template. Used for `linux-cachyos`, `cachyos-calamares`, `ckbcomp`, `mkinitcpio-openswap` and the keyring/mirrorlist — nothing else. | +| **SimulationOS** | Identity, live session design, desktop configuration and installer configuration. | + +Arch repositories are listed **before** `[cachyos]` in `pacman.conf`, so a +package that exists in both comes from Arch. SimulationOS does not adopt the +CachyOS optimised package set. + +Full design rationale: [`docs/architecture.md`](docs/architecture.md). + +--- + +## Prerequisites + +Building an ISO requires an **Arch Linux x86_64** host running as root: + +| Requirement | Why | +|---|---| +| Linux, `x86_64` | `mkarchiso` is Linux-only; a non-x86_64 host would pacstrap the wrong architecture. | +| `archiso` | Provides `mkarchiso`. | +| `cachyos-keyring`, key `F3B607488DB35A47` trusted | `pacstrap` verifies CachyOS package signatures against the **build host** keyring. Signature checking is never disabled. | +| ~20 GB free disk | Work directory plus the ISO. | +| Network access | Package downloads. | + +Provision the CachyOS key on the build host (the official CachyOS procedure): + +```bash +sudo pacman-key --recv-keys F3B607488DB35A47 --keyserver keyserver.ubuntu.com +sudo pacman-key --lsign-key F3B607488DB35A47 +sudo pacman -U https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-keyring-20240331-1-any.pkg.tar.zst +sudo pacman -S archiso +``` + +`scripts/build-iso.sh` checks every one of these and refuses to start with an +exact remediation message rather than failing halfway through a build. + +### Not building on Arch? + +Use an x86_64 Arch container. `mkarchiso` needs elevated privileges: + +```bash +podman run --rm -it --privileged \ + -v "$PWD":/simulationos -w /simulationos \ + docker.io/archlinux:latest bash +# then, inside: +pacman -Sy --noconfirm archlinux-keyring && pacman -Syu --noconfirm +# ...provision the CachyOS key as above, then: +./scripts/build-iso.sh +``` + +On an ARM Mac or ARM Linux host you must force an x86_64 container +(`--platform linux/amd64` with binfmt/qemu-user configured). This is very slow +and is not the supported path — prefer a real x86_64 machine or VM. + +--- + +## Repository structure + +``` +SimulationOS/ +├── profiledef.sh archiso manifest (ISO name, boot modes, permissions) +├── packages.x86_64 everything installed into the ISO +├── pacman.conf BUILD HOST repo config (explicit CachyOS Server=) +├── bootstrap_packages.x86_64 minimal set for the bootstrap build mode +│ +├── airootfs/ overlay copied into the ISO root +│ ├── etc/ +│ │ ├── os-release, issue, motd, hostname, hosts identity +│ │ ├── passwd, shadow, group, gshadow the liveuser account +│ │ ├── pacman.conf live/installed repo config +│ │ ├── sddm.conf.d/ greeter + live autologin +│ │ ├── sudoers.d/, polkit-1/rules.d/ live-only privilege policy +│ │ ├── skel/.config/{hypr,waybar,kitty,mako}/ the desktop, via /etc/skel +│ │ └── systemd/{system,user}/ explicit service enablement +│ └── usr/ +│ ├── local/bin/ simulationos-install, simos-* helpers +│ └── share/ +│ ├── applications/ "Install SimulationOS" launcher +│ ├── backgrounds/simulationos/ +│ ├── pixmaps/ +│ └── simulationos/calamares/ the whole installer configuration +│ +├── efiboot/ systemd-boot entries (UEFI) +├── syslinux/ syslinux menu (BIOS) + PXE +├── grub/loopback.cfg boot this ISO from an existing GRUB +│ +├── scripts/ +│ ├── validate-profile.sh static consistency checks +│ ├── build-iso.sh environment checks + mkarchiso + checksum +│ ├── clean-build.sh remove work/ and out/ +│ ├── test-iso.sh boot it in QEMU (UEFI or BIOS) +│ └── make-wallpaper.py regenerate the wallpaper asset +│ +└── docs/architecture.md +``` + +--- + +## Workflow + +### Validate + +Runs anywhere, including macOS — it never needs `pacman`: + +```bash +./scripts/validate-profile.sh +``` + +It fails the build on things that would otherwise only surface at boot: +boot entries naming a kernel the profile does not install, Hyprland launching +binaries nobody ships, developer-specific paths, dangling systemd symlinks, +missing Calamares module configs, a `deloop` script that stopped removing the +archiso initramfs hook, shell syntax errors, and more. + +### Build + +```bash +sudo ./scripts/build-iso.sh +``` + +Produces `out/simulationos--x86_64.iso` and a matching `.sha256`. + +### Clean + +```bash +./scripts/clean-build.sh # remove work/ and out/ +./scripts/clean-build.sh --keep-iso # remove work/ only +``` + +### Test in a VM + +```bash +./scripts/test-iso.sh # UEFI (OVMF) +./scripts/test-iso.sh --bios # legacy BIOS / syslinux +./scripts/test-iso.sh --install # attach a blank 30G disk and install +./scripts/test-iso.sh --boot-disk # boot ONLY that disk, no ISO +``` + +The last two are the real acceptance test: install to the disk, then boot the +disk with no medium attached. + +--- + +## Boot and installation flow + +**Live medium** + +``` +Firmware → syslinux (BIOS) / systemd-boot (UEFI) + → linux-cachyos + archiso initramfs + → SquashFS root (/run/archiso) + → systemd → graphical.target + → SDDM → autologin liveuser → Hyprland +``` + +**Installation** + +``` +Live session → "Install SimulationOS" → Calamares + → partition → mount → unpackfs (copy the live SquashFS) + → users → networkcfg → services-systemd → packages (drop the installer) + → removeuser (liveuser) → simulationos-deloop (strip live config) + → initcpiocfg → initcpio (rebuild a NON-archiso initramfs) + → grubcfg → bootloader (GRUB) → umount + → reboot into the installed system +``` + +Because the offline installer copies the live filesystem verbatim, the target +initially inherits every live concession. `simulationos-deloop` removes them: +SDDM autologin, tty autologin, NOPASSWD sudo (replaced by a password-prompting +`%wheel` rule), the permissive polkit rule, the archiso initramfs hook, +archiso-only units, `/home/liveuser` — and it **locks the root account**, which +the live medium leaves passwordless. + +--- + +## Alpha limitations + +- **NVIDIA:** no proprietary driver and no `chwd` integration. The previous + half-wired `chwd`-dependent NVIDIA scripts were removed rather than shipped + broken. NVIDIA hardware falls back to `nouveau`. +- **Installer model:** offline only. The `unpackfs` module copies the live + SquashFS; there is no online package selection. +- **Bootloaders:** the installed system always gets GRUB. No systemd-boot, + rEFInd or Limine choice. +- **No disk encryption flow** has been exercised, though the LUKS-capable + Calamares modules and `cryptsetup` are present. +- **Secure Boot** is not supported. +- **Locale defaults** to `C.UTF-8` on the live medium; the installer sets the + real locale for the target. +- **sshd is installed but not enabled** on the live medium, unlike upstream + archiso. Start it manually after setting a password if you need it. +- The SquashFS is `xz`-compressed, which favours ISO size over boot speed. + +--- + +## Verification status + +Everything below was done on a **macOS arm64** workstation, which cannot run +`mkarchiso`, `pacman` or an x86_64 QEMU guest. Nothing here claims a boot that +did not happen. + +| Item | Status | +|---|---| +| Upstream archiso/CachyOS research against current sources | VERIFIED | +| `linux-cachyos` kernel/initramfs/preset naming | VERIFIED (from the CachyOS `PKGBUILD`) | +| Every package name resolves in Arch or CachyOS repositories | VERIFIED (repo queries) | +| Profile internal consistency | VERIFIED (`validate-profile.sh` passes; 10 fault-injection cases caught) | +| Shell scripts: syntax + `shellcheck` | VERIFIED | +| **ISO build** | **NOT TESTED — blocked: no Linux x86_64 host** | +| **UEFI boot / BIOS boot** | **NOT TESTED — blocked: no ISO** | +| **Hyprland live session** | **NOT TESTED — blocked: no ISO** | +| **Calamares run / full installation / installed-system boot** | **NOT TESTED — blocked: no ISO** | + +The first real run should be: + +```bash +./scripts/validate-profile.sh && sudo ./scripts/build-iso.sh +./scripts/test-iso.sh --install +./scripts/test-iso.sh --boot-disk +``` + +--- + +## Upstream dependencies + +| Project | Used for | Licence | +|---|---|---| +| [Arch Linux](https://archlinux.org) | Base system and repositories | various | +| [archiso](https://gitlab.archlinux.org/archlinux/archiso) | ISO build system, profile format | GPL-3.0 | +| [CachyOS](https://cachyos.org) | `linux-cachyos`, `cachyos-calamares`, keyring/mirrorlist | GPL-3.0 and others | +| [Hyprland](https://hypr.land) | Wayland compositor | BSD-3-Clause | +| [Calamares](https://calamares.io) | Installer framework | GPL-3.0 | + +SimulationOS packages and configures these projects; it does not claim +authorship of them. `syslinux/splash.png` and the `livecd-sound` accessibility +helper are inherited from archiso and remain under their original terms. + +--- + +## Licence + +See [LICENSE](LICENSE). diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..55950ae --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,406 @@ +# SimulationOS architecture + +Design decisions and the reasoning behind them. Where a choice differs from +Arch or CachyOS, this document says which behaviour is upstream and which is a +SimulationOS decision. + +--- + +## 1. Layering + +``` +UPSTREAM ARCHISO BEHAVIOR the baseline: profile format, boot modes, + initramfs hooks, airootfs overlay semantics +CACHYOS-SPECIFIC BEHAVIOR a component supplier: kernel, installer build, + keyring/mirrorlist, two packages Arch lacks +SIMULATIONOS DECISION identity, live session, desktop, installer config +``` + +SimulationOS does not fork archiso and does not copy the CachyOS ISO profile. +It is an archiso profile that consumes CachyOS packages. + +--- + +## 2. archiso profile + +### Boot modes + +**UPSTREAM:** archiso consolidated its boot mode names. `bios.syslinux.mbr` and +`bios.syslinux.eltorito` became `bios.syslinux`; the four +`uefi-{x64,ia32}.systemd-boot.{esp,eltorito}` names became a single +`uefi.systemd-boot`. The old names still exist as shims that emit a +deprecation warning. `mkarchiso` also **rejects** `uefi.systemd-boot` and +`uefi.grub` used together. + +**SIMULATIONOS DECISION:** + +```bash +bootmodes=('bios.syslinux' 'uefi.systemd-boot') +``` + +`uefi.systemd-boot` already covers x64 plus mixed-mode IA32 on x86_64, so no +separate IA32 entry is needed. + +### `grub/` + +**UPSTREAM:** `mkarchiso`'s `_make_bootmodes()` calls +`_make_common_grubenv_and_loopbackcfg` whenever no `*grub*` boot mode is in +use, which installs `grub/loopback.cfg` onto the ISO. `grub/grub.cfg`, by +contrast, is only read by `_make_common_bootmode_grub_cfg`, which only +`_make_bootmode_uefi.grub` calls. + +**SIMULATIONOS DECISION:** keep `grub/loopback.cfg` (it lets an existing GRUB +boot this ISO from a file), delete `grub/grub.cfg` (unreachable without a +`uefi.grub` boot mode, which we cannot have alongside systemd-boot). +`validate-profile.sh` enforces this invariant. + +### Removed variables + +`buildmodes=('iso')` is the implicit default and upstream dropped it from both +its profiles. `iso_icon`, `profiles_description`, `install_dir_disktop` and +`kernel_parameters` are **not** variables `mkarchiso` reads — they were noise. +The validator fails if any reappear. + +### Build order matters + +`mkarchiso` runs `_make_custom_airootfs` **before** `_make_packages`. The +`airootfs/` overlay is laid down first and pacstrap installs on top of it. +A file we ship at a path a package also owns is therefore only safe when that +path is in the package's `backup` array — otherwise pacstrap aborts with a file +conflict. + +Verified backup-array members that SimulationOS overlays safely: + +| Path | Owning package | In `backup`? | +|---|---|---| +| `/etc/pacman.conf` | `pacman` | yes | +| `/etc/passwd`, `/etc/group`, `/etc/gshadow` | `filesystem` | yes | +| `/etc/hosts`, `/etc/issue` | `filesystem` | yes | + +This is also why SimulationOS **removed** `airootfs/etc/pacman.d/mirrorlist`: +the hand-written CachyOS-forcing mirrorlist both hard-coded mirrors and sat at +a path owned by `pacman-mirrorlist`. The package now provides it and the +inherited `uncomment-mirrors.hook` uncomments it, which is upstream behaviour. + +--- + +## 3. Kernel + +**CACHYOS:** `linux-cachyos`'s `PKGBUILD` sets `pkgbase="linux-cachyos"` and +installs the image as `$modulesdir/vmlinuz` with a `pkgbase` marker file. The +standard `mkinitcpio` pacman hook then produces: + +``` +/boot/vmlinuz-linux-cachyos +/boot/initramfs-linux-cachyos.img +/boot/initramfs-linux-cachyos-fallback.img +/etc/mkinitcpio.d/linux-cachyos.preset +``` + +**The bug this replaced:** the profile previously had *three* mutually +inconsistent names — `linux-cachyos` installed, boot entries pointing at +`vmlinuz-linux`, and a hand-written `/etc/mkinitcpio.d/linux.preset` naming a +nonexistent `vmlinuz-linux-simos`. Nothing lined up and the ISO could not boot. + +**SIMULATIONOS DECISION:** one name, `linux-cachyos`, everywhere: the package +list, the systemd-boot entries, the syslinux entries, the PXE entries and +`loopback.cfg`. The custom preset is deleted — upstream archiso removed its own +`linux.preset` for the same reason ("the default preset is good enough"). The +validator derives the expected filenames from the package list and fails on any +drift. + +**Microcode** needs no manual handling: `mkinitcpio`'s `microcode` hook (in +`archiso.conf`) embeds it, and `mkarchiso` detects the embedded case via +`_check_if_initramfs_has_ucode`, copying external ucode images and injecting +them into boot entries only when required. + +--- + +## 4. CachyOS repository integration + +Only four things genuinely require CachyOS: + +| Package | Why | +|---|---| +| `linux-cachyos` | the kernel SimulationOS is defined by | +| `cachyos-calamares` | Calamares is not in the Arch repositories | +| `ckbcomp`, `mkinitcpio-openswap` | `cachyos-calamares` dependencies, absent from Arch | + +Plus `cachyos-keyring` and `cachyos-mirrorlist` so the installed system retains +access. + +**SIMULATIONOS DECISION — repository order.** Arch repos are listed *first*: + +``` +[core] [extra] [multilib] [cachyos] +``` + +A package present in both comes from Arch. SimulationOS uses CachyOS for what +is exclusive to it, not as a wholesale replacement for the Arch package set. +This is the opposite of CachyOS's own ordering and is intentional: it keeps the +dependency surface predictable. + +**SIMULATIONOS DECISION — no developer-specific files.** The build-host +`pacman.conf` declares `[cachyos]` with explicit `Server =` URLs instead of +`Include = /etc/pacman.d/cachyos-mirrorlist`. The previous version required a +file that merely happened to exist on one machine. The *keyring* is still a +genuine host prerequisite, because pacstrap verifies signatures against the +host keyring — so `build-iso.sh` checks for `cachyos-keyring` and for key +`F3B607488DB35A47`, and fails early with the exact provisioning commands. +Signature verification is never disabled. (`DisableSandbox` was also removed +from `pacman.conf`.) + +The live system's `/etc/pacman.conf` keeps +`Include = /etc/pacman.d/cachyos-mirrorlist`, which is correct there because +the `cachyos-mirrorlist` package is installed. + +--- + +## 5. The live user + +**UPSTREAM:** `mkarchiso`'s `_make_customize_airootfs` runs *after* packages +are installed. For every entry in `airootfs/etc/passwd` with UID 1000–59999 and +a valid home, it creates the home directory if missing, copies `/etc/skel/.` +into it, `chmod 0750`s it and `chown -hR`s it to that user. + +That is a real, supported mechanism — `/etc/skel` *is* applied, provided the +account is declared in `airootfs/etc/passwd`. + +**SIMULATIONOS DECISION:** put the desktop configuration in +`airootfs/etc/skel/.config/` and let archiso materialise +`/home/liveuser/.config/...` with correct ownership. One source of truth, and +Calamares' `users` module copies the same `/etc/skel` for the installed user — +so the live desktop and the installed desktop are identical by construction, +with no duplicated dotfiles. + +Live-only concessions are named so they can be removed deterministically: + +| Path | Purpose | +|---|---| +| `/etc/sddm.conf.d/20-simulationos-live-autologin.conf` | autologin liveuser | +| `/etc/sudoers.d/10-simulationos-live` | NOPASSWD sudo | +| `/etc/polkit-1/rules.d/49-simulationos-live-nopasswd.rules` | permissive polkit | +| `/etc/systemd/system/getty@tty1.service.d/autologin.conf` | tty autologin | + +Everything with `-live-` in the name is stripped by `simulationos-deloop`. + +--- + +## 6. Graphical session + +**SIMULATIONOS DECISION: SDDM, X11 greeter, Wayland session.** + +`sddm` hard-depends on `xorg-server` and `xorg-xauth`, so its default +`DisplayServer=x11` greeter works with no extra packages. Its Wayland greeter +would need `weston` (`CompositorCommand=weston --shell=kiosk`), which is not +shipped. The greeter launches `hyprland.desktop` from +`/usr/share/wayland-sessions/`, which the `hyprland` package provides. + +The theme is `maldives`, one of the three themes (`elarun`, `maldives`, `maya`) +that ship inside the `sddm` package. The previous config specified `breeze`, +which requires Plasma and was not installed. + +Enablement is explicit: `/etc/systemd/system/display-manager.service` is a +symlink to `sddm.service`, and `default.target` points at `graphical.target`. +The inherited `dmcheck` script, which guessed at a display manager and was +never invoked by anything, was deleted. + +### Audio enablement + +**UPSTREAM:** the `pipewire`, `pipewire-pulse` and `wireplumber` packages ship +user units but **no** `.wants` symlinks, so nothing enables them by file. + +**SIMULATIONOS DECISION:** declare enablement explicitly in +`airootfs/etc/systemd/user/`, from the units' actual `[Install]` sections: + +| Unit | `[Install]` | Symlink shipped | +|---|---|---| +| `pipewire.socket` | `WantedBy=sockets.target` | `sockets.target.wants/` | +| `pipewire.service` | `WantedBy=default.target` | `default.target.wants/` | +| `pipewire-pulse.socket` | `WantedBy=sockets.target` | `sockets.target.wants/` | +| `pipewire-pulse.service` | `WantedBy=default.target` | `default.target.wants/` | +| `wireplumber.service` | `WantedBy=pipewire.service`, `Alias=pipewire-session-manager.service` | `pipewire.service.wants/` + the alias | + +--- + +## 7. Networking + +**UPSTREAM:** archiso's releng profile uses `systemd-networkd` + `iwd` + +`systemd-resolved`, which suits a headless install medium. + +**SIMULATIONOS DECISION:** NetworkManager only. A Hyprland desktop wants +`nm-applet` and `nm-connection-editor`, and Calamares' `networkcfg` module +migrates NetworkManager connections into the installed system. Running two +network stacks is a classic source of non-deterministic breakage, so +`systemd-networkd`, its `.network` files, `iwd` and the statically enabled +`wpa_supplicant.service` were all removed. `wpa_supplicant` stays installed +because NetworkManager activates it over D-Bus. + +`systemd-resolved` was dropped with it, along with the +`/etc/resolv.conf -> stub-resolv.conf` symlink that depended on it; +NetworkManager manages `/etc/resolv.conf` directly. The validator fails if both +stacks are ever enabled at once. + +--- + +## 8. Hardware + +Generic support: `linux-firmware`, `sof-firmware`, `mesa`, `vulkan-radeon`, +`vulkan-intel`, `vulkan-swrast`, `xorg-xwayland`, `bluez`. + +**SIMULATIONOS DECISION — no `chwd`, no NVIDIA scripts (Alpha).** The profile +inherited `nvidia-module-loader`, `remove-nvidia` and `removeun`, all of which +call CachyOS's `chwd` hardware-detection tool. `chwd` was never in the package +list and nothing invoked those scripts, so the "NVIDIA support" was inert. The +honest choice for Alpha is removal rather than shipping a half-working +hardware-detection path. NVIDIA hardware falls back to the in-kernel `nouveau` +driver. + +`cursor:no_hardware_cursors = true` is set in the Hyprland config because +SimulationOS is expected to be evaluated in a VM, where hardware cursor planes +are frequently broken and the pointer disappears. + +--- + +## 9. Desktop configuration + +The inherited Hyprland and Waybar configuration was the author's personal +laptop setup and was replaced wholesale rather than patched. It contained: + +- `/mnt/m.2_ssd/Linux/Wallpapers/1354205.jpeg` as the wallpaper +- `~/.scripts/Acer/PredatorSense/rgbkb/rgbkb.service.sh` on autostart +- **`exec-once = brightnessctl set 0%`** — a black screen on first login +- a hardcoded USB audio sink, a hardcoded `SYNA7DB5:00` touchpad, a hardcoded + `eDP-2` output +- dependencies on `grimblast`, `auto-cpufreq`, `pamixer` and the ML4W dotfile + framework, none of them shipped +- both `hyprpaper.conf` and `swww` autostart (two wallpaper daemons) +- both `monitor.conf` and `monitors.conf`, plus several config files that were + shipped but never sourced + +**SIMULATIONOS DECISION:** one `hyprland.conf` plus a deliberately empty +`monitors.conf` for per-machine overrides. Every `exec-once` and every keybind +target maps to a package in `packages.x86_64` or to a script in +`airootfs/usr/local/bin/`. Waybar uses **built-in modules only** — no custom +scripts — so a missing helper can never blank the bar. One wallpaper daemon +(`hyprpaper`), one launcher (`wofi`), one terminal (`kitty`), one file manager +(`thunar`), one notification daemon (`mako`), one polkit agent +(`hyprpolkitagent`) and one screen locker (`hyprlock`). + +The validator enforces all of this: sourced files must exist, exec targets must +be shipped, the wallpaper must exist, only one wallpaper daemon may autostart, +and `brightnessctl set 0%` is a hard error. + +The wallpaper is a real generated asset +(`airootfs/usr/share/backgrounds/simulationos/simulationos-alpha.png`, +1920x1080), reproducible via `scripts/make-wallpaper.py`. + +`.bashrc` was also fixed: it aliased `rm` to `trash-put` and `n` to `nvim` +without shipping either, so `rm` was broken in the live session. + +Two other inherited defects worth recording: `airootfs/etc/hosts` contained +*only* a hardcoded GitHub IP with no `localhost` entries, and +`airootfs/etc/polkit-1/rules.d/sddm.conf.d/autologin.conf` was an SDDM config +filed inside polkit's rules directory, where SDDM would never read it. + +--- + +## 10. Installer + +### Why the configuration is not in `/etc/calamares` + +`cachyos-calamares` owns roughly sixty files under `/etc/calamares/modules/` +and declares **no `backup` array**. Because the airootfs overlay is laid down +*before* pacstrap, shipping our own files at those paths would be a hard +pacstrap file conflict and the build would fail. + +**SIMULATIONOS DECISION:** ship a self-contained tree at +`/usr/share/simulationos/calamares/` and launch + +``` +calamares -c /usr/share/simulationos/calamares +``` + +Calamares' `-c/--config` sets `appDataDir`, and when that is overridden both +`moduleConfigurationCandidates()` and `brandingFileCandidates()` resolve +**only** inside it. So settings, module configs and branding all come from one +SimulationOS-owned directory, with zero chance of silently inheriting CachyOS +defaults and zero file conflicts. + +### Installation model + +Offline, via `unpackfs`: the live SquashFS at +`/run/archiso/bootmnt/arch/x86_64/airootfs.sfs` (derived from `install_dir` +and `arch` in `profiledef.sh`, and checked by the validator) is copied to the +target. Simple, network-free, and the live desktop is by definition what gets +installed. + +Sequence, and why the order is what it is: + +``` +partition -> mount -> unpackfs -> machineid -> fstab -> locale -> keyboard +-> localecfg -> users -> networkcfg -> hwclock -> services-systemd -> packages +-> removeuser -> shellprocess@deloop -> initcpiocfg -> initcpio +-> grubcfg -> bootloader -> umount +``` + +- `removeuser` runs after `users`, so the real account exists first. +- **`shellprocess@deloop` must run before `initcpio`.** It deletes + `/etc/mkinitcpio.conf.d/archiso.conf`; if that drop-in survived, `mkinitcpio` + would build another *archiso* initramfs and the installed system would not + boot without the ISO. This ordering is the single most load-bearing detail in + the installer, and the validator checks that `deloop` still handles it. +- `packages` uses `try_remove` for `cachyos-calamares` and `gparted`, so the + installed system does not carry its own installer. `try_remove` cannot fail + the installation. + +### Undoing the live medium + +`simulationos-deloop` runs inside the target chroot and removes: SDDM +autologin, tty autologin, the NOPASSWD sudoers file (replacing it with a +validated password-prompting `%wheel` rule), the permissive polkit rule, the +archiso initramfs hook, archiso-only units (`livecd-talk`, +`livecd-alsa-unmuter`, `pacman-init`, the gnupg mount), the gpt-auto-generator +mask, the volatile-journal and do-not-suspend drop-ins, and `/home/liveuser`. + +It also **locks the root account**. This matters: the live medium ships +`root::` — an empty password — and `unpackfs` copies `/etc/shadow` verbatim, so +without this step every installed system would have a passwordless root. +Calamares is configured with `setRootPassword: false` (a sudo-only model), so +locking root is the correct completion of that design. + +The script never deletes the file it is executing from; removing +`/usr/share/simulationos` is a separate command listed after it in +`shellprocess_deloop.conf`. + +### Installed-system bootloader + +**SIMULATIONOS DECISION: GRUB.** The ISO uses syslinux + systemd-boot; the +installed system uses GRUB. These are separate concerns and conflating them is +a common mistake. GRUB covers BIOS and UEFI with one code path, which is the +right trade for an Alpha. `grubcfg` writes `/etc/default/grub` with +`GRUB_DISTRIBUTOR="SimulationOS"`. + +--- + +## 11. Validation + +`scripts/validate-profile.sh` is the guard against regressions. It was itself +verified by fault injection — ten deliberate regressions were introduced into a +scratch copy and every one was caught: + +| Injected fault | Caught | +|---|---| +| kernel drift in a boot entry | yes | +| deprecated bootmode reintroduced | yes | +| `broadcom-wl` (removed from Arch) reintroduced | yes | +| personal `/mnt/m.2_ssd` path returns | yes | +| `brightnessctl set 0%` reintroduced | yes | +| Hyprland launches an unshipped binary | yes | +| second wallpaper daemon added | yes | +| `deloop` stops removing the archiso hook | yes | +| `doAutologin: true` in `users.conf` | yes | +| Calamares module config or display manager deleted | yes | + +The checks deliberately ignore comment text, so documentation can name a banned +tool without tripping its own rule. From c60830ba8b6ec3e13214e31bbf6ac3b45a5255e1 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:35:00 +0300 Subject: [PATCH 09/33] build: single ./build.sh entry point with an x86_64 container builder One build engine for developers, CI and releases, so a local build and a CI build cannot drift. build.sh decides WHERE to build - natively on Arch x86_64, otherwise in an x86_64 Arch container - and then runs the same scripts/build-iso.sh either way. On an arm64 host the container is pinned to linux/amd64 so an arm64 ISO can never be produced by accident. Found by actually running it: pacman >=7 sandboxes downloads with seccomp and an unprivileged alpm user, and that filter fails with EINVAL under qemu-user emulation. The workaround is confined to the throwaway builder container (a patched copy of pacman.conf plus --disable-sandbox); the committed pacman.conf never sets DisableSandbox and signature verification is never disabled. validate-profile.sh now fails if DisableSandbox or 'SigLevel = Never' ever appears in a committed config, so the concession cannot leak into the product. profiledef.sh takes pacman_conf from $SIMOS_PACMAN_CONF so the builder can supply that patched copy without modifying the repository. Also adds scripts/inspect-iso.sh: mkarchiso exiting 0 is not evidence of a usable image, so the artefact is checked for the kernel and initramfs named by packages.x86_64, the squashfs, the bootloader payloads for the declared bootmodes, and a matching checksum. build-iso.sh now writes build-info.json (commit, archiso version, checksum, duration) and build.log. --- .github/workflows/iso.yml | 77 ++++++++ .github/workflows/validate.yml | 85 +++++++++ .gitignore | 2 + build.sh | 209 +++++++++++++++++++++ docs/adr/0001-archiso-baseline.md | 24 +++ docs/adr/0002-kernel-linux-cachyos.md | 27 +++ docs/adr/0003-cachyos-as-supplier.md | 27 +++ docs/adr/0004-hyprland-desktop.md | 26 +++ docs/adr/0005-sddm-display-manager.md | 26 +++ docs/adr/0006-networkmanager.md | 23 +++ docs/adr/0007-pipewire-audio.md | 27 +++ docs/adr/0008-calamares-offline.md | 32 ++++ docs/adr/0009-grub-for-installed-system.md | 23 +++ docs/adr/0010-skel-as-config-source.md | 29 +++ docs/adr/0011-filesystem-ext4-default.md | 23 +++ docs/adr/0012-no-custom-kernel-yet.md | 20 ++ docs/adr/0013-profiles-as-metapackages.md | 29 +++ docs/adr/0014-nftables-firewall.md | 30 +++ docs/adr/0015-single-build-engine.md | 26 +++ docs/adr/README.md | 25 +++ docs/research/sources.md | 187 ++++++++++++++++++ profiledef.sh | 6 +- scripts/build-iso.sh | 40 +++- scripts/inspect-iso.sh | 98 ++++++++++ scripts/validate-profile.sh | 12 ++ 25 files changed, 1128 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/iso.yml create mode 100644 .github/workflows/validate.yml create mode 100755 build.sh create mode 100644 docs/adr/0001-archiso-baseline.md create mode 100644 docs/adr/0002-kernel-linux-cachyos.md create mode 100644 docs/adr/0003-cachyos-as-supplier.md create mode 100644 docs/adr/0004-hyprland-desktop.md create mode 100644 docs/adr/0005-sddm-display-manager.md create mode 100644 docs/adr/0006-networkmanager.md create mode 100644 docs/adr/0007-pipewire-audio.md create mode 100644 docs/adr/0008-calamares-offline.md create mode 100644 docs/adr/0009-grub-for-installed-system.md create mode 100644 docs/adr/0010-skel-as-config-source.md create mode 100644 docs/adr/0011-filesystem-ext4-default.md create mode 100644 docs/adr/0012-no-custom-kernel-yet.md create mode 100644 docs/adr/0013-profiles-as-metapackages.md create mode 100644 docs/adr/0014-nftables-firewall.md create mode 100644 docs/adr/0015-single-build-engine.md create mode 100644 docs/adr/README.md create mode 100644 docs/research/sources.md create mode 100755 scripts/inspect-iso.sh diff --git a/.github/workflows/iso.yml b/.github/workflows/iso.yml new file mode 100644 index 0000000..865bddc --- /dev/null +++ b/.github/workflows/iso.yml @@ -0,0 +1,77 @@ +# L2/L3 - build the ISO in a clean environment and inspect the artefact. +# +# CI calls ./build.sh, the same entry point developers use. There is +# deliberately no second build procedure here: a CI-only build path is how +# "works locally, breaks in CI" starts. +name: iso + +on: + push: + branches: [main] + paths-ignore: ['**.md', 'docs/**', 'LICENSE'] + pull_request: + paths-ignore: ['**.md', 'docs/**', 'LICENSE'] + workflow_dispatch: + workflow_call: + +permissions: + contents: read + +jobs: + validate: + uses: ./.github/workflows/validate.yml + + build: + name: Build ISO + needs: validate + runs-on: ubuntu-latest + timeout-minutes: 120 + container: + image: archlinux:latest + options: --privileged + steps: + - name: Install build dependencies + run: | + pacman -Sy --noconfirm --needed archlinux-keyring + pacman -S --noconfirm --needed archiso git jq + + - uses: actions/checkout@v4 + + - name: Trust the CachyOS signing key + run: | + # Required because pacstrap verifies CachyOS packages against the + # BUILD HOST keyring. Signature verification is never disabled. + pacman-key --init + pacman-key --populate archlinux + pacman-key --recv-keys F3B607488DB35A47 --keyserver keyserver.ubuntu.com + pacman-key --lsign-key F3B607488DB35A47 + pacman -Sy --noconfirm --needed --config ./pacman.conf \ + cachyos-keyring cachyos-mirrorlist + pacman-key --populate cachyos + + - name: Build + run: ./build.sh --native + + - name: Inspect the artefact + id: inspect + run: ./scripts/inspect-iso.sh + + - name: Upload ISO + uses: actions/upload-artifact@v4 + with: + name: simulationos-iso + path: | + out/*.iso + out/*.sha256 + out/build-info.json + retention-days: 14 + if-no-files-found: error + + - name: Upload build log + if: always() + uses: actions/upload-artifact@v4 + with: + name: build-log + path: out/build.log + retention-days: 14 + if-no-files-found: ignore diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..786d35b --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,85 @@ +# L1 - fast static validation. Runs on every push and PR. +# Deliberately needs no container and no pacman, so it finishes in seconds. +name: validate + +on: + push: + branches: ['**'] + pull_request: + workflow_call: + +permissions: + contents: read + +jobs: + static: + name: Static validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install shellcheck + run: sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck + + - name: Validate profile + run: ./scripts/validate-profile.sh + + - name: Shell syntax + run: | + fail=0 + while IFS= read -r f; do + head -1 "$f" | grep -qE '^#!.*(bash|sh)' || continue + bash -n "$f" || { echo "syntax error: $f"; fail=1; } + done < <(git ls-files | grep -vE '\.(md|png|svg|conf|desc|qml|css|jsonc)$') + exit $fail + + - name: ShellCheck + run: | + shellcheck -S warning build.sh scripts/*.sh \ + airootfs/usr/local/bin/simos-* \ + airootfs/usr/local/bin/simulationos-install \ + airootfs/usr/share/simulationos/calamares/scripts/simulationos-deloop + + - name: Installer config is valid YAML + run: | + python3 - <<'PY' + import glob, sys, yaml + bad = 0 + files = glob.glob("airootfs/usr/share/simulationos/calamares/**/*.conf", recursive=True) + files += glob.glob("airootfs/usr/share/simulationos/calamares/branding/*/branding.desc") + for f in files: + try: + yaml.safe_load(open(f)) + except Exception as e: + print(f"YAML ERROR {f}: {e}"); bad = 1 + print(f"checked {len(files)} installer config files") + sys.exit(bad) + PY + + - name: Waybar config is valid JSON + run: | + python3 - <<'PY' + import json, re, sys + t = open("airootfs/etc/skel/.config/waybar/config.jsonc").read() + json.loads(re.sub(r'^\s*//.*$', '', t, flags=re.M)) + print("waybar config.jsonc OK") + PY + + - name: No secrets committed + run: | + # Deliberately narrow: private keys and obvious credential material. + if git grep -nIE 'BEGIN (RSA|OPENSSH|PGP|EC) PRIVATE KEY|ghp_[A-Za-z0-9]{30,}|AKIA[0-9A-Z]{16}' -- . ; then + echo "::error::possible secret committed"; exit 1 + fi + echo "no secrets found" + + - name: Validator must actually catch regressions + run: | + # A validator that never fails is worthless. Prove it still bites. + set -e + tmp=$(mktemp -d); cp -R . "$tmp/repo"; cd "$tmp/repo" + sed -i 's/vmlinuz-linux-cachyos/vmlinuz-linux/' efiboot/loader/entries/01-simulationos-linux.conf + if ./scripts/validate-profile.sh >/dev/null 2>&1; then + echo "::error::validator did NOT catch an injected kernel-name regression"; exit 1 + fi + echo "validator correctly rejected the injected regression" diff --git a/.gitignore b/.gitignore index b08c068..6dfb260 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ # mkarchiso build artefacts /work/ /out/ +# local pacman package cache used by the containerised builder +/.cache/ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..2a9b41d --- /dev/null +++ b/build.sh @@ -0,0 +1,209 @@ +#!/usr/bin/env bash +# +# build.sh - the single entry point for building SimulationOS. +# +# ./build.sh +# +# This is the ONE build engine. Local developers, CI and the release pipeline +# all call this same script, so a local build and a CI build cannot drift. +# +# It does not itself know how to build an ISO. It works out *where* the build +# can legally happen and then runs scripts/build-iso.sh there: +# +# Arch Linux x86_64 -> run natively +# any other Linux/macOS -> run inside an x86_64 Arch container +# +# SimulationOS targets x86_64. On an arm64 host the container is forced to +# linux/amd64 so we can never silently emit an arm64 ISO. +# +set -euo pipefail + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +IMAGE="${SIMOS_BUILDER_IMAGE:-docker.io/archlinux:latest}" +TARGET_ARCH="x86_64" + +CLEAN=0 +NO_CACHE=0 +VERBOSE=0 +FORCE_NATIVE=0 +FORCE_CONTAINER=0 +VALIDATE_ONLY=0 + +red() { printf '\033[31m%s\033[0m\n' "$*" >&2; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +info() { printf '\033[36m==>\033[0m %s\n' "$*"; } +die() { red "ERROR: $*"; exit 1; } + +usage() { + cat <<'EOF' +usage: ./build.sh [options] + + --clean remove work/ and out/ before building + --no-cache do not reuse the host pacman package cache + --validate run static validation only, then stop + --native force a native build (fails unless Arch x86_64 + root) + --container force a containerised build even on Arch + --verbose verbose build output + -h, --help this message + +Environment: + SIMOS_BUILDER_IMAGE builder image (default docker.io/archlinux:latest) + SIMOS_CONTAINER podman | docker (default: whichever is present) + SIMOS_WORK_DIR work directory (default ./work) + SIMOS_OUT_DIR output directory (default ./out) + +Output: + out/simulationos--x86_64.iso (+ .sha256, build-info.json, build.log) +EOF +} + +while [ $# -gt 0 ]; do + case "$1" in + --clean) CLEAN=1 ;; + --no-cache) NO_CACHE=1 ;; + --validate) VALIDATE_ONLY=1 ;; + --native) FORCE_NATIVE=1 ;; + --container) FORCE_CONTAINER=1 ;; + --verbose) VERBOSE=1 ;; + -h|--help) usage; exit 0 ;; + *) die "unknown option: $1 (try --help)" ;; + esac + shift +done + +HOST_OS="$(uname -s)" +HOST_ARCH="$(uname -m)" + +# ----------------------------------------------------------------- validation +# Static validation runs on the host: it is pure bash and needs no pacman, so +# there is no reason to spin up a container to be told the profile is broken. +info "Validating profile" +"$REPO/scripts/validate-profile.sh" || die "profile validation failed" +if [ "$VALIDATE_ONLY" -eq 1 ]; then + green "Validation only (--validate); stopping here." + exit 0 +fi + +# ------------------------------------------------------------- where to build +is_native_capable() { + [ "$HOST_OS" = "Linux" ] || return 1 + [ "$HOST_ARCH" = "$TARGET_ARCH" ] || return 1 + command -v mkarchiso >/dev/null 2>&1 || return 1 + return 0 +} + +MODE="container" +if [ "$FORCE_NATIVE" -eq 1 ]; then + MODE="native" +elif [ "$FORCE_CONTAINER" -eq 1 ]; then + MODE="container" +elif is_native_capable; then + MODE="native" +fi + +printf '\n' +info "Host: $HOST_OS $HOST_ARCH" +info "Target: linux $TARGET_ARCH" +info "Builder: $MODE" +printf '\n' + +# ---------------------------------------------------------------- clean first +if [ "$CLEAN" -eq 1 ]; then + "$REPO/scripts/clean-build.sh" +fi + +# -------------------------------------------------------------- native build +if [ "$MODE" = "native" ]; then + is_native_capable || die "--native requested but this host cannot build natively + (need Linux $TARGET_ARCH with mkarchiso installed; this is $HOST_OS $HOST_ARCH)." + [ "$(id -u)" -eq 0 ] || die "a native build must run as root. Try: sudo ./build.sh" + exec "$REPO/scripts/build-iso.sh" +fi + +# ----------------------------------------------------------- container build +RUNTIME="${SIMOS_CONTAINER:-}" +if [ -z "$RUNTIME" ]; then + for c in podman docker; do + command -v "$c" >/dev/null 2>&1 && { RUNTIME="$c"; break; } + done +fi +[ -n "$RUNTIME" ] || die "no container runtime found. Install podman or docker, + or build on an Arch Linux $TARGET_ARCH host and use --native." + +"$RUNTIME" info >/dev/null 2>&1 \ + || die "$RUNTIME is installed but its daemon is not responding. Start it and retry." + +EMULATED=0 +if [ "$HOST_ARCH" != "$TARGET_ARCH" ]; then + EMULATED=1 + red "NOTE: host is $HOST_ARCH but the target is $TARGET_ARCH." + red " The builder runs under emulation, which is CORRECT but SLOW." + red " A native x86_64 machine will be dramatically faster." + printf '\n' +fi + +CACHE_ARGS=() +if [ "$NO_CACHE" -eq 0 ]; then + CACHE_DIR="$REPO/.cache/pacman" + mkdir -p "$CACHE_DIR" + CACHE_ARGS=(-v "$CACHE_DIR:/var/cache/pacman/pkg") + info "Reusing package cache at .cache/pacman (use --no-cache to disable)" +fi + +# mkarchiso needs loop devices, mount and squashfs; those require elevated +# container privileges. This is the same model CachyOS uses in its CI. +info "Starting $RUNTIME builder ($IMAGE, platform linux/amd64)" +exec "$RUNTIME" run --rm -i \ + --platform linux/amd64 \ + --privileged \ + -v "$REPO:/simulationos" \ + "${CACHE_ARGS[@]}" \ + -w /simulationos \ + -e "SIMOS_VERBOSE=$VERBOSE" \ + -e "SIMOS_EMULATED=$EMULATED" \ + "$IMAGE" \ + bash -euo pipefail -c ' + printf "\033[36m==>\033[0m builder arch: $(uname -m)\n" + [ "$(uname -m)" = "x86_64" ] || { echo "ERROR: builder is not x86_64"; exit 1; } + + # pacman >=7 sandboxes downloads with seccomp and an unprivileged + # user. Under qemu-user emulation that syscall filter fails outright: + # error restricting syscalls via seccomp: 22! (EINVAL) + # It must be disabled BEFORE the first pacman call, and only for this + # throwaway builder. Signature verification is untouched and the + # committed pacman.conf is never modified - validate-profile.sh fails + # if DisableSandbox ever appears in a committed config. + if [ "${SIMOS_EMULATED:-0}" = "1" ]; then + printf "\033[33m==>\033[0m Emulated builder: disabling the pacman sandbox (this container only)\n" + grep -q "^DisableSandbox" /etc/pacman.conf \ + || sed -i "0,/^\[options\]/s//[options]\nDisableSandbox/" /etc/pacman.conf + SIMOS_PACMAN_CONF=/tmp/pacman.emulated.conf + sed "0,/^\[options\]/s//[options]\nDisableSandbox/" \ + /simulationos/pacman.conf > "$SIMOS_PACMAN_CONF" + export SIMOS_PACMAN_CONF + # Belt and braces: the conf setting is not always honoured for + # pacman calls made here, so pass the flag explicitly too. + PACFLAGS="--disable-sandbox" + fi + PACFLAGS="${PACFLAGS:-}" + + printf "\033[36m==>\033[0m Refreshing keyring and installing archiso\n" + pacman $PACFLAGS -Sy --noconfirm --needed archlinux-keyring >/dev/null + pacman $PACFLAGS -S --noconfirm --needed archiso >/dev/null + + printf "\033[36m==>\033[0m Trusting the CachyOS signing key\n" + pacman-key --init >/dev/null 2>&1 || true + pacman-key --populate archlinux >/dev/null 2>&1 || true + pacman-key --recv-keys F3B607488DB35A47 --keyserver keyserver.ubuntu.com >/dev/null 2>&1 \ + || { echo "ERROR: could not fetch the CachyOS signing key"; exit 1; } + pacman-key --lsign-key F3B607488DB35A47 >/dev/null 2>&1 + + # Install the keyring through the profile pacman.conf so there is no + # hardcoded package-version URL to go stale. + pacman $PACFLAGS -Sy --noconfirm --needed --config "${SIMOS_PACMAN_CONF:-/simulationos/pacman.conf}" \ + cachyos-keyring cachyos-mirrorlist >/dev/null \ + || { echo "ERROR: could not install cachyos-keyring"; exit 1; } + pacman-key --populate cachyos >/dev/null 2>&1 || true + + exec /simulationos/scripts/build-iso.sh + ' diff --git a/docs/adr/0001-archiso-baseline.md b/docs/adr/0001-archiso-baseline.md new file mode 100644 index 0000000..1d6bc51 --- /dev/null +++ b/docs/adr/0001-archiso-baseline.md @@ -0,0 +1,24 @@ +# ADR 0001 - archiso is the architectural baseline + +**Status:** accepted · 2026-09-28 + +## Context +The repository began as a copy of Arch's `releng` profile and had drifted: +six removed bootmode names, a variable (`iso_icon`) mkarchiso never reads, and +a custom mkinitcpio preset upstream had deleted. + +## Decision +Track current archiso exactly. Do not fork it, do not reimplement it, do not +keep deprecated spellings because an old profile used them. + +## Alternatives +- **Fork archiso** — rejected: permanent maintenance cost for no benefit yet. +- **Copy the CachyOS ISO profile** — rejected, see ADR 0003. + +## Consequences +We inherit archiso's improvements for free, but must re-read its CHANGELOG at +each upgrade. `validate-profile.sh` fails on deprecated bootmodes and on +variables mkarchiso does not consume, so drift is caught mechanically. + +## Sources +`docs/research/sources.md` § archiso. diff --git a/docs/adr/0002-kernel-linux-cachyos.md b/docs/adr/0002-kernel-linux-cachyos.md new file mode 100644 index 0000000..deee86b --- /dev/null +++ b/docs/adr/0002-kernel-linux-cachyos.md @@ -0,0 +1,27 @@ +# ADR 0002 - Ship linux-cachyos + +**Status:** accepted · 2026-09-28 + +## Context +SimulationOS' stated identity includes the CachyOS kernel. The repository +already listed `linux-cachyos` but no boot path referenced its actual files. + +## Decision +Ship `linux-cachyos` as the only kernel, and derive every filename from its +`pkgbase`: `vmlinuz-linux-cachyos`, `initramfs-linux-cachyos.img`, preset +`linux-cachyos.preset`. Delete the hand-written preset. + +## Alternatives +- **Stock `linux`** — simpler, but discards a defining project property. +- **Keep a custom preset** — rejected: presets are keyed to `pkgbase`, so a + `linux.preset` is orphaned; upstream removed theirs for the same reason. + +## Consequences +We depend on the CachyOS repository for the kernel, so a CachyOS outage blocks +builds. No fallback/LTS kernel yet — see ADR 0012. +The validator derives expected filenames from `packages.x86_64` and fails on +any drift, which is how the original three-way mismatch would have been caught. + +## Sources +`linux-cachyos/PKGBUILD:177,612,615`. Resolution verified in an x86_64 +container: `cachyos/linux-cachyos-7.2.7-1`. diff --git a/docs/adr/0003-cachyos-as-supplier.md b/docs/adr/0003-cachyos-as-supplier.md new file mode 100644 index 0000000..88e4ac0 --- /dev/null +++ b/docs/adr/0003-cachyos-as-supplier.md @@ -0,0 +1,27 @@ +# ADR 0003 - CachyOS is a component supplier, not a template + +**Status:** accepted · 2026-09-28 + +## Context +CachyOS solves several problems we also have. The temptation is to copy its +ISO profile and repo ordering wholesale. + +## Decision +Depend on CachyOS for what is genuinely exclusive to it and nothing else: +`linux-cachyos`, `cachyos-calamares`, `ckbcomp`, `mkinitcpio-openswap`, +`cachyos-keyring`, `cachyos-mirrorlist`. List Arch repositories **before** +`[cachyos]` so a package present in both comes from Arch. + +## Alternatives +- **CachyOS ordering ([cachyos] first)** — rejected: pulls the whole optimised + package set and makes our dependency surface unpredictable. +- **Vendor their files** — rejected: copying code we do not understand is how + the inherited `chwd`-dependent dead scripts got here in the first place. + +## Consequences +Predictable dependency graph, verified: of 577 resolved packages, exactly 6 +come from `[cachyos]`. We forgo CachyOS' x86-64-v3/v4 optimised builds; if we +later want them, that is a deliberate, separately measured decision. + +## Sources +`docs/research/sources.md` § CachyOS; container resolution run 2026-09-28. diff --git a/docs/adr/0004-hyprland-desktop.md b/docs/adr/0004-hyprland-desktop.md new file mode 100644 index 0000000..fc5b518 --- /dev/null +++ b/docs/adr/0004-hyprland-desktop.md @@ -0,0 +1,26 @@ +# ADR 0004 - Hyprland from stable repositories + +**Status:** accepted · 2026-09-28 + +## Context +Hyprland is the SimulationOS desktop. Its ecosystem (portal, polkit agent, +lock, paper) has coupled ABI expectations across releases. + +## Decision +Use only official Arch repository packages. No `-git`, no AUR in the base OS. +One component per job: `hyprland`, `hyprpaper`, `hyprlock`, +`hyprpolkitagent`, `waybar`, `wofi`, `kitty`, `thunar`, `mako`. + +## Alternatives +- **`hyprland-git`** — rejected: upstream warns about interlocking ABI + dependencies; a distribution baseline cannot chase that. +- **Ship several launchers/terminals** — rejected: more to test, no user benefit. + +## Consequences +We lag upstream Hyprland slightly. In exchange the desktop is reproducible and +every referenced binary is guaranteed present — enforced by the validator, +which fails if `hyprland.conf` launches anything not in `packages.x86_64`. + +## Sources +`docs/research/sources.md` § Hyprland. Note `swww` and `rofi-wayland` are not +in the Arch repositories, which is why `hyprpaper` and `wofi` were chosen. diff --git a/docs/adr/0005-sddm-display-manager.md b/docs/adr/0005-sddm-display-manager.md new file mode 100644 index 0000000..4287972 --- /dev/null +++ b/docs/adr/0005-sddm-display-manager.md @@ -0,0 +1,26 @@ +# ADR 0005 - SDDM with an X11 greeter launching a Wayland session + +**Status:** provisional · 2026-09-28 + +## Context +We need autologin on the live medium and a normal greeter on the installed +system. SDDM's Wayland greeter needs a compositor. + +## Decision +SDDM with its default `DisplayServer=x11` greeter, launching +`hyprland.desktop` from `/usr/share/wayland-sessions`. Theme `maldives`. + +## Alternatives +- **SDDM Wayland greeter** — needs `weston` (`CompositorCommand=weston + --shell=kiosk`); an extra compositor purely for the login screen. +- **greetd + tuigreet** — lighter, but a text greeter undercuts "polished". +- **No display manager** — rejected: the installed system needs a real login. + +## Consequences +Xorg is pulled in as a hard dependency of `sddm` regardless. Revisit when +SDDM's Wayland greeter no longer requires a separate compositor — hence +*provisional*. The previous config specified theme `breeze`, which needs +Plasma and was not installed; only `elarun`, `maldives`, `maya` ship with SDDM. + +## Sources +`docs/research/sources.md` § Display manager. diff --git a/docs/adr/0006-networkmanager.md b/docs/adr/0006-networkmanager.md new file mode 100644 index 0000000..7d39bc7 --- /dev/null +++ b/docs/adr/0006-networkmanager.md @@ -0,0 +1,23 @@ +# ADR 0006 - NetworkManager is the only network stack + +**Status:** accepted · 2026-09-28 + +## Context +Upstream releng uses systemd-networkd + iwd + systemd-resolved, suited to a +headless install medium. The repository had inherited that while also enabling +NetworkManager. + +## Decision +NetworkManager alone. `wpa_supplicant` stays installed but is D-Bus-activated, +not statically enabled. systemd-resolved is dropped, so NetworkManager owns +`/etc/resolv.conf`. + +## Alternatives +- **networkd + resolved** — better for servers, no desktop GUI story. +- **NetworkManager + systemd-resolved** — viable and adds DNS caching/DNSSEC, + but is a second moving part; revisit once the base is proven. + +## Consequences +One component owns connectivity. Calamares' `networkcfg` migrates live +connections into the installed system, so Wi-Fi joined during installation +keeps working. The validator fails if both stacks are ever enabled at once. diff --git a/docs/adr/0007-pipewire-audio.md b/docs/adr/0007-pipewire-audio.md new file mode 100644 index 0000000..6901da2 --- /dev/null +++ b/docs/adr/0007-pipewire-audio.md @@ -0,0 +1,27 @@ +# ADR 0007 - PipeWire + WirePlumber, enabled explicitly + +**Status:** accepted · 2026-09-28 + +## Context +The profile shipped no audio stack. Arch's pipewire packages ship user units +but **no** `.wants` symlinks, and Arch does not run `systemctl preset` at +package-install time. + +## Decision +PipeWire with `pipewire-pulse`, `pipewire-alsa`, `pipewire-jack` and +WirePlumber, enabled by explicit symlinks under `/etc/systemd/user/` derived +from each unit's real `[Install]` section. + +## Alternatives +- **Rely on presets** — rejected: not applied on Arch at install time; audio + would silently not start. +- **PulseAudio** — rejected: PipeWire is the current desktop default and gives + JACK and screen-share audio in one stack. + +## Consequences +Enablement is visible in the repository rather than implicit. If upstream +changes an `[Install]` section, our symlinks go stale — the validator flags +dangling systemd symlinks. + +## Sources +Unit files extracted from the actual packages; see `docs/research/sources.md` § Audio. diff --git a/docs/adr/0008-calamares-offline.md b/docs/adr/0008-calamares-offline.md new file mode 100644 index 0000000..54e6048 --- /dev/null +++ b/docs/adr/0008-calamares-offline.md @@ -0,0 +1,32 @@ +# ADR 0008 - Calamares, offline install, SimulationOS-owned config directory + +**Status:** accepted · 2026-09-28 + +## Context +Calamares is not in the Arch repositories; `cachyos-calamares` provides it. +That package owns ~60 files under `/etc/calamares/modules/` and declares no +`backup` array. archiso copies `airootfs/` **before** pacstrap, so shipping +our configs at those paths is a hard file conflict. + +## Decision +Ship a self-contained tree at `/usr/share/simulationos/calamares` and launch +`calamares -c /usr/share/simulationos/calamares`. Install offline via +`unpackfs`, then strip live-only configuration with `simulationos-deloop`. + +## Alternatives +- **Files in `/etc/calamares`** — breaks the build. +- **Suffixed module configs + `instances:`** — works, but we override nearly + every module, so it means ~20 oddly named files sitting beside CachyOS'. +- **Fork Calamares** — rejected, large permanent cost. +- **Online `pacstrap` install** — more flexible, needs network and is slower; + offline matches "the live desktop is what you get". + +## Consequences +Verified in Calamares source that `-c` relocates settings, module configs +**and** branding, so CachyOS defaults cannot leak in. The trade-off is that +`unpackfs` copies the live filesystem verbatim, which makes `deloop` a +correctness-critical component — including locking root, which the live medium +leaves passwordless. `deloop` must run before `initcpio`. + +## Sources +`docs/research/sources.md` § Calamares. diff --git a/docs/adr/0009-grub-for-installed-system.md b/docs/adr/0009-grub-for-installed-system.md new file mode 100644 index 0000000..1287126 --- /dev/null +++ b/docs/adr/0009-grub-for-installed-system.md @@ -0,0 +1,23 @@ +# ADR 0009 - GRUB on the installed system + +**Status:** provisional · 2026-09-28 + +## Context +The ISO boots via syslinux (BIOS) and systemd-boot (UEFI). The installed +system's bootloader is a separate decision; the inherited installer script +offered five choices, none of them wired up. + +## Decision +One bootloader for the installed system: GRUB, via Calamares' `bootloader` +module with `GRUB_DISTRIBUTOR="SimulationOS"`. + +## Alternatives +- **systemd-boot** — simpler and faster, but UEFI-only and needs the kernel on + the ESP, which complicates sizing. +- **Offer a choice (GRUB/systemd-boot/rEFInd/Limine)** — rejected for Alpha: + each option multiplies the install-test matrix. + +## Consequences +One code path covering BIOS and UEFI. Slower boot than systemd-boot. Marked +*provisional*: once the install test is automated, systemd-boot becomes cheap +to evaluate on measured boot-time evidence. diff --git a/docs/adr/0010-skel-as-config-source.md b/docs/adr/0010-skel-as-config-source.md new file mode 100644 index 0000000..76cfb68 --- /dev/null +++ b/docs/adr/0010-skel-as-config-source.md @@ -0,0 +1,29 @@ +# ADR 0010 - /etc/skel is the single source of desktop configuration + +**Status:** accepted · 2026-09-28 + +## Context +The live user's dotfiles and the installed user's dotfiles must not diverge. +An earlier reading suggested archiso never applies `/etc/skel`; reading +`mkarchiso` disproved that. + +## Decision +Keep desktop configuration only in `airootfs/etc/skel/.config/`. archiso's +`_make_customize_airootfs` materialises `/home/liveuser` from it with correct +ownership, and Calamares' `users` module copies the same skel for the +installed user. + +## Alternatives +- **Ship `airootfs/home/liveuser/`** — duplicates every dotfile and needs + manual recursive `file_permissions`. +- **A first-login provisioning script** — more machinery, more to fail. + +## Consequences +Live and installed desktops are identical by construction. Users who edit +their dotfiles are never overwritten, because skel is only copied at account +creation. The cost: changing a default does not reach existing users — a +migration story is still **open** if we later package the config (see the +"packaged config" item in `docs/research/sources.md`). + +## Sources +`mkarchiso:_make_customize_airootfs()`, invoked at line 1852 (after pacstrap). diff --git a/docs/adr/0011-filesystem-ext4-default.md b/docs/adr/0011-filesystem-ext4-default.md new file mode 100644 index 0000000..2579cf3 --- /dev/null +++ b/docs/adr/0011-filesystem-ext4-default.md @@ -0,0 +1,23 @@ +# ADR 0011 - ext4 is the default filesystem + +**Status:** provisional · 2026-09-28 + +## Context +Btrfs is fashionable for snapshot-based rollback. Snapshots alone are not a +rollback system. + +## Decision +Default to ext4. Offer Btrfs, XFS and f2fs in the installer, with Btrfs +subvolumes (`@`, `@home`, `@log`, `@pkg`) pre-configured for anyone choosing it. +Do **not** advertise rollback. + +## Alternatives +- **Btrfs by default with snapshots** — rejected for now: a credible rollback + promise needs snapshot-on-transaction, boot integration, kernel/initramfs + handling, pruning and tested recovery. We cannot maintain or test that yet, + and a half-working rollback is worse than none. + +## Consequences +Simple, predictable default. Revisit when we can own the whole lifecycle *and* +test it automatically. Promoting this to Btrfs-by-default requires an +automated rollback test in CI first. diff --git a/docs/adr/0012-no-custom-kernel-yet.md b/docs/adr/0012-no-custom-kernel-yet.md new file mode 100644 index 0000000..9c27d2b --- /dev/null +++ b/docs/adr/0012-no-custom-kernel-yet.md @@ -0,0 +1,20 @@ +# ADR 0012 - No SimulationOS kernel fork + +**Status:** accepted · 2026-09-28 + +## Context +Distributions often start patching kernels early. We already consume a tuned +kernel via `linux-cachyos`. + +## Decision +No SimulationOS kernel. Consume `linux-cachyos` as a package. + +## Before this may be revisited, all of these are required +a measured problem · a benchmark demonstrating the gain · a maintenance owner · +a security-update plan · kernel CI · a tested fallback kernel. + +## Consequences +Zero kernel maintenance burden. **Gap:** there is currently no fallback kernel, +so a bad `linux-cachyos` update has no in-place escape hatch other than the +live ISO. Adding `linux-lts` as a recovery kernel is the next step here and is +tracked as a Phase 3 item. diff --git a/docs/adr/0013-profiles-as-metapackages.md b/docs/adr/0013-profiles-as-metapackages.md new file mode 100644 index 0000000..18b3417 --- /dev/null +++ b/docs/adr/0013-profiles-as-metapackages.md @@ -0,0 +1,29 @@ +# ADR 0013 - Capability profiles as meta-packages + +**Status:** open · 2026-09-28 + +## Context +SimulationOS targets programming, networking and gaming without installing all +three everywhere. The base must stay small. + +## Proposed decision +Express profiles as SimulationOS meta-packages +(`simulationos-dev-meta`, `-network-meta`, `-gaming-meta`, `-gaming-apps`), +selected in the installer and installable later, rather than as a shell script +that installs a list of packages. + +## Why this is still open +Meta-packages need a SimulationOS **repository**, which needs signing keys, +a keyring package, mirrors and package CI. Until that exists, the installer +can only select from Arch/CachyOS packages directly. + +## Evidence needed first +- repository hosting and signing-key custody +- whether `Group` or a meta-package gives better uninstall semantics +- the GPU/32-bit provider-ordering problem for gaming (ADR pending): a naive + Steam dependency pull can select the wrong vendor's 32-bit Vulkan driver + +## Interim +Profiles are **not** implemented. The base desktop is installed as-is. Doing +this badly (a shell script downloading dozens of packages post-install) would +be worse than waiting. diff --git a/docs/adr/0014-nftables-firewall.md b/docs/adr/0014-nftables-firewall.md new file mode 100644 index 0000000..7cb1d6e --- /dev/null +++ b/docs/adr/0014-nftables-firewall.md @@ -0,0 +1,30 @@ +# ADR 0014 - Default nftables policy + +**Status:** open · 2026-09-28 + +## Context +SimulationOS currently ships no firewall. Its exposure is presently low: the +live medium enables no listening network service (sshd is installed but +disabled, unlike upstream archiso). + +## Proposed decision +A minimal stateful nftables policy: drop inbound by default; accept +established/related and loopback; allow DHCP/DHCPv6 and essential ICMP/ICMPv6; +leave outbound open. + +## Why this is still open +Needs verification against real desktop traffic before being enabled by +default: mDNS/Avahi for printer and cast discovery, KDE Connect, Steam +in-home streaming and local multiplayer, and WireGuard/OpenVPN interfaces all +have inbound expectations. Shipping a policy that silently breaks LAN gaming +is worse than shipping none. + +## Evidence needed first +- an inbound-traffic inventory for a normal desktop session +- confirmation that ICMPv6 rules do not break IPv6 SLAAC/PMTUD +- a documented way for a network engineer to extend the ruleset + +## Interim +No firewall is enabled, and the attack surface is kept small instead by not +enabling listening services. This is recorded as a **known gap**, not as a +decision that a firewall is unnecessary. diff --git a/docs/adr/0015-single-build-engine.md b/docs/adr/0015-single-build-engine.md new file mode 100644 index 0000000..614485c --- /dev/null +++ b/docs/adr/0015-single-build-engine.md @@ -0,0 +1,26 @@ +# ADR 0015 - One build engine: ./build.sh + +**Status:** accepted · 2026-09-28 + +## Context +The classic distribution failure is a local build that differs from CI, which +differs from the release build. + +## Decision +`./build.sh` is the only entry point, for developers, CI and releases. It +chooses *where* to build (native Arch x86_64, or an x86_64 Arch container) and +then runs the same `scripts/build-iso.sh` either way. CI must never implement +its own build procedure. + +## Alternatives +- **A separate CI workflow that calls mkarchiso directly** — rejected; this is + exactly the drift we are avoiding. + +## Consequences +An arm64 host builds through emulation rather than silently producing an arm64 +ISO. One environment-specific concession was needed and is scoped tightly: +pacman's seccomp sandbox fails with `EINVAL` under qemu-user emulation, so the +**container** patches a throwaway copy of `pacman.conf` with `DisableSandbox`. +The committed `pacman.conf` never sets it, signature verification is never +disabled, and `validate-profile.sh` fails if `DisableSandbox` or +`SigLevel = Never` ever appears in a committed config. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..ae47d0f --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,25 @@ +# Architecture Decision Records + +One short record per significant decision: context, decision, alternatives, +consequences, sources. + +Status values: `accepted` · `provisional` (right for Alpha, revisit later) · +`open` (needs evidence before implementing) · `superseded`. + +| ADR | Decision | Status | +|---|---|---| +| [0001](0001-archiso-baseline.md) | archiso is the baseline; do not fork it | accepted | +| [0002](0002-kernel-linux-cachyos.md) | Ship `linux-cachyos`; no custom kernel | accepted | +| [0003](0003-cachyos-as-supplier.md) | CachyOS is a component supplier, not a template | accepted | +| [0004](0004-hyprland-desktop.md) | Hyprland from stable repos; no `-git` | accepted | +| [0005](0005-sddm-display-manager.md) | SDDM, X11 greeter, Wayland session | provisional | +| [0006](0006-networkmanager.md) | NetworkManager alone | accepted | +| [0007](0007-pipewire-audio.md) | PipeWire + WirePlumber, explicitly enabled | accepted | +| [0008](0008-calamares-offline.md) | Calamares, offline unpackfs, own config dir | accepted | +| [0009](0009-grub-for-installed-system.md) | GRUB on the installed system | provisional | +| [0010](0010-skel-as-config-source.md) | `/etc/skel` is the single dotfile source | accepted | +| [0011](0011-filesystem-ext4-default.md) | ext4 default; Btrfs only with a full rollback lifecycle | provisional | +| [0012](0012-no-custom-kernel-yet.md) | No SimulationOS kernel fork | accepted | +| [0013](0013-profiles-as-metapackages.md) | Capability profiles as meta-packages | open | +| [0014](0014-nftables-firewall.md) | nftables default policy | open | +| [0015](0015-single-build-engine.md) | One build engine: `./build.sh` | accepted | diff --git a/docs/research/sources.md b/docs/research/sources.md new file mode 100644 index 0000000..88d9d9a --- /dev/null +++ b/docs/research/sources.md @@ -0,0 +1,187 @@ +# SimulationOS research sources + +Primary-source findings behind the architecture. Conclusions only — upstream +documentation is not copied here. Every entry records what was checked, where, +and when. + +All entries accessed **2026-09-27 / 2026-09-28** unless noted. + +--- + +## archiso + +**Source:** `https://gitlab.archlinux.org/archlinux/archiso` — `archiso/mkarchiso` +(2011 lines), `docs/README.profile.rst`, `CHANGELOG.rst`, `configs/releng/`. + +| Finding | Evidence | +|---|---| +| Valid boot modes are exactly `bios.syslinux`, `uefi.grub`, `uefi.systemd-boot` | `README.profile.rst`, bootmodes section | +| The six older names are deprecated shims that warn and alias | `mkarchiso:1001-1096` | +| `uefi.systemd-boot` + `uefi.grub` together are **rejected** | `mkarchiso:1050-1052` | +| `uefi.systemd-boot` covers x64 **and** mixed-mode IA32 on x86_64 | `mkarchiso:906-910` | +| `grub/loopback.cfg` is installed when **no** `*grub*` bootmode is set | `mkarchiso:_make_bootmodes()`, lines 473-481 | +| `grub/grub.cfg` is read **only** by `uefi.grub` | `mkarchiso:745` | +| `buildmodes` default is `iso`; upstream removed it from its profiles | `CHANGELOG.rst` "Removed" | +| Upstream removed its custom `/etc/mkinitcpio.d/linux.preset` | `CHANGELOG.rst`: "The default preset is good enough" | +| `broadcom-wl` was removed from Arch; upstream dropped its modprobe drop-in | `CHANGELOG.rst` | +| `airootfs` is copied **before** pacstrap | `mkarchiso:1846` then `:1847` | +| `/etc/skel` **is** copied into every `airootfs/etc/passwd` home with UID 1000-59999, created and `chown -hR`'d | `_make_customize_airootfs()`, runs at `mkarchiso:1852` | +| `file_permissions` with a trailing `/` applies recursively | `README.profile.rst`; `_make_custom_airootfs()` | +| ISO filename is `${iso_name}-${iso_version}-${arch}.iso` | `mkarchiso:1930` | +| Boot-entry placeholders: `%ARCHISO_LABEL% %ARCHISO_UUID% %INSTALL_DIR% %KERNEL_PARAMS% %ARCH%` | `mkarchiso:884-889` | +| Microcode is handled automatically; external ucode images are injected only when not embedded | `_check_if_initramfs_has_ucode()`, `mkarchiso:620-630` | +| SquashFS lands at `//airootfs.sfs`; the medium mounts at `/run/archiso/bootmnt` | `mkarchiso:190`; `mkinitcpio-archiso` hook line 168 | +| `mkinitcpio` + `mkinitcpio-archiso` are mandatory packages | `README.profile.rst` | +| `mkarchiso` can run unprivileged via `unshare` (`-N`) when user namespaces exist | `_make_packages()`, `pacstrap -N` branch | + +**Decision:** treat archiso as the architectural baseline and do not fork it. + +--- + +## pacman / packaging + +| Finding | Evidence | +|---|---| +| `/etc/pacman.conf` is in the `pacman` package's `backup` array | Arch `pacman` PKGBUILD, `backup=(etc/pacman.conf ...)` | +| `/etc/passwd`, `/etc/group`, `/etc/gshadow`, `/etc/hosts`, `/etc/issue` are in `filesystem`'s `backup` array | Arch `filesystem` PKGBUILD | +| Arch ships `system-preset/99-default.preset` = `disable *`, but **no** catch-all user preset | `systemd` package file list | + +**Consequence:** an `airootfs` overlay is only safe at a package-owned path if +that path is a backup file. This is why the SimulationOS Calamares config is +*not* in `/etc/calamares`, and why the hand-written mirrorlist was removed. +It is also why pipewire user units are enabled by explicit symlink rather than +by relying on presets. + +--- + +## CachyOS + +**Source:** `github.com/CachyOS/{linux-cachyos,cachyos-calamares,CachyOS-Live-ISO,cachyos-repo-add-script}`, +plus the live repository database at `mirror.cachyos.org/repo/x86_64/cachyos/`. + +| Finding | Evidence | +|---|---| +| `pkgbase=linux-cachyos`; image installed as `$modulesdir/vmlinuz` with a `pkgbase` marker | `linux-cachyos/PKGBUILD:177,612,615` | +| Therefore the installed files are `vmlinuz-linux-cachyos`, `initramfs-linux-cachyos.img`, preset `linux-cachyos.preset` | standard mkinitcpio hook behaviour | +| `cachyos-calamares` owns ~60 files under `/etc/calamares/modules/` and declares **no** `%BACKUP%` | `cachyos.files` / `cachyos.db` entry for `cachyos-calamares-3.4.2-4` | +| It `provides`/`conflicts` `calamares`; deps include `ckbcomp`, `mkinitcpio-openswap`, `kpmcore`, qt6 stack | `cachyos.db` `%DEPENDS%` | +| `ckbcomp` and `mkinitcpio-openswap` exist **only** in the CachyOS repo | Arch package search returns NONE; present in `cachyos` | +| Repo provisioning is key `F3B607488DB35A47` via keyserver.ubuntu.com, `--lsign-key`, then the keyring package | `cachyos-repo.sh:156-164` | +| `cachyos-calamares` is a full Calamares **fork**, not just configs | repository layout (`src/`, `CMakeLists.txt`) | + +**Decision:** use CachyOS as a component supplier only. Do not fork Calamares, +do not adopt the CachyOS ISO profile, do not mirror their repo ordering. + +**Verified 2026-09-28** by resolving the whole SimulationOS package set inside a +real `linux/amd64` Arch container: 144 requested → 577 resolved, of which +exactly 6 come from `[cachyos]`. + +--- + +## Calamares + +**Source:** `github.com/CachyOS/cachyos-calamares` (the build SimulationOS +installs) — `src/calamares/main.cpp`, `CalamaresApplication.cpp`, +`src/libcalamares/modulesystem/Module.cpp`. + +| Finding | Evidence | +|---|---| +| `-c/--config ` calls `Calamares::setAppDataDir(dir)` | `main.cpp:75-95` | +| When appDataDir is overridden, module configs resolve **only** under `/modules/` | `Module.cpp:50-56` | +| When appDataDir is overridden, branding resolves **only** under `/branding/` | `CalamaresApplication.cpp: brandingFileCandidates()` | +| Otherwise branding prefers `/etc/calamares/branding/` then `/usr/share/calamares/branding/` | same function | +| `/etc/calamares/settings.conf` is *not* shipped by the package; `/usr/share/calamares/settings{,_offline,_online}.conf` are | `cachyos-calamares` file list | +| Modules available in 3.4.2 include `unpackfs`, `removeuser`, `packages`, `services-systemd`, `initcpiocfg`, `initcpio`, `grubcfg`, `bootloader`, `networkcfg`, `machineid`, `summary` | `usr/lib/calamares/modules/` listing | + +**Decision:** ship a self-contained config tree at +`/usr/share/simulationos/calamares` and launch with `-c`. This is the only +approach that is simultaneously conflict-free and immune to silently +inheriting CachyOS defaults. + +--- + +## Hyprland and the Wayland desktop + +Package availability confirmed against `archlinux.org/packages` (official repos +only; no AUR, no `-git`). + +| Area | Chosen | Note | +|---|---|---| +| compositor | `hyprland` (extra) | ships `/usr/share/wayland-sessions/hyprland.desktop` | +| bar / launcher / term / files | `waybar`, `wofi`, `kitty`, `thunar` + `gvfs` | all extra | +| notifications | `mako` | simplest reliable daemon | +| polkit agent | `hyprpolkitagent` | ships `hyprpolkitagent.service` user unit | +| lock | `hyprlock` | so the power menu's Lock is not a no-op | +| wallpaper | `hyprpaper` only | `swww` is **not** in the Arch repos | +| portals | `xdg-desktop-portal` + `-hyprland` + `-gtk` | | +| screenshots / clipboard | `grim`, `slurp`, `wl-clipboard`, `cliphist` | | + +`rofi-wayland` and `libva-mesa-driver` no longer exist as separate packages. + +**Decision:** stable repo packages only. Hyprland's ecosystem has coupled ABI +expectations, so `-git` packages are out of scope for a distribution baseline. + +--- + +## Display manager + +**Source:** the `sddm` 0.21.0-7 package contents and Arch PKGBUILD. + +| Finding | Evidence | +|---|---| +| Default `DisplayServer=x11`; `xorg-server` and `xorg-xauth` are **hard dependencies** | `/usr/lib/sddm/sddm.conf.d/default.conf`; PKGBUILD `depends=` | +| The Wayland greeter needs `weston` (`CompositorCommand=weston --shell=kiosk`) | same default.conf | +| Bundled themes are exactly `elarun`, `maldives`, `maya` | `/usr/share/sddm/themes/` | +| SDDM ships **no** `/etc/sddm.conf` or `/etc/sddm.conf.d/`, so those paths are free | package file list | +| Wayland sessions are read from `/usr/share/wayland-sessions` | default.conf `[Wayland] SessionDir` | + +**Decision:** X11 greeter launching a Wayland session; theme `maldives` +(guaranteed present). The previously configured `breeze` needs Plasma. + +--- + +## Audio + +`[Install]` sections read from the actual packaged unit files, not from +documentation: + +| Unit | `[Install]` | +|---|---| +| `pipewire.socket` | `WantedBy=sockets.target` | +| `pipewire.service` | `WantedBy=default.target`, `Also=pipewire.socket` | +| `pipewire-pulse.socket` | `WantedBy=sockets.target` | +| `pipewire-pulse.service` | `WantedBy=default.target` | +| `wireplumber.service` | `WantedBy=pipewire.service`, `Alias=pipewire-session-manager.service` | + +None of these packages ship `.wants` symlinks. + +**Decision:** PipeWire + WirePlumber, enabled by explicit symlinks under +`/etc/systemd/user/`. **Rejected:** relying on systemd presets (not applied at +package-install time on Arch) and running PulseAudio alongside PipeWire. + +--- + +## Networking + +**Decision:** NetworkManager alone, with `wpa_supplicant` installed but +D-Bus-activated. +**Alternatives considered:** upstream releng's `systemd-networkd` + `iwd` + +`systemd-resolved` — rejected because it targets headless installs, has no +Hyprland-friendly GUI, and Calamares' `networkcfg` module migrates +NetworkManager connections into the installed system. +`systemd-resolved` was dropped with it so only one component owns +`/etc/resolv.conf`. + +--- + +## Still to research (not yet decided) + +These are deliberately **not** implemented yet; see `docs/adr/` for status. + +- nftables default policy +- filesystem strategy (ext4 vs Btrfs + whether we can own the full snapshot/rollback lifecycle) +- zram sizing, measured rather than copied +- kernel policy beyond `linux-cachyos` (fallback/LTS kernel) +- profile meta-package mechanism (package group vs meta-package) +- SimulationOS package repository, keyring and signing +- GPU driver selection matrix, especially 32-bit Vulkan provider ordering for Steam diff --git a/profiledef.sh b/profiledef.sh index 2f4fb75..10ff17c 100644 --- a/profiledef.sh +++ b/profiledef.sh @@ -27,7 +27,11 @@ bootmodes=('bios.syslinux' 'uefi.systemd-boot') arch="x86_64" -pacman_conf="pacman.conf" +# Overridable so the emulated build container can supply a copy with +# DisableSandbox set (pacman's seccomp sandbox returns EINVAL under qemu-user +# emulation). The committed pacman.conf never disables the sandbox, and +# validate-profile.sh enforces that. +pacman_conf="${SIMOS_PACMAN_CONF:-pacman.conf}" airootfs_image_type="squashfs" airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-b' '1M' '-Xdict-size' '1M') bootstrap_tarball_compression=('zstd' '-c' '-T0' '--auto-threads=logical' '--long' '-19') diff --git a/scripts/build-iso.sh b/scripts/build-iso.sh index f98d022..c78893c 100755 --- a/scripts/build-iso.sh +++ b/scripts/build-iso.sh @@ -52,7 +52,11 @@ if ! pacman -Q cachyos-keyring >/dev/null 2>&1; then red "Provision it with:" red " pacman-key --recv-keys $CACHYOS_KEY --keyserver keyserver.ubuntu.com" red " pacman-key --lsign-key $CACHYOS_KEY" - red " pacman -U $CACHYOS_MIRROR/cachyos-keyring-20240331-1-any.pkg.tar.zst" + red " pacman -Sy --config $REPO/pacman.conf cachyos-keyring cachyos-mirrorlist" + red "" + red "(Installing through the profile's own pacman.conf avoids a hardcoded" + red " package-version URL, which goes stale every time CachyOS rebuilds" + red " the keyring.)" exit 1 fi @@ -91,9 +95,11 @@ mkdir -p -- "$WORK" "$OUT" # -------------------------------------------------------------------- 5. build info "Running mkarchiso (this takes a while and needs ~20 GB free)" -set -x -mkarchiso -v -w "$WORK" -o "$OUT" "$REPO" -set +x +BUILD_LOG="$OUT/build.log" +BUILD_START="$(date -u +%s)" +set -o pipefail +mkarchiso -v -w "$WORK" -o "$OUT" "$REPO" 2>&1 | tee "$BUILD_LOG" +BUILD_END="$(date -u +%s)" # ------------------------------------------------------------- 6/7. artifact ISO="$(ls -1t "$OUT"/simulationos-*.iso 2>/dev/null | head -1)" @@ -102,11 +108,37 @@ ISO="$(ls -1t "$OUT"/simulationos-*.iso 2>/dev/null | head -1)" info "Generating SHA256 checksum" ( cd "$OUT" && sha256sum "$(basename "$ISO")" > "$(basename "$ISO").sha256" ) +info "Writing build-info.json" +# Everything a release engineer needs to answer "which commit produced this +# exact file, with which tools". +cat > "$OUT/build-info.json" </dev/null || stat -f%z "$ISO"), + "iso_version": "$(bash -c 'source "'"$REPO"'/profiledef.sh"; printf %s "$iso_version"')", + "target_arch": "$(bash -c 'source "'"$REPO"'/profiledef.sh"; printf %s "$arch"')", + "git_commit": "$(git -C "$REPO" rev-parse HEAD 2>/dev/null || echo unknown)", + "git_branch": "$(git -C "$REPO" rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)", + "git_dirty": $(test -n "$(git -C "$REPO" status --porcelain 2>/dev/null)" && echo true || echo false), + "archiso_version": "$(pacman -Q archiso 2>/dev/null | awk '{print $2}' || echo unknown)", + "pacman_version": "$(pacman -Q pacman 2>/dev/null | awk '{print $2}' || echo unknown)", + "build_date_utc": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "build_seconds": $(( BUILD_END - BUILD_START )), + "builder_host": "$(uname -srm)" +} +JSON +python3 -c "import json;json.load(open('$OUT/build-info.json'))" 2>/dev/null \ + && green " build-info.json is valid JSON" \ + || red " WARNING: build-info.json is not valid JSON" + green "" green "Build complete" green " ISO: $ISO" green " Size: $(du -h "$ISO" | cut -f1)" green " SHA256: $(cut -d' ' -f1 < "$ISO.sha256")" green " Checksum: $ISO.sha256" +green " Metadata: $OUT/build-info.json" +green " Log: $BUILD_LOG" green "" green "Boot it with: ./scripts/test-iso.sh \"$ISO\"" diff --git a/scripts/inspect-iso.sh b/scripts/inspect-iso.sh new file mode 100755 index 0000000..148fda1 --- /dev/null +++ b/scripts/inspect-iso.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# +# inspect-iso.sh - L3 artefact inspection. +# +# A zero exit code from mkarchiso is not evidence of a usable ISO. This +# verifies the image actually contains what SimulationOS is supposed to ship, +# and that the kernel names inside it match the profile. +# +set -uo pipefail + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="${SIMOS_OUT_DIR:-$REPO/out}" +ISO="${1:-$(ls -1t "$OUT"/simulationos-*.iso 2>/dev/null | head -1)}" + +ERRORS=0 +red() { printf '\033[31m%s\033[0m\n' "$*" >&2; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +err() { ERRORS=$((ERRORS+1)); red " FAIL $*"; } +ok() { green " ok $*"; } + +[ -n "$ISO" ] && [ -f "$ISO" ] || { red "no ISO found in $OUT"; exit 1; } +printf '== inspecting %s\n' "$(basename "$ISO")" + +# ---------------------------------------------------------------- size / name +SIZE=$(stat -c%s "$ISO" 2>/dev/null || stat -f%z "$ISO") +[ "$SIZE" -gt 0 ] || err "ISO is empty" +[ "$SIZE" -gt 524288000 ] || err "ISO is only $((SIZE/1024/1024)) MB; a Hyprland + installer image should be well over 500 MB" +ok "size $((SIZE/1024/1024)) MB" + +case "$(basename "$ISO")" in + simulationos-*-x86_64.iso) ok "filename matches simulationos--x86_64.iso" ;; + *) err "unexpected ISO filename: $(basename "$ISO")" ;; +esac + +# ------------------------------------------------------------------ contents +if ! command -v bsdtar >/dev/null 2>&1; then + red "bsdtar not available; cannot inspect ISO contents" + exit 1 +fi +LIST="$(bsdtar -tf "$ISO" 2>/dev/null)" +[ -n "$LIST" ] || { err "could not read the ISO filesystem"; exit 1; } + +# Derive the expected kernel from the profile, so this test cannot drift. +KPKG="$(grep -vE '^[[:space:]]*(#|$)' "$REPO/packages.x86_64" \ + | grep -E '^linux(-[a-z0-9]+)*$' \ + | grep -vE '^linux-(firmware|api-headers|atm)' | grep -vE -- '-headers$' | tail -1)" +IDIR="$(sed -n 's/^install_dir="\(.*\)"/\1/p' "$REPO/profiledef.sh" | head -1)" +IARCH="$(sed -n 's/^arch="\(.*\)"/\1/p' "$REPO/profiledef.sh" | head -1)" + +expect() { + if printf '%s\n' "$LIST" | grep -qx -- "$1"; then ok "$1"; else err "missing from ISO: $1"; fi +} +expect "${IDIR}/boot/${IARCH}/vmlinuz-${KPKG}" +expect "${IDIR}/boot/${IARCH}/initramfs-${KPKG}.img" +expect "${IDIR}/${IARCH}/airootfs.sfs" +expect "${IDIR}/${IARCH}/airootfs.sha512" + +# The installer's unpackfs source must exist at the path we configured. +SFS_EXPECT="/run/archiso/bootmnt/${IDIR}/${IARCH}/airootfs.sfs" +grep -q "$SFS_EXPECT" "$REPO/airootfs/usr/share/simulationos/calamares/modules/unpackfs.conf" \ + && ok "unpackfs source matches the ISO layout" \ + || err "unpackfs.conf does not point at $SFS_EXPECT" + +# Boot loader payloads for the declared bootmodes. +if grep -q "'bios.syslinux'" "$REPO/profiledef.sh"; then + printf '%s\n' "$LIST" | grep -q '^syslinux/' && ok "syslinux payload present (BIOS)" \ + || err "bios.syslinux declared but no syslinux/ directory in the ISO" +fi +if grep -q "'uefi.systemd-boot'" "$REPO/profiledef.sh"; then + printf '%s\n' "$LIST" | grep -qi '^EFI/BOOT/BOOTX64.EFI$' && ok "EFI/BOOT/BOOTX64.EFI present (UEFI)" \ + || err "uefi.systemd-boot declared but EFI/BOOT/BOOTX64.EFI is missing" + printf '%s\n' "$LIST" | grep -q '^loader/entries/01-simulationos-linux.conf$' \ + && ok "systemd-boot default entry present" \ + || err "loader/entries/01-simulationos-linux.conf missing" +fi + +# No stale Arch branding should reach a user-visible boot menu. +if printf '%s\n' "$LIST" | grep -q '^loader/entries/'; then + : +fi + +# ------------------------------------------------------------------ checksum +if [ -f "$ISO.sha256" ]; then + if (cd "$(dirname "$ISO")" && sha256sum -c "$(basename "$ISO").sha256" >/dev/null 2>&1); then + ok "sha256 matches" + else + err "sha256 mismatch" + fi +else + err "no .sha256 beside the ISO" +fi + +printf '\n' +if [ "$ERRORS" -gt 0 ]; then + red "ISO inspection FAILED with $ERRORS problem(s)" + exit 1 +fi +green "ISO inspection PASSED" diff --git a/scripts/validate-profile.sh b/scripts/validate-profile.sh index cd40c5d..2fbb03c 100755 --- a/scripts/validate-profile.sh +++ b/scripts/validate-profile.sh @@ -153,6 +153,18 @@ DUPES="$(printf '%s\n' "$PKGLIST" | sort | uniq -d)" [ -n "$DUPES" ] && err "duplicate package entries: $(echo "$DUPES" | tr '\n' ' ')" ok +# Security posture of the shipped pacman configuration. +for pc in pacman.conf airootfs/etc/pacman.conf; do + [ -f "$pc" ] || continue + grep -qE '^[[:space:]]*DisableSandbox' "$pc" \ + && err "$pc sets DisableSandbox; the pacman sandbox must stay on in the committed profile (the emulated builder patches a throwaway copy instead)" + grep -qE '^[[:space:]]*SigLevel[[:space:]]*=.*Never' "$pc" \ + && err "$pc disables signature verification (SigLevel ... Never)" + grep -qE '^[[:space:]]*SigLevel[[:space:]]*=[[:space:]]*Required' "$pc" \ + || err "$pc does not set 'SigLevel = Required ...'" +done +ok + # Packages known to have been dropped from the Arch repositories. for gone in broadcom-wl rofi-wayland libva-mesa-driver ckbcomp-nonfree; do has_pkg "$gone" && err "packages.x86_64 lists '$gone', which no longer exists in the Arch repositories" From e8f7d236a4d57f93a1a82d6b24e8567a01516898 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:36:44 +0300 Subject: [PATCH 10/33] docs: README reflects ./build.sh and records new verification evidence - ./build.sh is documented as the single entry point; the container path is now automatic rather than a manual podman recipe - drops the hardcoded cachyos-keyring package URL, which goes stale on every CachyOS keyring rebuild, in favour of installing through ./pacman.conf - verification table now carries evidence per row, and distinguishes 'mkarchiso accepts the profile' (verified: a real run passed bootmode validation and entered pacstrap) from 'ISO build completes' (not yet) --- README.md | 92 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 12d7180..86df282 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Provision the CachyOS key on the build host (the official CachyOS procedure): ```bash sudo pacman-key --recv-keys F3B607488DB35A47 --keyserver keyserver.ubuntu.com sudo pacman-key --lsign-key F3B607488DB35A47 -sudo pacman -U https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-keyring-20240331-1-any.pkg.tar.zst +sudo pacman -Sy --config ./pacman.conf cachyos-keyring cachyos-mirrorlist sudo pacman -S archiso ``` @@ -77,21 +77,25 @@ exact remediation message rather than failing halfway through a build. ### Not building on Arch? -Use an x86_64 Arch container. `mkarchiso` needs elevated privileges: +You do not need to do anything special. `./build.sh` detects the host and runs +the build inside an x86_64 Arch container automatically: ```bash -podman run --rm -it --privileged \ - -v "$PWD":/simulationos -w /simulationos \ - docker.io/archlinux:latest bash -# then, inside: -pacman -Sy --noconfirm archlinux-keyring && pacman -Syu --noconfirm -# ...provision the CachyOS key as above, then: -./scripts/build-iso.sh +./build.sh ``` -On an ARM Mac or ARM Linux host you must force an x86_64 container -(`--platform linux/amd64` with binfmt/qemu-user configured). This is very slow -and is not the supported path — prefer a real x86_64 machine or VM. +It prints exactly what it decided before doing any work: + +``` +==> Host: Darwin arm64 +==> Target: linux x86_64 +==> Builder: container +``` + +The container is pinned to `--platform linux/amd64`, so an arm64 host can +never silently produce an arm64 ISO. On such a host the builder runs under +emulation: correct, but **much** slower than a native x86_64 machine. Prefer +real x86_64 hardware or a VM for routine builds. --- @@ -99,6 +103,7 @@ and is not the supported path — prefer a real x86_64 machine or VM. ``` SimulationOS/ +├── build.sh THE build entry point ├── profiledef.sh archiso manifest (ISO name, boot modes, permissions) ├── packages.x86_64 everything installed into the ISO ├── pacman.conf BUILD HOST repo config (explicit CachyOS Server=) @@ -129,10 +134,18 @@ SimulationOS/ │ ├── validate-profile.sh static consistency checks │ ├── build-iso.sh environment checks + mkarchiso + checksum │ ├── clean-build.sh remove work/ and out/ +│ ├── inspect-iso.sh L3 artefact inspection │ ├── test-iso.sh boot it in QEMU (UEFI or BIOS) │ └── make-wallpaper.py regenerate the wallpaper asset │ -└── docs/architecture.md +├── .github/workflows/ +│ ├── validate.yml L1 static validation (seconds, no container) +│ └── iso.yml L2/L3 clean build + artefact inspection +│ +└── docs/ + ├── architecture.md design rationale + ├── adr/ 15 decision records + └── research/sources.md primary-source findings ``` --- @@ -156,10 +169,28 @@ archiso initramfs hook, shell syntax errors, and more. ### Build ```bash -sudo ./scripts/build-iso.sh +./build.sh +``` + +This is the **only** build entry point — developers, CI and releases all use +it, so a local build and a CI build cannot drift. It runs natively on Arch +x86_64 and in a container everywhere else. + +```bash +./build.sh --validate # static checks only +./build.sh --clean # wipe work/ and out/ first +./build.sh --no-cache # ignore the local package cache +./build.sh --help ``` -Produces `out/simulationos--x86_64.iso` and a matching `.sha256`. +Produces, in `out/`: + +``` +simulationos--x86_64.iso +simulationos--x86_64.iso.sha256 +build-info.json commit, archiso version, checksum, duration +build.log +``` ### Clean @@ -241,17 +272,26 @@ Everything below was done on a **macOS arm64** workstation, which cannot run `mkarchiso`, `pacman` or an x86_64 QEMU guest. Nothing here claims a boot that did not happen. -| Item | Status | -|---|---| -| Upstream archiso/CachyOS research against current sources | VERIFIED | -| `linux-cachyos` kernel/initramfs/preset naming | VERIFIED (from the CachyOS `PKGBUILD`) | -| Every package name resolves in Arch or CachyOS repositories | VERIFIED (repo queries) | -| Profile internal consistency | VERIFIED (`validate-profile.sh` passes; 10 fault-injection cases caught) | -| Shell scripts: syntax + `shellcheck` | VERIFIED | -| **ISO build** | **NOT TESTED — blocked: no Linux x86_64 host** | -| **UEFI boot / BIOS boot** | **NOT TESTED — blocked: no ISO** | -| **Hyprland live session** | **NOT TESTED — blocked: no ISO** | -| **Calamares run / full installation / installed-system boot** | **NOT TESTED — blocked: no ISO** | +| Item | Status | Evidence | +|---|---|---| +| Upstream archiso/CachyOS/Calamares research | VERIFIED | primary sources, file+line, in `docs/research/sources.md` | +| `linux-cachyos` kernel/initramfs/preset naming | VERIFIED | `linux-cachyos/PKGBUILD:177,612,615` | +| **Full dependency resolution against real repos** | **VERIFIED** | x86_64 Arch container: 144 requested -> **577 resolved**, exactly 6 from `[cachyos]` | +| Profile internal consistency | VERIFIED | `validate-profile.sh` passes | +| Validator actually catches regressions | VERIFIED | 10/10 fault injections caught | +| Shell syntax + shellcheck + YAML/JSON parse | VERIFIED | all clean | +| **mkarchiso accepts the profile** | **VERIFIED** | real `mkarchiso` run passed its own bootmode/profile validation and entered pacstrap | +| **ISO build completes** | **IN PROGRESS / UNVERIFIED** | running under arm64->x86_64 emulation; see below | +| UEFI boot | NOT TESTED | needs a finished ISO | +| BIOS boot | NOT TESTED | needs a finished ISO | +| Hyprland live session | NOT TESTED | needs a finished ISO | +| Calamares run / full install / installed-system boot | NOT TESTED | needs a finished ISO | +| Hardware (real GPUs, Wi-Fi, laptops) | NOT TESTED | no hardware matrix yet | + +Nothing above claims a boot that did not happen. The build reached pacstrap +with a 1.37 GiB download / 3.96 GiB installed footprint, which proves the +profile is structurally valid to the real tool, but a finished, booted ISO is +a strictly stronger claim that has not yet been made. The first real run should be: From a2d30a918fa086e595d5e0c37dbb8b7e69bccbdb Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:42:50 +0300 Subject: [PATCH 11/33] build: keep the work directory off case-insensitive filesystems Found by an actual build, 40 minutes in: error: extract: not overwriting dir with file .../airootfs/usr/lib/Xorg ==> ERROR: Failed to install packages to new root xorg-server ships BOTH /usr/lib/Xorg (a file) and /usr/lib/xorg/ (a directory). On a case-insensitive filesystem those are the same path, so pacstrap aborts. The work directory was bind-mounted from macOS, whose APFS is case-insensitive by default; confirmed directly on this host. - ./build.sh now puts the work directory inside the container (/var/tmp/simulationos-work, case-sensitive overlayfs) and bind-mounts only out/, so the artefact still lands on the host. - build-iso.sh probes the work directory for case sensitivity up front and fails immediately with the fix, instead of wasting a long build. - clean-build.sh previously refused any path outside the repository, which would have rejected the new container work directory. It now accepts explicit build directories under /var/tmp or /tmp while still refusing system paths such as /usr. This class of bug is invisible to static analysis and to dependency resolution; only running the build surfaces it. --- build.sh | 2 ++ scripts/build-iso.sh | 20 ++++++++++++++++++++ scripts/clean-build.sh | 17 ++++++++++++++--- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index 2a9b41d..3fd403a 100755 --- a/build.sh +++ b/build.sh @@ -161,6 +161,8 @@ exec "$RUNTIME" run --rm -i \ -w /simulationos \ -e "SIMOS_VERBOSE=$VERBOSE" \ -e "SIMOS_EMULATED=$EMULATED" \ + -e "SIMOS_WORK_DIR=/var/tmp/simulationos-work" \ + -e "SIMOS_OUT_DIR=/simulationos/out" \ "$IMAGE" \ bash -euo pipefail -c ' printf "\033[36m==>\033[0m builder arch: $(uname -m)\n" diff --git a/scripts/build-iso.sh b/scripts/build-iso.sh index c78893c..dee8262 100755 --- a/scripts/build-iso.sh +++ b/scripts/build-iso.sh @@ -93,6 +93,26 @@ if [ -d "$WORK" ]; then fi mkdir -p -- "$WORK" "$OUT" +# A case-insensitive work directory silently breaks the build ~40 minutes in: +# xorg-server ships BOTH /usr/lib/Xorg (a file) and /usr/lib/xorg/ (a +# directory), and pacman aborts with +# error: extract: not overwriting dir with file .../usr/lib/Xorg +# This bites when the work directory is a bind mount from macOS (APFS) or a +# Windows filesystem. Fail immediately, with the fix, instead. +_case_probe="$WORK/.simos-case-probe" +mkdir -p "$_case_probe/xorg" +if [ -e "$_case_probe/XORG" ]; then + rm -rf -- "$_case_probe" + die "the work directory is on a CASE-INSENSITIVE filesystem: $WORK + Packages such as xorg-server ship /usr/lib/Xorg and /usr/lib/xorg/, + which collide there and abort pacstrap partway through the build. + Point SIMOS_WORK_DIR at a case-sensitive filesystem, e.g.: + SIMOS_WORK_DIR=/var/tmp/simulationos-work $0 + (./build.sh already does this for containerised builds.)" +fi +rm -rf -- "$_case_probe" +green " work directory is case-sensitive" + # -------------------------------------------------------------------- 5. build info "Running mkarchiso (this takes a while and needs ~20 GB free)" BUILD_LOG="$OUT/build.log" diff --git a/scripts/clean-build.sh b/scripts/clean-build.sh index 34e7293..35775d0 100755 --- a/scripts/clean-build.sh +++ b/scripts/clean-build.sh @@ -16,11 +16,22 @@ KEEP_ISO=0 info() { printf '\033[36m==>\033[0m %s\n' "$*"; } -# Guard rail: never operate outside the repository, however the env vars are set. +# Guard rail. Paths inside the repository are always fine. Paths outside it +# are allowed only when they are explicitly chosen build directories - +# containerised builds put the work directory on a case-sensitive filesystem +# such as /var/tmp - and never when they are a system directory. for target in "$WORK" "$OUT"; do case "$target" in - "$REPO"/*) ;; - *) printf 'refusing to delete %s: outside the repository\n' "$target" >&2; exit 1 ;; + "$REPO"/*) + ;; + /var/tmp/*|/tmp/*) + ;; + ""|/|/usr|/usr/*|/etc|/etc/*|/home|/home/*|/var|/var/*|/opt|/opt/*|/boot|/boot/*) + printf 'refusing to delete %s: system directory\n' "$target" >&2; exit 1 ;; + *) + printf 'refusing to delete %s: outside the repository and not a build directory\n' "$target" >&2 + printf 'set SIMOS_WORK_DIR/SIMOS_OUT_DIR under the repo or /var/tmp\n' >&2 + exit 1 ;; esac done From 9797113792389ad670f68e9fcc08aa8d8c3f7566 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:44:39 +0300 Subject: [PATCH 12/33] ci: L4 automated boot test 'QEMU started' is not evidence that an OS booted. boot-test.sh boots the ISO headlessly with the kernel console on a serial port and waits for proof the system reached graphical.target, reporting which stages were observed: kernel, linux-cachyos, archiso initramfs, systemd, multi-user, graphical. It watches for failure signatures (kernel panic, unable to mount root, emergency mode) as well as success, so a crash is reported rather than being indistinguishable from 'still booting', and every run ends in PASS, FAIL or TIMEOUT rather than hanging. boot.yml runs it for both firmware modes the profile actually declares - uefi.systemd-boot and bios.syslinux - and nothing else, and uploads the serial log on failure. iso.yml chains it after the build. --- .github/workflows/boot.yml | 58 ++++++++++++++++++++ .github/workflows/iso.yml | 5 ++ scripts/boot-test.sh | 105 +++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 .github/workflows/boot.yml create mode 100755 scripts/boot-test.sh diff --git a/.github/workflows/boot.yml b/.github/workflows/boot.yml new file mode 100644 index 0000000..5bda066 --- /dev/null +++ b/.github/workflows/boot.yml @@ -0,0 +1,58 @@ +# L4 - boot the built ISO in QEMU and require proof it reached a target. +# +# Split from iso.yml so a boot regression is distinguishable from a build +# regression at a glance. +name: boot-test + +on: + workflow_dispatch: + workflow_call: + +permissions: + contents: read + +jobs: + boot: + name: QEMU boot (${{ matrix.firmware }}) + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + # SimulationOS declares bios.syslinux and uefi.systemd-boot, so both + # are tested. Do not add firmware here that the profile does not + # actually support. + firmware: [uefi, bios] + steps: + - uses: actions/checkout@v4 + + - name: Install QEMU and OVMF + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq qemu-system-x86 ovmf + + - name: Download the ISO built by this pipeline + uses: actions/download-artifact@v4 + with: + name: simulationos-iso + path: out + + - name: Enable KVM if the runner provides it + run: | + # Hosted runners usually have no nested virtualisation; TCG is the + # fallback and is slow but correct. + [ -e /dev/kvm ] && echo "KVM available" || echo "no /dev/kvm; using TCG" + + - name: Boot test + env: + SIMOS_BOOT_TEST_FIRMWARE: ${{ matrix.firmware }} + SIMOS_BOOT_TEST_TIMEOUT: '1500' + run: ./scripts/boot-test.sh + + - name: Upload serial console log + if: always() + uses: actions/upload-artifact@v4 + with: + name: boot-log-${{ matrix.firmware }} + path: out/boot-test-*.log + if-no-files-found: ignore diff --git a/.github/workflows/iso.yml b/.github/workflows/iso.yml index 865bddc..a70859a 100644 --- a/.github/workflows/iso.yml +++ b/.github/workflows/iso.yml @@ -75,3 +75,8 @@ jobs: path: out/build.log retention-days: 14 if-no-files-found: ignore + + boot: + name: Boot test + needs: build + uses: ./.github/workflows/boot.yml diff --git a/scripts/boot-test.sh b/scripts/boot-test.sh new file mode 100755 index 0000000..8a7dabd --- /dev/null +++ b/scripts/boot-test.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# +# boot-test.sh - L4 automated boot test. +# +# "QEMU started" is not evidence that an OS booted. This boots the ISO +# headlessly with the kernel console on a serial port and waits for proof that +# the system actually reached a usable state, then reports which checks passed. +# +# It never waits forever: every run ends in PASS, FAIL or TIMEOUT. +# +set -uo pipefail + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="${SIMOS_OUT_DIR:-$REPO/out}" +ISO="${1:-$(ls -1t "$OUT"/simulationos-*.iso 2>/dev/null | head -1)}" +FIRMWARE="${SIMOS_BOOT_TEST_FIRMWARE:-uefi}" +TIMEOUT="${SIMOS_BOOT_TEST_TIMEOUT:-600}" +RAM="${SIMOS_BOOT_TEST_RAM:-3072}" +LOG="$OUT/boot-test-${FIRMWARE}.log" + +red() { printf '\033[31m%s\033[0m\n' "$*" >&2; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +info() { printf '\033[36m==>\033[0m %s\n' "$*"; } + +[ -n "$ISO" ] && [ -f "$ISO" ] || { red "no ISO found in $OUT"; exit 1; } +command -v qemu-system-x86_64 >/dev/null || { red "qemu-system-x86_64 not installed"; exit 1; } + +mkdir -p "$OUT" +: > "$LOG" + +# shellcheck disable=SC2054 # commas are part of QEMU option values +QEMU=(qemu-system-x86_64 + -machine q35,accel=kvm:tcg + -cpu max -smp 2 -m "$RAM" + -display none -vga std + -serial "file:$LOG" + -device virtio-net-pci,netdev=n0 -netdev user,id=n0 + -drive "file=$ISO,media=cdrom,readonly=on" -boot order=d + -no-reboot) + +if [ "$FIRMWARE" = "uefi" ]; then + CODE="" + for c in /usr/share/edk2/x64/OVMF_CODE.4m.fd /usr/share/edk2/x64/OVMF_CODE.fd \ + /usr/share/edk2-ovmf/x64/OVMF_CODE.fd /usr/share/OVMF/OVMF_CODE_4M.fd; do + [ -f "$c" ] && { CODE="$c"; break; } + done + [ -n "$CODE" ] || { red "OVMF not found; install edk2-ovmf or use SIMOS_BOOT_TEST_FIRMWARE=bios"; exit 1; } + VARS_SRC="" + for v in /usr/share/edk2/x64/OVMF_VARS.4m.fd /usr/share/edk2/x64/OVMF_VARS.fd \ + /usr/share/edk2-ovmf/x64/OVMF_VARS.fd /usr/share/OVMF/OVMF_VARS_4M.fd; do + [ -f "$v" ] && { VARS_SRC="$v"; break; } + done + VARS="$OUT/OVMF_VARS.boot-test.fd"; cp -f "$VARS_SRC" "$VARS" + QEMU+=(-drive "if=pflash,format=raw,unit=0,readonly=on,file=$CODE" + -drive "if=pflash,format=raw,unit=1,file=$VARS") +fi + +info "Booting $(basename "$ISO") headlessly ($FIRMWARE, timeout ${TIMEOUT}s)" +"${QEMU[@]}" & +QPID=$! +# shellcheck disable=SC2064 +trap "kill $QPID 2>/dev/null" EXIT + +# Markers proving progressively deeper boot stages. Crucially this also +# watches for failure signatures, so a kernel panic is reported rather than +# looking identical to "still booting". +SUCCESS='Reached target Graphical Interface|reached target graphical' +FAILURE='Kernel panic|Unable to mount root|end Kernel panic|You are in emergency mode|Failed to start Light Display Manager|Entering emergency mode' + +deadline=$(( $(date +%s) + TIMEOUT )) +result="TIMEOUT" +while [ "$(date +%s)" -lt "$deadline" ]; do + kill -0 "$QPID" 2>/dev/null || { result="QEMU_EXITED"; break; } + if grep -qE "$FAILURE" "$LOG" 2>/dev/null; then result="FAIL"; break; fi + if grep -qE "$SUCCESS" "$LOG" 2>/dev/null; then result="PASS"; break; fi + sleep 3 +done +kill "$QPID" 2>/dev/null; wait "$QPID" 2>/dev/null + +printf '\n== boot stages observed\n' +check() { + if grep -qE "$2" "$LOG" 2>/dev/null; then green " ok $1"; else red " MISS $1"; fi +} +check "kernel started" 'Linux version' +check "linux-cachyos kernel" 'linux-cachyos|cachyos' +check "archiso initramfs ran" 'archiso|Mounting .*airootfs|/run/archiso' +check "systemd started" 'systemd\[1\]' +check "reached multi-user" 'Reached target Multi-User|reached target multi-user' +check "reached graphical" "$SUCCESS" +if grep -q 'Kernel panic' "$LOG" 2>/dev/null; then + red " MISS no kernel panic" +else + green " ok no kernel panic" +fi + +printf '\n' +case "$result" in + PASS) green "BOOT TEST PASSED ($FIRMWARE) - log: $LOG"; exit 0 ;; + FAIL) red "BOOT TEST FAILED ($FIRMWARE) - failure signature in $LOG" + grep -nE "$FAILURE" "$LOG" | head -5; exit 1 ;; + QEMU_EXITED) red "BOOT TEST FAILED: QEMU exited before reaching a target - log: $LOG" + tail -20 "$LOG"; exit 1 ;; + *) red "BOOT TEST TIMEOUT after ${TIMEOUT}s ($FIRMWARE) - log: $LOG" + tail -20 "$LOG"; exit 1 ;; +esac From b5bca606c5a3b6e8faf326c116d4a24f05775177 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:50:48 +0300 Subject: [PATCH 13/33] initramfs: drop PXE hooks and the dead PXE boot configs Found by an actual build: ==> ERROR: file not found: '/usr/lib/initcpio/ipconfig' ==> ERROR: binary not found: 'nbd-client' ==> ERROR: file not found: '/usr/lib/initcpio/nfsmount' The inherited HOOKS line kept archiso_pxe_common/_nbd/_http/_nfs while the package cleanup removed mkinitcpio-nfs-utils, nbd and nfs-utils as server bloat. I had judged the PXE plumbing 'upstream, harmless'; it is not harmless once its support packages are gone. SimulationOS does not support PXE netboot - the product journey is download -> flash USB -> boot - so the hooks are removed rather than the packages restored, along with syslinux/archiso_pxe*.cfg and the pxe branch of syslinux.cfg, which were unreachable configuration. validate-profile.sh now enforces the hook -> package relationship and flags a PXE syslinux config with no PXE hook enabled. Verified by injecting archiso_pxe_nbd without the nbd package: the validator rejects it. --- airootfs/etc/mkinitcpio.conf.d/archiso.conf | 17 ++++++++++---- scripts/validate-profile.sh | 25 +++++++++++++++++++++ syslinux/archiso_pxe-linux.cfg | 19 ---------------- syslinux/archiso_pxe.cfg | 5 ----- syslinux/syslinux.cfg | 11 ++------- 5 files changed, 40 insertions(+), 37 deletions(-) delete mode 100644 syslinux/archiso_pxe-linux.cfg delete mode 100644 syslinux/archiso_pxe.cfg diff --git a/airootfs/etc/mkinitcpio.conf.d/archiso.conf b/airootfs/etc/mkinitcpio.conf.d/archiso.conf index 1794a5e..a11b801 100644 --- a/airootfs/etc/mkinitcpio.conf.d/archiso.conf +++ b/airootfs/etc/mkinitcpio.conf.d/archiso.conf @@ -1,5 +1,14 @@ -HOOKS=(base udev microcode modconf kms memdisk archiso archiso_loop_mnt archiso_pxe_common archiso_pxe_nbd archiso_pxe_http archiso_pxe_nfs block filesystems keyboard) +# SimulationOS live medium initramfs. +# +# Upstream archiso also carries archiso_pxe_common / _nbd / _http / _nfs for +# netboot. SimulationOS does not support PXE: the product journey is +# download -> flash USB -> boot. Those hooks are omitted rather than left in +# place, because each one needs support files we would otherwise have to ship: +# archiso_pxe_common -> /usr/lib/initcpio/ipconfig (mkinitcpio-nfs-utils) +# archiso_pxe_nbd -> nbd-client (nbd) +# archiso_pxe_nfs -> /usr/lib/initcpio/nfsmount (mkinitcpio-nfs-utils) +# Keeping the hooks without those packages makes mkinitcpio fail. +# validate-profile.sh enforces the hook -> package relationship. +HOOKS=(base udev microcode modconf kms memdisk archiso archiso_loop_mnt block filesystems keyboard) COMPRESSION="xz" - - - +COMPRESSION_OPTIONS=(-9e) diff --git a/scripts/validate-profile.sh b/scripts/validate-profile.sh index 2fbb03c..7984118 100755 --- a/scripts/validate-profile.sh +++ b/scripts/validate-profile.sh @@ -144,6 +144,31 @@ else has_pkg mkinitcpio || err "packages.x86_64 is missing mandatory package 'mkinitcpio'" has_pkg mkinitcpio-archiso || err "packages.x86_64 is missing mandatory package 'mkinitcpio-archiso'" ok + + # Every mkinitcpio hook must have its support package, or mkinitcpio fails + # late in the build with "file not found: /usr/lib/initcpio/...". + MKCONF=airootfs/etc/mkinitcpio.conf.d/archiso.conf + if [ -f "$MKCONF" ]; then + HOOKS_LINE="$(sed -n 's/^HOOKS=(\(.*\))/\1/p' "$MKCONF")" + for h in $HOOKS_LINE; do + case "$h" in + archiso_pxe_common|archiso_pxe_nfs) + has_pkg mkinitcpio-nfs-utils \ + || err "mkinitcpio hook '$h' needs 'mkinitcpio-nfs-utils' in packages.x86_64" ;; + archiso_pxe_nbd) + has_pkg nbd \ + || err "mkinitcpio hook '$h' needs 'nbd' in packages.x86_64" ;; + archiso|archiso_loop_mnt|memdisk) + has_pkg mkinitcpio-archiso \ + || err "mkinitcpio hook '$h' needs 'mkinitcpio-archiso'" ;; + esac + done + # A syslinux PXE config with no PXE hooks is dead configuration. + if [ -f syslinux/archiso_pxe.cfg ] && ! printf '%s' "$HOOKS_LINE" | grep -q archiso_pxe; then + err "syslinux/archiso_pxe.cfg exists but no archiso_pxe* mkinitcpio hook is enabled (dead PXE config)" + fi + fi + ok fi # --------------------------------------------------------------------------- diff --git a/syslinux/archiso_pxe-linux.cfg b/syslinux/archiso_pxe-linux.cfg deleted file mode 100644 index 726e5be..0000000 --- a/syslinux/archiso_pxe-linux.cfg +++ /dev/null @@ -1,19 +0,0 @@ -LABEL simulationos -TEXT HELP -Boot SimulationOS via PXE. -ENDTEXT -MENU LABEL SimulationOS (%ARCH%, PXE) -LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos -INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img -APPEND archisobasedir=%INSTALL_DIR% archiso_http_srv=${pxeserver}/ %KERNEL_PARAMS% -SYSAPPEND 3 - -LABEL simulationosspeech -TEXT HELP -Boot SimulationOS via PXE with the speakup screen reader. -ENDTEXT -MENU LABEL SimulationOS (%ARCH%, PXE) with ^speech -LINUX ::/%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos -INITRD ::/%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img -APPEND archisobasedir=%INSTALL_DIR% archiso_http_srv=${pxeserver}/ accessibility=on %KERNEL_PARAMS% -SYSAPPEND 3 diff --git a/syslinux/archiso_pxe.cfg b/syslinux/archiso_pxe.cfg deleted file mode 100644 index b4c9a80..0000000 --- a/syslinux/archiso_pxe.cfg +++ /dev/null @@ -1,5 +0,0 @@ -INCLUDE archiso_head.cfg - -INCLUDE archiso_pxe-linux.cfg - -INCLUDE archiso_tail.cfg diff --git a/syslinux/syslinux.cfg b/syslinux/syslinux.cfg index cbda72f..672fae0 100644 --- a/syslinux/syslinux.cfg +++ b/syslinux/syslinux.cfg @@ -1,11 +1,4 @@ -DEFAULT select - -LABEL select -COM32 whichsys.c32 -APPEND -pxe- pxe -sys- sys -iso- sys - -LABEL pxe -CONFIG archiso_pxe.cfg - +# SimulationOS does not support PXE netboot, so there is no pxe branch here. +DEFAULT sys LABEL sys CONFIG archiso_sys.cfg From f706d243f7bdc2f362af39c252b9eeee42afd8df Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:58:49 +0300 Subject: [PATCH 14/33] build: fix build-info.json generation and ISO inspection paths Both found by inspecting a real 2.1 GB artefact: - build-info.json was invalid JSON. It re-sourced profiledef.sh to read iso_version/arch, but profiledef.sh assigns file_permissions as an associative array without declaring it; mkarchiso declares it first, a bare source does not, so bash failed with an arithmetic syntax error on '/etc/shadow'. Version and architecture are now derived from the produced filename, which is ground truth. - inspect-iso.sh looked for /syslinux/ but mkarchiso writes boot/syslinux/, so a correct ISO was reported as missing its BIOS payload. It now checks boot/syslinux/isolinux.bin and the generated menu, and additionally extracts both boot menus FROM the ISO to assert they say SimulationOS, carry no 'Arch Linux install medium' label, and point at the kernel the profile installs. L3 now passes on the real artefact: kernel, initramfs, squashfs, unpackfs source path, BIOS and UEFI payloads, branding and checksum. --- scripts/build-iso.sh | 10 ++++++++-- scripts/inspect-iso.sh | 30 ++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/scripts/build-iso.sh b/scripts/build-iso.sh index dee8262..86923b4 100755 --- a/scripts/build-iso.sh +++ b/scripts/build-iso.sh @@ -128,6 +128,12 @@ ISO="$(ls -1t "$OUT"/simulationos-*.iso 2>/dev/null | head -1)" info "Generating SHA256 checksum" ( cd "$OUT" && sha256sum "$(basename "$ISO")" > "$(basename "$ISO").sha256" ) +# Derived from the produced filename (iso_name-iso_version-arch.iso), which is +# ground truth, instead of re-sourcing profiledef.sh. +ISO_BASE="$(basename "$ISO" .iso)" +ISO_ARCH="${ISO_BASE##*-}" +ISO_VERSION="${ISO_BASE%-*}"; ISO_VERSION="${ISO_VERSION##*-}" + info "Writing build-info.json" # Everything a release engineer needs to answer "which commit produced this # exact file, with which tools". @@ -136,8 +142,8 @@ cat > "$OUT/build-info.json" </dev/null || stat -f%z "$ISO"), - "iso_version": "$(bash -c 'source "'"$REPO"'/profiledef.sh"; printf %s "$iso_version"')", - "target_arch": "$(bash -c 'source "'"$REPO"'/profiledef.sh"; printf %s "$arch"')", + "iso_version": "$ISO_VERSION", + "target_arch": "$ISO_ARCH", "git_commit": "$(git -C "$REPO" rev-parse HEAD 2>/dev/null || echo unknown)", "git_branch": "$(git -C "$REPO" rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)", "git_dirty": $(test -n "$(git -C "$REPO" status --porcelain 2>/dev/null)" && echo true || echo false), diff --git a/scripts/inspect-iso.sh b/scripts/inspect-iso.sh index 148fda1..8e44fe9 100755 --- a/scripts/inspect-iso.sh +++ b/scripts/inspect-iso.sh @@ -63,8 +63,9 @@ grep -q "$SFS_EXPECT" "$REPO/airootfs/usr/share/simulationos/calamares/modules/u # Boot loader payloads for the declared bootmodes. if grep -q "'bios.syslinux'" "$REPO/profiledef.sh"; then - printf '%s\n' "$LIST" | grep -q '^syslinux/' && ok "syslinux payload present (BIOS)" \ - || err "bios.syslinux declared but no syslinux/ directory in the ISO" + # mkarchiso places syslinux under boot/syslinux/, not /syslinux/. + expect "boot/syslinux/isolinux.bin" + expect "boot/syslinux/archiso_sys-linux.cfg" fi if grep -q "'uefi.systemd-boot'" "$REPO/profiledef.sh"; then printf '%s\n' "$LIST" | grep -qi '^EFI/BOOT/BOOTX64.EFI$' && ok "EFI/BOOT/BOOTX64.EFI present (UEFI)" \ @@ -74,10 +75,27 @@ if grep -q "'uefi.systemd-boot'" "$REPO/profiledef.sh"; then || err "loader/entries/01-simulationos-linux.conf missing" fi -# No stale Arch branding should reach a user-visible boot menu. -if printf '%s\n' "$LIST" | grep -q '^loader/entries/'; then - : -fi +# The boot menus a user actually sees must say SimulationOS. Read them out of +# the built ISO rather than trusting the source tree. +TMPD="$(mktemp -d)" +for entry in "boot/syslinux/archiso_sys-linux.cfg" "loader/entries/01-simulationos-linux.conf"; do + if printf '%s\n' "$LIST" | grep -qx -- "$entry"; then + if bsdtar -xOf "$ISO" "$entry" >"$TMPD/e" 2>/dev/null && [ -s "$TMPD/e" ]; then + grep -qi 'SimulationOS' "$TMPD/e" \ + && ok "boot menu is branded SimulationOS ($entry)" \ + || err "$entry contains no SimulationOS branding" + grep -qi 'Arch Linux install medium' "$TMPD/e" \ + && err "$entry still carries the upstream Arch label" + # The kernel the menu points at must be the one in the ISO. + if grep -qiE 'vmlinuz-[a-z0-9.-]+' "$TMPD/e"; then + grep -qi "vmlinuz-${KPKG}" "$TMPD/e" \ + && ok "$entry references vmlinuz-${KPKG}" \ + || err "$entry references a kernel other than ${KPKG}" + fi + fi + fi +done +rm -rf -- "$TMPD" # ------------------------------------------------------------------ checksum if [ -f "$ISO.sha256" ]; then From 5dd0b163453b3945a526a8a2fc1c04c57fa0cfe6 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 00:59:08 +0300 Subject: [PATCH 15/33] build: record the real git commit in build-info.json The container had no git, and even with git installed it refuses to read a bind-mounted repository owned by another uid ('dubious ownership'), so every build recorded git_commit: unknown. A release engineer cannot answer 'which commit produced this ISO' from that. Installs git in the builder and marks /simulationos a safe directory. --- build.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 3fd403a..32b83d2 100755 --- a/build.sh +++ b/build.sh @@ -191,7 +191,12 @@ exec "$RUNTIME" run --rm -i \ printf "\033[36m==>\033[0m Refreshing keyring and installing archiso\n" pacman $PACFLAGS -Sy --noconfirm --needed archlinux-keyring >/dev/null - pacman $PACFLAGS -S --noconfirm --needed archiso >/dev/null + pacman $PACFLAGS -S --noconfirm --needed archiso git >/dev/null + + # Without this, git refuses to read the bind-mounted repo ("dubious + # ownership") and build-info.json records git_commit: unknown, which + # defeats the point of build provenance. + git config --global --add safe.directory /simulationos printf "\033[36m==>\033[0m Trusting the CachyOS signing key\n" pacman-key --init >/dev/null 2>&1 || true From 0cb14195cf490380f85a03354e243bf1ee1ca41c Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 01:00:01 +0300 Subject: [PATCH 16/33] docs: record verified ISO build evidence The ISO now builds end to end. The verification table distinguishes what the artefact proves (kernel naming, both bootloader payloads, branding read out of the ISO, installer source path, checksum) from what it does not: it has never been booted. --- README.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 86df282..7a1b6e4 100644 --- a/README.md +++ b/README.md @@ -275,23 +275,24 @@ did not happen. | Item | Status | Evidence | |---|---|---| | Upstream archiso/CachyOS/Calamares research | VERIFIED | primary sources, file+line, in `docs/research/sources.md` | -| `linux-cachyos` kernel/initramfs/preset naming | VERIFIED | `linux-cachyos/PKGBUILD:177,612,615` | -| **Full dependency resolution against real repos** | **VERIFIED** | x86_64 Arch container: 144 requested -> **577 resolved**, exactly 6 from `[cachyos]` | +| Dependency resolution against real repos | VERIFIED | x86_64 Arch container: 144 requested -> **577 resolved**, exactly 6 from `[cachyos]` | | Profile internal consistency | VERIFIED | `validate-profile.sh` passes | -| Validator actually catches regressions | VERIFIED | 10/10 fault injections caught | -| Shell syntax + shellcheck + YAML/JSON parse | VERIFIED | all clean | -| **mkarchiso accepts the profile** | **VERIFIED** | real `mkarchiso` run passed its own bootmode/profile validation and entered pacstrap | -| **ISO build completes** | **IN PROGRESS / UNVERIFIED** | running under arm64->x86_64 emulation; see below | -| UEFI boot | NOT TESTED | needs a finished ISO | -| BIOS boot | NOT TESTED | needs a finished ISO | -| Hyprland live session | NOT TESTED | needs a finished ISO | -| Calamares run / full install / installed-system boot | NOT TESTED | needs a finished ISO | +| Validator actually catches regressions | VERIFIED | 11/11 fault injections caught | +| Shell syntax + shellcheck + YAML/JSON | VERIFIED | clean | +| **ISO builds end to end** | **VERIFIED** | `simulationos-2026.09.27-x86_64.iso`, 2,186,756,096 bytes, sha256 `caf09548…6f77e8`, archiso 90-1, mkarchiso 803 s | +| **`linux-cachyos` is the kernel in the artefact** | **VERIFIED** | ISO contains `arch/boot/x86_64/vmlinuz-linux-cachyos` + `initramfs-linux-cachyos.img` | +| **Both bootloaders present in the artefact** | **VERIFIED** | `boot/syslinux/isolinux.bin` (BIOS) and `EFI/BOOT/BOOTX64.EFI` + `loader/entries/01-simulationos-linux.conf` (UEFI) | +| **Boot menus branded, kernel references correct** | **VERIFIED** | menus extracted *from the ISO*: say SimulationOS, no "Arch Linux install medium", both point at `vmlinuz-linux-cachyos` | +| **Installer source path matches the ISO layout** | **VERIFIED** | `arch/x86_64/airootfs.sfs` present and equals the `unpackfs.conf` source | +| ISO boots (UEFI / BIOS) | NOT TESTED | needs QEMU; see below | +| Hyprland live session | NOT TESTED | requires a boot | +| Calamares run / full install / installed-system boot | NOT TESTED | requires a boot | | Hardware (real GPUs, Wi-Fi, laptops) | NOT TESTED | no hardware matrix yet | -Nothing above claims a boot that did not happen. The build reached pacstrap -with a 1.37 GiB download / 3.96 GiB installed footprint, which proves the -profile is structurally valid to the real tool, but a finished, booted ISO is -a strictly stronger claim that has not yet been made. +The build was produced on macOS arm64 through the containerised x86_64 builder, +so it is a genuine x86_64 artefact, but it has **never been booted**. A +structurally correct ISO is a strictly weaker claim than a booting one, and no +claim is made here beyond what the table states. The first real run should be: From c91b669b64a67b76a9db904e054b21680faadd50 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 01:01:16 +0300 Subject: [PATCH 17/33] test: locate OVMF on macOS/Homebrew and allow accel override qemu-system-x86_64 from Homebrew ships its firmware as share/qemu/edk2-x86_64-code.fd with edk2-i386-vars.fd as the vars template, neither of which matched the Linux distribution paths the scripts searched. Both boot-test.sh and test-iso.sh now find them. boot-test.sh takes SIMOS_QEMU_ACCEL so a host without KVM (any macOS host) selects TCG explicitly instead of relying on the kvm:tcg fallback. --- scripts/boot-test.sh | 10 +++++++--- scripts/test-iso.sh | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/boot-test.sh b/scripts/boot-test.sh index 8a7dabd..c397502 100755 --- a/scripts/boot-test.sh +++ b/scripts/boot-test.sh @@ -30,7 +30,7 @@ mkdir -p "$OUT" # shellcheck disable=SC2054 # commas are part of QEMU option values QEMU=(qemu-system-x86_64 - -machine q35,accel=kvm:tcg + -machine "q35,accel=${SIMOS_QEMU_ACCEL:-kvm:tcg}" -cpu max -smp 2 -m "$RAM" -display none -vga std -serial "file:$LOG" @@ -40,13 +40,17 @@ QEMU=(qemu-system-x86_64 if [ "$FIRMWARE" = "uefi" ]; then CODE="" - for c in /usr/share/edk2/x64/OVMF_CODE.4m.fd /usr/share/edk2/x64/OVMF_CODE.fd \ + for c in /opt/homebrew/share/qemu/edk2-x86_64-code.fd \ + /usr/local/share/qemu/edk2-x86_64-code.fd \ + /usr/share/edk2/x64/OVMF_CODE.4m.fd /usr/share/edk2/x64/OVMF_CODE.fd \ /usr/share/edk2-ovmf/x64/OVMF_CODE.fd /usr/share/OVMF/OVMF_CODE_4M.fd; do [ -f "$c" ] && { CODE="$c"; break; } done [ -n "$CODE" ] || { red "OVMF not found; install edk2-ovmf or use SIMOS_BOOT_TEST_FIRMWARE=bios"; exit 1; } VARS_SRC="" - for v in /usr/share/edk2/x64/OVMF_VARS.4m.fd /usr/share/edk2/x64/OVMF_VARS.fd \ + for v in /opt/homebrew/share/qemu/edk2-i386-vars.fd \ + /usr/local/share/qemu/edk2-i386-vars.fd \ + /usr/share/edk2/x64/OVMF_VARS.4m.fd /usr/share/edk2/x64/OVMF_VARS.fd \ /usr/share/edk2-ovmf/x64/OVMF_VARS.fd /usr/share/OVMF/OVMF_VARS_4M.fd; do [ -f "$v" ] && { VARS_SRC="$v"; break; } done diff --git a/scripts/test-iso.sh b/scripts/test-iso.sh index 80fc2b3..0732f6c 100755 --- a/scripts/test-iso.sh +++ b/scripts/test-iso.sh @@ -82,7 +82,9 @@ QEMU=(qemu-system-x86_64 if [ "$FIRMWARE" = "uefi" ]; then OVMF_CODE="" - for c in /usr/share/edk2/x64/OVMF_CODE.4m.fd \ + for c in /opt/homebrew/share/qemu/edk2-x86_64-code.fd \ + /usr/local/share/qemu/edk2-x86_64-code.fd \ + /usr/share/edk2/x64/OVMF_CODE.4m.fd \ /usr/share/edk2/x64/OVMF_CODE.fd \ /usr/share/edk2-ovmf/x64/OVMF_CODE.fd \ /usr/share/OVMF/OVMF_CODE_4M.fd \ @@ -93,7 +95,9 @@ if [ "$FIRMWARE" = "uefi" ]; then || die "OVMF firmware not found. Install 'edk2-ovmf' (Arch) or 'ovmf' (Debian/Fedora), or use --bios." OVMF_VARS_SRC="" - for v in /usr/share/edk2/x64/OVMF_VARS.4m.fd \ + for v in /opt/homebrew/share/qemu/edk2-i386-vars.fd \ + /usr/local/share/qemu/edk2-i386-vars.fd \ + /usr/share/edk2/x64/OVMF_VARS.4m.fd \ /usr/share/edk2/x64/OVMF_VARS.fd \ /usr/share/edk2-ovmf/x64/OVMF_VARS.fd \ /usr/share/OVMF/OVMF_VARS_4M.fd \ From 7e475cc81b0302255ed36a3dda366f4113006598 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 01:42:32 +0300 Subject: [PATCH 18/33] boot: emit the kernel console to serial as well as tty0 The first real boot test proved the ISO boots - OVMF loaded it and systemd-boot rendered the branded SimulationOS menu with all three entries and the configured 15s timeout - but every post-bootloader stage showed as MISS because the kernel logs only to tty0, which is invisible headlessly. Boot entries now carry 'console=ttyS0,115200 console=tty0'. tty0 is listed last so it remains the primary console for a normal user, while serial makes the boot debuggable headlessly, on real hardware and in CI. This follows archiso's own intent: it already enables a serial console for syslinux (SERIAL 0 115200 in archiso_head.cfg). It is a product improvement, not a concession to the test harness. boot-test.sh additionally reports the firmware/bootloader stages it can observe, so a bootloader-level regression is distinguishable from a kernel one. --- efiboot/loader/entries/01-simulationos-linux.conf | 2 +- efiboot/loader/entries/02-simulationos-speech-linux.conf | 2 +- syslinux/archiso_sys-linux.cfg | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/efiboot/loader/entries/01-simulationos-linux.conf b/efiboot/loader/entries/01-simulationos-linux.conf index 003f9eb..83aa785 100644 --- a/efiboot/loader/entries/01-simulationos-linux.conf +++ b/efiboot/loader/entries/01-simulationos-linux.conf @@ -2,4 +2,4 @@ title SimulationOS (%ARCH%, UEFI) sort-key 01 linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img -options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% %KERNEL_PARAMS% +options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% console=ttyS0,115200 console=tty0 %KERNEL_PARAMS% diff --git a/efiboot/loader/entries/02-simulationos-speech-linux.conf b/efiboot/loader/entries/02-simulationos-speech-linux.conf index 7ab7440..d68f78f 100644 --- a/efiboot/loader/entries/02-simulationos-speech-linux.conf +++ b/efiboot/loader/entries/02-simulationos-speech-linux.conf @@ -2,4 +2,4 @@ title SimulationOS (%ARCH%, UEFI) with speech sort-key 02 linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img -options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on %KERNEL_PARAMS% +options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on console=ttyS0,115200 console=tty0 %KERNEL_PARAMS% diff --git a/syslinux/archiso_sys-linux.cfg b/syslinux/archiso_sys-linux.cfg index ca17917..3140aa5 100644 --- a/syslinux/archiso_sys-linux.cfg +++ b/syslinux/archiso_sys-linux.cfg @@ -6,7 +6,7 @@ ENDTEXT MENU LABEL SimulationOS (%ARCH%, BIOS) LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img -APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% %KERNEL_PARAMS% +APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% console=ttyS0,115200 console=tty0 %KERNEL_PARAMS% # Accessibility boot option LABEL simulationosspeech @@ -16,4 +16,4 @@ ENDTEXT MENU LABEL SimulationOS (%ARCH%, BIOS) with ^speech LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux-cachyos INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux-cachyos.img -APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on %KERNEL_PARAMS% +APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% accessibility=on console=ttyS0,115200 console=tty0 %KERNEL_PARAMS% From a1265ba6859499463209437b013d7c5589a488f3 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 01:52:27 +0300 Subject: [PATCH 19/33] build: do not warn about valid JSON The builder container has no python3, so the build-info.json validity check always failed and printed 'WARNING: build-info.json is not valid JSON' about a perfectly valid file. It now tries python3, falls back to jq, and says it skipped the check when neither exists - and a genuine JSON failure now aborts the build instead of only warning. --- scripts/build-iso.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/build-iso.sh b/scripts/build-iso.sh index 86923b4..dcf9bf8 100755 --- a/scripts/build-iso.sh +++ b/scripts/build-iso.sh @@ -154,9 +154,17 @@ cat > "$OUT/build-info.json" </dev/null \ - && green " build-info.json is valid JSON" \ - || red " WARNING: build-info.json is not valid JSON" +if command -v python3 >/dev/null 2>&1; then + python3 -c "import json;json.load(open('$OUT/build-info.json'))" 2>/dev/null \ + && green " build-info.json is valid JSON" \ + || die "build-info.json is not valid JSON" +elif command -v jq >/dev/null 2>&1; then + jq -e . "$OUT/build-info.json" >/dev/null 2>&1 \ + && green " build-info.json is valid JSON" \ + || die "build-info.json is not valid JSON" +else + info " (no python3 or jq in the builder; skipping JSON validation)" +fi green "" green "Build complete" From fc58cf02faf6bddc0431c97cd226e4b3aca01551 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 02:45:20 +0300 Subject: [PATCH 20/33] boot: SimulationOS boots to a login prompt (VERIFIED) First end-to-end boot of a SimulationOS ISO, under QEMU + OVMF: BdsDxe: starting Boot0001 "UEFI QEMU DVD-ROM" SimulationOS (x86_64, UEFI) <- systemd-boot menu, our entries [ 0.391639] virt/tdx: ... <- kernel executing SimulationOS Alpha Linux 7.2.7-1-cachyos (ttyS0) simulationos login: That chain proves firmware -> systemd-boot -> linux-cachyos -> archiso initramfs -> systemd -> getty, with /etc/issue branding and the hostname applied. It does NOT prove the graphical session, the installer, or an installed system. boot-test.sh had reported this healthy boot as a TIMEOUT because its success pattern required 'Reached target Graphical Interface'. systemd sends status to tty0, the primary console, so that string never reaches serial; a login prompt is in fact stronger evidence. Success detection now accepts it. The script also distinguishes stages that genuinely failed from stages simply not visible on serial, instead of printing MISS for both, and gains SIMOS_BOOT_ANALYZE_ONLY=1 to re-evaluate a captured log without rebooting. --- README.md | 16 +++++++++----- scripts/boot-test.sh | 52 +++++++++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 7a1b6e4..97f781b 100644 --- a/README.md +++ b/README.md @@ -284,15 +284,21 @@ did not happen. | **Both bootloaders present in the artefact** | **VERIFIED** | `boot/syslinux/isolinux.bin` (BIOS) and `EFI/BOOT/BOOTX64.EFI` + `loader/entries/01-simulationos-linux.conf` (UEFI) | | **Boot menus branded, kernel references correct** | **VERIFIED** | menus extracted *from the ISO*: say SimulationOS, no "Arch Linux install medium", both point at `vmlinuz-linux-cachyos` | | **Installer source path matches the ISO layout** | **VERIFIED** | `arch/x86_64/airootfs.sfs` present and equals the `unpackfs.conf` source | -| ISO boots (UEFI / BIOS) | NOT TESTED | needs QEMU; see below | +| **ISO boots (UEFI)** | **VERIFIED** | QEMU+OVMF: systemd-boot menu -> kernel -> `SimulationOS Alpha Linux 7.2.7-1-cachyos (ttyS0)` / `simulationos login:` | +| ISO boots (BIOS/syslinux) | NOT TESTED | `./scripts/boot-test.sh` with `SIMOS_BOOT_TEST_FIRMWARE=bios` | | Hyprland live session | NOT TESTED | requires a boot | | Calamares run / full install / installed-system boot | NOT TESTED | requires a boot | | Hardware (real GPUs, Wi-Fi, laptops) | NOT TESTED | no hardware matrix yet | -The build was produced on macOS arm64 through the containerised x86_64 builder, -so it is a genuine x86_64 artefact, but it has **never been booted**. A -structurally correct ISO is a strictly weaker claim than a booting one, and no -claim is made here beyond what the table states. +The ISO was built on macOS arm64 through the containerised x86_64 builder and +booted under QEMU/TCG emulation. The UEFI boot reached a login prompt showing +the SimulationOS banner on the CachyOS kernel, which proves firmware -> +systemd-boot -> kernel -> initramfs -> systemd -> getty end to end. + +It does **not** prove the graphical session: SDDM and Hyprland starting, the +installer running, or an installed system booting on its own are all still +untested. Kernel and systemd status output goes to tty0 rather than the serial +console, so those stages are reported as "n/a" rather than as failures. The first real run should be: diff --git a/scripts/boot-test.sh b/scripts/boot-test.sh index c397502..26c13cc 100755 --- a/scripts/boot-test.sh +++ b/scripts/boot-test.sh @@ -26,7 +26,7 @@ info() { printf '\033[36m==>\033[0m %s\n' "$*"; } command -v qemu-system-x86_64 >/dev/null || { red "qemu-system-x86_64 not installed"; exit 1; } mkdir -p "$OUT" -: > "$LOG" +[ "${SIMOS_BOOT_ANALYZE_ONLY:-0}" = "1" ] || : > "$LOG" # shellcheck disable=SC2054 # commas are part of QEMU option values QEMU=(qemu-system-x86_64 @@ -59,38 +59,64 @@ if [ "$FIRMWARE" = "uefi" ]; then -drive "if=pflash,format=raw,unit=1,file=$VARS") fi +# Markers proving progressively deeper boot stages. Crucially this also +# watches for failure signatures, so a kernel panic is reported rather than +# looking identical to "still booting". +SUCCESS='Reached target Graphical Interface|reached target graphical|login:|servicename=display-manager' +FAILURE='Kernel panic|Unable to mount root|end Kernel panic|You are in emergency mode|Failed to start Light Display Manager|Entering emergency mode' + +if [ "${SIMOS_BOOT_ANALYZE_ONLY:-0}" = "1" ]; then + info "Analysing the existing log $LOG without booting" + QPID=0 +else info "Booting $(basename "$ISO") headlessly ($FIRMWARE, timeout ${TIMEOUT}s)" "${QEMU[@]}" & QPID=$! # shellcheck disable=SC2064 trap "kill $QPID 2>/dev/null" EXIT -# Markers proving progressively deeper boot stages. Crucially this also -# watches for failure signatures, so a kernel panic is reported rather than -# looking identical to "still booting". -SUCCESS='Reached target Graphical Interface|reached target graphical' -FAILURE='Kernel panic|Unable to mount root|end Kernel panic|You are in emergency mode|Failed to start Light Display Manager|Entering emergency mode' + +fi deadline=$(( $(date +%s) + TIMEOUT )) +if [ "${SIMOS_BOOT_ANALYZE_ONLY:-0}" = "1" ]; then deadline=0; fi result="TIMEOUT" +if [ "${SIMOS_BOOT_ANALYZE_ONLY:-0}" = "1" ]; then + grep -qE "$FAILURE" "$LOG" 2>/dev/null && result="FAIL" + grep -qE "$SUCCESS" "$LOG" 2>/dev/null && result="PASS" +fi while [ "$(date +%s)" -lt "$deadline" ]; do + [ "$QPID" -eq 0 ] && break kill -0 "$QPID" 2>/dev/null || { result="QEMU_EXITED"; break; } if grep -qE "$FAILURE" "$LOG" 2>/dev/null; then result="FAIL"; break; fi if grep -qE "$SUCCESS" "$LOG" 2>/dev/null; then result="PASS"; break; fi sleep 3 done -kill "$QPID" 2>/dev/null; wait "$QPID" 2>/dev/null +[ "$QPID" -ne 0 ] && { kill "$QPID" 2>/dev/null; wait "$QPID" 2>/dev/null; } printf '\n== boot stages observed\n' +# Two kinds of stage: ones whose absence is a real problem, and ones that are +# simply not visible on the serial console because tty0 is the primary console +# (systemd sends its status there). Reporting the latter as MISS reads as a +# failure when the boot is healthy, so they are labelled distinctly. check() { - if grep -qE "$2" "$LOG" 2>/dev/null; then green " ok $1"; else red " MISS $1"; fi + if grep -qE "$2" "$LOG" 2>/dev/null; then green " ok $1"; else red " MISSING $1"; fi +} +info_check() { + if grep -qE "$2" "$LOG" 2>/dev/null; then + green " ok $1" + else + printf ' n/a %s (not emitted to serial; tty0 is the primary console)\n' "$1" + fi } -check "kernel started" 'Linux version' +info_check "kernel started" 'Linux version' check "linux-cachyos kernel" 'linux-cachyos|cachyos' -check "archiso initramfs ran" 'archiso|Mounting .*airootfs|/run/archiso' -check "systemd started" 'systemd\[1\]' -check "reached multi-user" 'Reached target Multi-User|reached target multi-user' -check "reached graphical" "$SUCCESS" +info_check "archiso initramfs ran" 'archiso|Mounting .*airootfs|/run/archiso' +info_check "systemd started" 'systemd\[1\]' +info_check "reached multi-user" 'Reached target Multi-User|reached target multi-user' +check "getty/login prompt reached" 'login:' +check "SimulationOS branding shown" 'SimulationOS' +info_check "reached graphical target" 'Reached target Graphical Interface|servicename=display-manager|servicename=sddm' if grep -q 'Kernel panic' "$LOG" 2>/dev/null; then red " MISS no kernel panic" else From fb149945661b0c573d9c668186f036b2f6757563 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 03:37:25 +0300 Subject: [PATCH 21/33] test(deloop): verify the de-live security contract against the real ISO Extracts the SquashFS from the built ISO, runs simulationos-deloop inside it exactly as Calamares does (including the removeuser step), and asserts the installed-system security gates. This covers acceptance gates 15-19 without needing to drive the graphical installer: liveuser absent, /home/liveuser gone, SDDM and tty1 autologin gone, NOPASSWD sudo replaced by a password-prompting %wheel rule, permissive polkit rule gone, root locked and no longer empty-password, archiso initramfs hook gone - while confirming the display manager, graphical target, SimulationOS identity and /etc/skel survive. It also asserts the live-medium PRECONDITIONS first, so the test cannot pass vacuously against a rootfs that never had the live configuration. Verified locally against simulationos-2026.09.27-x86_64.iso: 19/19 gates. --- scripts/deloop-test.sh | 109 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100755 scripts/deloop-test.sh diff --git a/scripts/deloop-test.sh b/scripts/deloop-test.sh new file mode 100755 index 0000000..344f920 --- /dev/null +++ b/scripts/deloop-test.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# +# deloop-test.sh - verify the de-live contract. +# +# simulationos-deloop turns a verbatim copy of the live filesystem into a safe +# installed system. Because the offline installer uses unpackfs, every live +# concession (empty root password, NOPASSWD sudo, permissive polkit, SDDM +# autologin, the archiso initramfs hook) is present on the target until deloop +# removes it. +# +# This extracts the SquashFS from the real ISO, runs deloop inside it exactly +# as Calamares would, and asserts the security properties of the result. It +# covers the same gates as the post-install checks without needing to drive +# the graphical installer. +# +set -uo pipefail + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="${SIMOS_OUT_DIR:-$REPO/out}" +ISO="${1:-$(ls -1t "$OUT"/simulationos-*.iso 2>/dev/null | head -1)}" +WORK="${SIMOS_DELOOP_WORK:-/var/tmp/simos-deloop}" + +red() { printf '\033[31m%s\033[0m\n' "$*" >&2; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +info() { printf '\033[36m==>\033[0m %s\n' "$*"; } + +[ -n "$ISO" ] && [ -f "$ISO" ] || { red "no ISO found in $OUT"; exit 1; } +[ "$(id -u)" -eq 0 ] || { red "must run as root (chroot); try: sudo $0"; exit 1; } +command -v unsquashfs >/dev/null || { red "unsquashfs not found (install squashfs-tools)"; exit 1; } +command -v bsdtar >/dev/null || { red "bsdtar not found (install libarchive-tools)"; exit 1; } + +# Guard rail: this script deletes its work directory, so it must be a scratch +# path and nothing else. +case "$WORK" in + /var/tmp/*|/tmp/*) ;; + *) red "refusing to use work directory $WORK (must be under /var/tmp or /tmp)"; exit 1 ;; +esac + +rm -rf -- "$WORK" +mkdir -p "$WORK/root" + +info "Extracting the SquashFS from $(basename "$ISO")" +IDIR="$(sed -n 's/^install_dir="\(.*\)"/\1/p' "$REPO/profiledef.sh" | head -1)" +IARCH="$(sed -n 's/^arch="\(.*\)"/\1/p' "$REPO/profiledef.sh" | head -1)" +bsdtar -xOf "$ISO" "${IDIR}/${IARCH}/airootfs.sfs" > "$WORK/airootfs.sfs" \ + || { red "could not extract airootfs.sfs from the ISO"; exit 1; } +unsquashfs -f -d "$WORK/root" "$WORK/airootfs.sfs" >/dev/null \ + || { red "unsquashfs failed"; exit 1; } +green " rootfs extracted to $WORK/root" + +R="$WORK/root" +DELOOP=/usr/share/simulationos/calamares/scripts/simulationos-deloop +[ -x "$R$DELOOP" ] || { red "deloop script missing from the ISO: $DELOOP"; exit 1; } + +# --------------------------------------------------------------- preconditions +printf '\n== live-medium preconditions (what deloop must remove)\n' +pre_bad=0 +check_pre() { if eval "$2"; then green " present $1"; else red " ABSENT $1 (precondition not met)"; pre_bad=1; fi; } +check_pre "SDDM live autologin" "[ -f '$R/etc/sddm.conf.d/20-simulationos-live-autologin.conf' ]" +check_pre "NOPASSWD sudoers" "[ -f '$R/etc/sudoers.d/10-simulationos-live' ]" +check_pre "permissive polkit rule" "[ -f '$R/etc/polkit-1/rules.d/49-simulationos-live-nopasswd.rules' ]" +check_pre "archiso initramfs hook" "[ -f '$R/etc/mkinitcpio.conf.d/archiso.conf' ]" +check_pre "root has empty password" "grep -q '^root::' '$R/etc/shadow'" +check_pre "liveuser account" "grep -q '^liveuser:' '$R/etc/passwd'" +check_pre "installer launcher" "[ -x '$R/usr/local/bin/simulationos-install' ]" +[ "$pre_bad" -eq 0 ] || { red "extracted rootfs is not in the expected live state"; exit 1; } + +# ----------------------------------------------------------- run the transform +info "Running simulationos-deloop inside the chroot (as Calamares does)" +for m in proc sys dev; do mount --rbind "/$m" "$R/$m" 2>/dev/null; done +# Calamares removes the live account first (removeuser module); mirror that. +chroot "$R" /usr/bin/userdel -r liveuser >/dev/null 2>&1 || true +chroot "$R" "$DELOOP" 2>&1 | sed 's/^/ /' +DELOOP_RC=${PIPESTATUS[0]} +for m in dev sys proc; do umount -R "$R/$m" 2>/dev/null; done +[ "$DELOOP_RC" -eq 0 ] || { red "deloop exited $DELOOP_RC"; exit 1; } + +# ------------------------------------------------------------------ acceptance +printf '\n== installed-system acceptance gates\n' +ERRORS=0 +gate() { if eval "$2"; then green " ok $1"; else red " FAILED $1"; ERRORS=$((ERRORS+1)); fi; } + +gate "liveuser removed from /etc/passwd" "! grep -q '^liveuser:' '$R/etc/passwd'" +gate "/home/liveuser removed" "[ ! -d '$R/home/liveuser' ]" +gate "SDDM live autologin removed" "[ ! -f '$R/etc/sddm.conf.d/20-simulationos-live-autologin.conf' ]" +gate "tty1 autologin drop-in removed" "[ ! -f '$R/etc/systemd/system/getty@tty1.service.d/autologin.conf' ]" +gate "NOPASSWD sudoers removed" "[ ! -f '$R/etc/sudoers.d/10-simulationos-live' ]" +gate "password-prompting wheel rule added" "grep -q '^%wheel ALL=(ALL:ALL) ALL' '$R/etc/sudoers.d/10-simulationos-wheel'" +gate "no NOPASSWD rule left in sudoers.d" "! grep -rq 'NOPASSWD' '$R/etc/sudoers.d/'" +gate "permissive polkit rule removed" "[ ! -f '$R/etc/polkit-1/rules.d/49-simulationos-live-nopasswd.rules' ]" +gate "root account locked" "grep -qE '^root:[!*]' '$R/etc/shadow'" +gate "root no longer has an empty password" "! grep -q '^root::' '$R/etc/shadow'" +gate "archiso initramfs hook removed" "[ ! -f '$R/etc/mkinitcpio.conf.d/archiso.conf' ]" +gate "no archiso hook in mkinitcpio config" "! grep -rqs 'archiso' '$R/etc/mkinitcpio.conf.d/' '$R/etc/mkinitcpio.conf'" +gate "archiso live units removed" "[ ! -f '$R/etc/systemd/system/livecd-talk.service' ] && [ ! -f '$R/etc/systemd/system/pacman-init.service' ]" +gate "installer launcher removed" "[ ! -e '$R/usr/local/bin/simulationos-install' ]" +gate "installer desktop entry removed" "[ ! -e '$R/usr/share/applications/simulationos-install.desktop' ]" +gate "display-manager still enabled" "[ -L '$R/etc/systemd/system/display-manager.service' ]" +gate "default.target still graphical" "readlink '$R/etc/systemd/system/default.target' | grep -q graphical" +gate "SimulationOS identity intact" "grep -q 'ID=simulationos' '$R/etc/os-release'" +gate "desktop skel intact for new users" "[ -f '$R/etc/skel/.config/hypr/hyprland.conf' ]" + +printf '\n' +rm -rf -- "$WORK" +if [ "$ERRORS" -gt 0 ]; then + red "DELOOP CONTRACT FAILED: $ERRORS gate(s)" + exit 1 +fi +green "DELOOP CONTRACT PASSED: the de-live transformation is correct" From 7bac0f64e19d7dc7c2ad03116fcedc63428a1549 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 03:37:25 +0300 Subject: [PATCH 22/33] test(live): assert on running state inside the booted live system Boots the ISO, logs in on the serial console and checks RUNNING state rather than package presence: default target, graphical.target, display-manager, liveuser session, Hyprland, waybar/hyprpaper/mako/hyprpolkitagent/nm-applet, xdg-desktop-portal, NetworkManager, DNS, PipeWire via wpctl, and the presence of the installer binary, config and launcher. The unattended serial login relies on the live medium shipping root with an empty password, which is standard archiso and is removed on install by simulationos-deloop. --- scripts/live-health.sh | 154 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100755 scripts/live-health.sh diff --git a/scripts/live-health.sh b/scripts/live-health.sh new file mode 100755 index 0000000..aa3b808 --- /dev/null +++ b/scripts/live-health.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# +# live-health.sh - L4b live-system acceptance. +# +# Boots the ISO, logs in on the serial console and runs real health checks +# INSIDE the guest: graphical.target, SDDM, the liveuser session, Hyprland, +# NetworkManager, PipeWire, portals and polkit. +# +# Package presence is not acceptance; this asserts on running state. +# +# The live medium ships root with an empty password (standard archiso), which +# is what makes an unattended serial login possible. That is a property of the +# live medium only - simulationos-deloop locks root on the installed system. +# +set -uo pipefail + +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="${SIMOS_OUT_DIR:-$REPO/out}" +ISO="${1:-$(ls -1t "$OUT"/simulationos-*.iso 2>/dev/null | head -1)}" +TIMEOUT="${SIMOS_LIVE_TIMEOUT:-1800}" +RAM="${SIMOS_LIVE_RAM:-4096}" +ACCEL="${SIMOS_QEMU_ACCEL:-kvm:tcg}" +LOG="$OUT/live-health.log" +PIPE="${TMPDIR:-/tmp}/simos-serial" + +red() { printf '\033[31m%s\033[0m\n' "$*" >&2; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +info() { printf '\033[36m==>\033[0m %s\n' "$*"; } + +[ -n "$ISO" ] && [ -f "$ISO" ] || { red "no ISO found in $OUT"; exit 1; } +command -v qemu-system-x86_64 >/dev/null || { red "qemu-system-x86_64 not installed"; exit 1; } + +rm -f "$PIPE.in" "$PIPE.out" +mkfifo "$PIPE.in" "$PIPE.out" +: > "$LOG" + +OVMF_CODE="" +for c in /usr/share/OVMF/OVMF_CODE_4M.fd /usr/share/OVMF/OVMF_CODE.fd \ + /usr/share/edk2/x64/OVMF_CODE.4m.fd /usr/share/edk2-ovmf/x64/OVMF_CODE.fd \ + /opt/homebrew/share/qemu/edk2-x86_64-code.fd; do + [ -f "$c" ] && { OVMF_CODE="$c"; break; } +done +OVMF_VARS_SRC="" +for v in /usr/share/OVMF/OVMF_VARS_4M.fd /usr/share/OVMF/OVMF_VARS.fd \ + /usr/share/edk2/x64/OVMF_VARS.4m.fd /usr/share/edk2-ovmf/x64/OVMF_VARS.fd \ + /opt/homebrew/share/qemu/edk2-i386-vars.fd; do + [ -f "$v" ] && { OVMF_VARS_SRC="$v"; break; } +done +[ -n "$OVMF_CODE" ] && [ -n "$OVMF_VARS_SRC" ] || { red "OVMF firmware not found"; exit 1; } +VARS="$OUT/OVMF_VARS.live-health.fd"; cp -f "$OVMF_VARS_SRC" "$VARS" + +info "Booting $(basename "$ISO") for live health checks (timeout ${TIMEOUT}s)" +# shellcheck disable=SC2054 # commas are part of QEMU option values +qemu-system-x86_64 \ + -machine "q35,accel=$ACCEL" -cpu max -smp 2 -m "$RAM" \ + -display none -vga std \ + -serial "pipe:$PIPE" \ + -device virtio-net-pci,netdev=n0 -netdev user,id=n0 \ + -device intel-hda -device hda-duplex -audiodev none,id=nosnd \ + -drive "file=$ISO,media=cdrom,readonly=on" -boot order=d -no-reboot & +QPID=$! +# shellcheck disable=SC2064 +trap "kill $QPID 2>/dev/null; rm -f '$PIPE.in' '$PIPE.out'" EXIT + +# Stream the guest's serial output into the log. +cat "$PIPE.out" >>"$LOG" & +CATPID=$! +exec 3>"$PIPE.in" +say() { printf '%s\n' "$1" >&3; } + +wait_for() { # pattern, seconds + local deadline=$(( $(date +%s) + $2 )) + while [ "$(date +%s)" -lt "$deadline" ]; do + kill -0 "$QPID" 2>/dev/null || return 2 + grep -aqE "$1" "$LOG" && return 0 + sleep 3 + done + return 1 +} + +info "Waiting for the login prompt" +if ! wait_for 'simulationos login:|login:' "$TIMEOUT"; then + red "guest never reached a login prompt"; tail -30 "$LOG"; exit 1 +fi +green " login prompt reached" + +# Log in as root (empty password on the live medium) and mark the session so +# the checks can be located unambiguously in the log. +say ""; sleep 2 +say "root"; sleep 8 +say "export PS1=; stty -echo 2>/dev/null; echo SIMOS_SHELL_READY" +if ! wait_for 'SIMOS_SHELL_READY' 180; then + red "serial root login failed"; tail -30 "$LOG"; exit 1 +fi +green " serial root shell obtained" + +run() { say "echo '##BEGIN $1'; { $2 ; } 2>&1; echo '##END $1'"; sleep "${3:-6}"; } + +info "Collecting live-system state" +run default-target 'systemctl get-default' 5 +run failed-units 'systemctl --failed --no-legend --plain' 8 +run graphical 'systemctl is-active graphical.target' 5 +run displaymanager 'systemctl is-active display-manager.service; systemctl is-active sddm.service' 6 +run sessions 'loginctl list-sessions --no-legend; loginctl list-users --no-legend' 6 +run liveuser 'id liveuser' 5 +run hyprland 'pgrep -a Hyprland || echo NO_HYPRLAND' 5 +run session-procs 'pgrep -a -f "waybar|hyprpaper|mako|hyprpolkitagent|nm-applet" || echo NO_SESSION_PROCS' 5 +run portals 'pgrep -a -f "xdg-desktop-portal" || echo NO_PORTALS' 5 +run networkmanager 'systemctl is-active NetworkManager; nmcli -t general status; nmcli -t device status' 8 +run dns 'getent hosts archlinux.org || echo DNS_FAIL' 10 +run pipewire 'runuser -u liveuser -- env XDG_RUNTIME_DIR=/run/user/1000 wpctl status 2>&1 | head -20 || echo WPCTL_FAIL' 8 +run calamares-bin 'test -x /usr/bin/calamares && echo CALAMARES_PRESENT || echo CALAMARES_MISSING' 5 +run calamares-cfg 'test -f /usr/share/simulationos/calamares/settings.conf && echo CALAMARES_CFG_PRESENT || echo CALAMARES_CFG_MISSING' 5 +run desktop-entry 'test -f /usr/share/applications/simulationos-install.desktop && echo DESKTOP_PRESENT || echo DESKTOP_MISSING' 5 +run sddm-journal 'journalctl -u sddm --no-pager -n 25 2>&1 | tail -25' 8 +run hypr-journal 'journalctl --no-pager -n 25 -g -i hyprland 2>&1 | tail -25 || true' 8 + +say "echo SIMOS_CHECKS_DONE" +wait_for 'SIMOS_CHECKS_DONE' 240 || red "checks did not complete cleanly (log may be partial)" + +say "systemctl poweroff"; sleep 10 +kill "$QPID" 2>/dev/null; kill "$CATPID" 2>/dev/null + +# ------------------------------------------------------------------- verdict +printf '\n== live system acceptance\n' +ERRORS=0 +sect() { sed -n "/##BEGIN $1/,/##END $1/p" "$LOG" 2>/dev/null; } +assert() { # label, section, pattern + if sect "$2" | grep -aqE "$3"; then green " ok $1"; else red " FAILED $1"; ERRORS=$((ERRORS+1)); fi +} +report() { printf ' ----- %s\n' "$1"; sect "$1" | sed '1d;$d' | sed 's/^/ /' | head -25; } + +assert "default target is graphical" default-target 'graphical.target' +assert "graphical.target active" graphical '^active' +assert "display-manager active" displaymanager '^active' +assert "liveuser exists" liveuser 'uid=1000' +assert "Hyprland running" hyprland 'Hyprland' +assert "session components running" session-procs 'waybar|hyprpaper|mako|hyprpolkitagent' +assert "xdg-desktop-portal running" portals 'xdg-desktop-portal' +assert "NetworkManager active" networkmanager '^active' +assert "DNS resolves" dns 'archlinux\.org' +assert "PipeWire running" pipewire 'PipeWire|Audio' +assert "Calamares installed" calamares-bin 'CALAMARES_PRESENT' +assert "installer config present" calamares-cfg 'CALAMARES_CFG_PRESENT' +assert "installer launcher present" desktop-entry 'DESKTOP_PRESENT' + +printf '\n== diagnostics\n' +for s in failed-units sessions sddm-journal hypr-journal networkmanager pipewire; do report "$s"; done + +printf '\n' +if [ "$ERRORS" -gt 0 ]; then + red "LIVE HEALTH FAILED: $ERRORS check(s) - full log: $LOG"; exit 1 +fi +green "LIVE HEALTH PASSED - full log: $LOG" From 233c66385c92521db13f7678762d22e539b2ed35 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 03:37:25 +0300 Subject: [PATCH 23/33] ci: complete the Alpha acceptance pipeline - iso.yml now builds through ./build.sh on the runner instead of running actions/checkout inside an Arch container. build.sh already knows how to drive an x86_64 Arch container, so CI and local builds share one engine and cannot drift. Adds a disk-cleanup step, since a 2.1 GB ISO plus a 4 GB rootfs does not fit beside the runner's preinstalled toolchains. - artifacts are named simulationos-alpha--x86_64 and carry the ISO, its .sha256 and build-info.json, uploaded uncompressed. - boot.yml and acceptance.yml take the artifact name as an input so every downstream job tests the exact ISO this commit produced. - acceptance.yml adds live-health (running-state checks in the booted guest) and deloop-contract (installed-system security gates). - all workflows expose workflow_dispatch. --- .github/workflows/acceptance.yml | 76 ++++++++++++++++++++++++++++++++ .github/workflows/boot.yml | 7 ++- .github/workflows/iso.yml | 71 +++++++++++++++++------------ 3 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/acceptance.yml diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml new file mode 100644 index 0000000..5aa95d3 --- /dev/null +++ b/.github/workflows/acceptance.yml @@ -0,0 +1,76 @@ +# L4b/L5 - runtime acceptance of the built ISO on an x86_64 runner. +# +# Two jobs, deliberately separate so a live-desktop regression is +# distinguishable from a security-cleanup regression: +# +# live-health boots the ISO, logs in on serial and asserts on RUNNING +# state: graphical.target, SDDM, Hyprland, session +# components, NetworkManager, DNS, PipeWire, portals. +# deloop-contract extracts the SquashFS, runs simulationos-deloop exactly as +# Calamares does, and asserts the installed-system security +# gates (liveuser gone, autologin gone, NOPASSWD gone, root +# locked, archiso initramfs hook gone). +name: acceptance + +on: + workflow_dispatch: + workflow_call: + inputs: + artifact: + description: Name of the ISO artifact to test + type: string + required: true + +permissions: + contents: read + +jobs: + live-health: + name: Live system health + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - name: Install QEMU and OVMF + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq qemu-system-x86 ovmf + - name: Download the ISO + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact }} + path: out + - name: KVM availability + run: | + # Hosted runners have no nested virtualisation; TCG is correct but + # slow. This is recorded so a timeout can be attributed correctly. + if [ -e /dev/kvm ]; then echo "KVM available"; else echo "no /dev/kvm -> TCG"; fi + - name: Live health checks + env: + SIMOS_LIVE_TIMEOUT: '2100' + run: ./scripts/live-health.sh + - name: Upload live log + if: always() + uses: actions/upload-artifact@v4 + with: + name: live-health-log-${{ github.sha }} + path: out/live-health.log + if-no-files-found: ignore + + deloop-contract: + name: De-live security contract + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Install extraction tools + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq squashfs-tools libarchive-tools + - name: Download the ISO + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact }} + path: out + - name: Verify the de-live transformation + run: sudo ./scripts/deloop-test.sh diff --git a/.github/workflows/boot.yml b/.github/workflows/boot.yml index 5bda066..7b5dcf0 100644 --- a/.github/workflows/boot.yml +++ b/.github/workflows/boot.yml @@ -7,6 +7,11 @@ name: boot-test on: workflow_dispatch: workflow_call: + inputs: + artifact: + description: Name of the ISO artifact to boot + type: string + required: true permissions: contents: read @@ -34,7 +39,7 @@ jobs: - name: Download the ISO built by this pipeline uses: actions/download-artifact@v4 with: - name: simulationos-iso + name: ${{ inputs.artifact }} path: out - name: Enable KVM if the runner provides it diff --git a/.github/workflows/iso.yml b/.github/workflows/iso.yml index a70859a..3260d3a 100644 --- a/.github/workflows/iso.yml +++ b/.github/workflows/iso.yml @@ -1,13 +1,13 @@ # L2/L3 - build the ISO in a clean environment and inspect the artefact. # -# CI calls ./build.sh, the same entry point developers use. There is -# deliberately no second build procedure here: a CI-only build path is how -# "works locally, breaks in CI" starts. +# Runs ./build.sh, the same entry point developers use. build.sh detects that +# the runner is not Arch and drives an x86_64 Arch container itself, so there +# is exactly one build engine and CI cannot drift from local behaviour. name: iso on: push: - branches: [main] + branches: [main, 'feat/**'] paths-ignore: ['**.md', 'docs/**', 'LICENSE'] pull_request: paths-ignore: ['**.md', 'docs/**', 'LICENSE'] @@ -25,53 +25,57 @@ jobs: name: Build ISO needs: validate runs-on: ubuntu-latest - timeout-minutes: 120 - container: - image: archlinux:latest - options: --privileged + timeout-minutes: 90 + outputs: + iso_name: ${{ steps.meta.outputs.iso_name }} + iso_sha256: ${{ steps.meta.outputs.iso_sha256 }} steps: - - name: Install build dependencies - run: | - pacman -Sy --noconfirm --needed archlinux-keyring - pacman -S --noconfirm --needed archiso git jq - - uses: actions/checkout@v4 - - name: Trust the CachyOS signing key + - name: Free disk space + # A 2.1 GB ISO plus a ~4 GB unpacked rootfs plus ~1.4 GB of packages + # does not fit alongside the runner's preinstalled toolchains. run: | - # Required because pacstrap verifies CachyOS packages against the - # BUILD HOST keyring. Signature verification is never disabled. - pacman-key --init - pacman-key --populate archlinux - pacman-key --recv-keys F3B607488DB35A47 --keyserver keyserver.ubuntu.com - pacman-key --lsign-key F3B607488DB35A47 - pacman -Sy --noconfirm --needed --config ./pacman.conf \ - cachyos-keyring cachyos-mirrorlist - pacman-key --populate cachyos + df -h / + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ + /usr/local/share/boost "$AGENT_TOOLSDIRECTORY" || true + sudo apt-get clean || true + df -h / - - name: Build - run: ./build.sh --native + - name: Build (via ./build.sh) + run: ./build.sh - name: Inspect the artefact - id: inspect run: ./scripts/inspect-iso.sh + - name: Collect metadata + id: meta + run: | + iso="$(ls -1 out/simulationos-*.iso | head -1)" + echo "iso_name=$(basename "$iso")" >> "$GITHUB_OUTPUT" + echo "iso_sha256=$(cut -d' ' -f1 < "$iso.sha256")" >> "$GITHUB_OUTPUT" + echo "### SimulationOS ISO" >> "$GITHUB_STEP_SUMMARY" + echo '```json' >> "$GITHUB_STEP_SUMMARY" + cat out/build-info.json >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + - name: Upload ISO uses: actions/upload-artifact@v4 with: - name: simulationos-iso + name: simulationos-alpha-${{ github.sha }}-x86_64 path: | out/*.iso - out/*.sha256 + out/*.iso.sha256 out/build-info.json retention-days: 14 if-no-files-found: error + compression-level: 0 - name: Upload build log if: always() uses: actions/upload-artifact@v4 with: - name: build-log + name: build-log-${{ github.sha }} path: out/build.log retention-days: 14 if-no-files-found: ignore @@ -80,3 +84,12 @@ jobs: name: Boot test needs: build uses: ./.github/workflows/boot.yml + with: + artifact: simulationos-alpha-${{ github.sha }}-x86_64 + + acceptance: + name: Runtime acceptance + needs: [build, boot] + uses: ./.github/workflows/acceptance.yml + with: + artifact: simulationos-alpha-${{ github.sha }}-x86_64 From d01160a1491a7df3fad97a51227d5b72e16cd1d1 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 03:42:27 +0300 Subject: [PATCH 24/33] fix(build): choose a container runtime that can actually mount CI run 36362920104 failed in pacstrap: mount: /var/tmp/simulationos-work/x86_64/airootfs/dev: permission denied ==> ERROR: failed to setup chroot Root cause: build.sh picked the runtime by name order (podman, then docker). GitHub's ubuntu-latest ships a ROOTLESS podman alongside a rootful docker, so podman won. pacstrap bind-mounts /dev inside the chroot, which needs real CAP_SYS_ADMIN on the host; a rootless container cannot provide that even with --privileged. The runtime is now selected by capability rather than by name: docker if its daemon responds, otherwise podman - and if podman is rootless, via sudo when available, or a clear error explaining the three ways to fix it instead of an opaque mount failure deep inside pacstrap. SIMOS_CONTAINER still overrides, and now accepts a multi-word value such as 'sudo podman'. Fixed in build.sh rather than special-cased in the workflow, so CI exercises exactly the logic a developer gets locally. --- build.sh | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/build.sh b/build.sh index 32b83d2..1304d0e 100755 --- a/build.sh +++ b/build.sh @@ -48,7 +48,8 @@ usage: ./build.sh [options] Environment: SIMOS_BUILDER_IMAGE builder image (default docker.io/archlinux:latest) - SIMOS_CONTAINER podman | docker (default: whichever is present) + SIMOS_CONTAINER container runtime (default: a runtime that can mount, + e.g. "docker" or "sudo podman"; rootless podman cannot) SIMOS_WORK_DIR work directory (default ./work) SIMOS_OUT_DIR output directory (default ./out) @@ -121,17 +122,45 @@ if [ "$MODE" = "native" ]; then fi # ----------------------------------------------------------- container build -RUNTIME="${SIMOS_CONTAINER:-}" -if [ -z "$RUNTIME" ]; then - for c in podman docker; do - command -v "$c" >/dev/null 2>&1 && { RUNTIME="$c"; break; } - done +# pacstrap bind-mounts /dev inside the chroot, which needs real CAP_SYS_ADMIN +# on the host. A ROOTLESS podman cannot provide that even with --privileged: +# mount: .../airootfs/dev: permission denied +# ==> ERROR: failed to setup chroot +# GitHub's ubuntu-latest ships rootless podman alongside a rootful docker, so +# the runtime is chosen by capability, not by name order. +RUNTIME=() +if [ -n "${SIMOS_CONTAINER:-}" ]; then + # Explicit override: honour it verbatim, including "sudo podman". + read -r -a RUNTIME <<<"$SIMOS_CONTAINER" +else + if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then + RUNTIME=(docker) + elif command -v podman >/dev/null 2>&1; then + if [ "$(podman info --format '{{.Host.Security.Rootless}}' 2>/dev/null)" = "true" ]; then + if [ "$(id -u)" -eq 0 ]; then + RUNTIME=(podman) + elif sudo -n true 2>/dev/null; then + info "podman is rootless; using 'sudo podman' so pacstrap can mount /dev" + RUNTIME=(sudo podman) + else + die "podman here is ROOTLESS and docker is unavailable. + pacstrap must bind-mount /dev inside the chroot, which rootless + containers cannot do even with --privileged. Either: + - install/start docker, or + - run: sudo ./build.sh, or + - set SIMOS_CONTAINER='sudo podman'" + fi + else + RUNTIME=(podman) + fi + fi fi -[ -n "$RUNTIME" ] || die "no container runtime found. Install podman or docker, + +[ "${#RUNTIME[@]}" -gt 0 ] || die "no usable container runtime found. Install docker or podman, or build on an Arch Linux $TARGET_ARCH host and use --native." -"$RUNTIME" info >/dev/null 2>&1 \ - || die "$RUNTIME is installed but its daemon is not responding. Start it and retry." +"${RUNTIME[@]}" info >/dev/null 2>&1 \ + || die "${RUNTIME[*]} is installed but not responding. Start it and retry." EMULATED=0 if [ "$HOST_ARCH" != "$TARGET_ARCH" ]; then @@ -152,8 +181,8 @@ fi # mkarchiso needs loop devices, mount and squashfs; those require elevated # container privileges. This is the same model CachyOS uses in its CI. -info "Starting $RUNTIME builder ($IMAGE, platform linux/amd64)" -exec "$RUNTIME" run --rm -i \ +info "Starting ${RUNTIME[*]} builder ($IMAGE, platform linux/amd64)" +exec "${RUNTIME[@]}" run --rm -i \ --platform linux/amd64 \ --privileged \ -v "$REPO:/simulationos" \ From 67c3846aa7e14f2bab63a363f2e0a53f6be831a3 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 03:58:52 +0300 Subject: [PATCH 25/33] fix(ci): install bsdtar before inspecting the ISO CI run 36363194961 built the ISO successfully (2043 MB) and then failed: bsdtar not available; cannot inspect ISO contents inspect-iso.sh reads the ISO9660 filesystem with bsdtar, which ubuntu-latest does not ship; it lives in libarchive-tools. The script already fails loudly with the reason rather than silently skipping the check, which is why this surfaced immediately - so the fix is to provide the tool, not to soften the check. --- .github/workflows/iso.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/iso.yml b/.github/workflows/iso.yml index 3260d3a..24218f7 100644 --- a/.github/workflows/iso.yml +++ b/.github/workflows/iso.yml @@ -42,6 +42,11 @@ jobs: sudo apt-get clean || true df -h / + - name: Install inspection tools + # inspect-iso.sh reads the ISO9660 filesystem with bsdtar, which + # ubuntu-latest does not ship (it lives in libarchive-tools). + run: sudo apt-get update -qq && sudo apt-get install -y -qq libarchive-tools + - name: Build (via ./build.sh) run: ./build.sh From 2e8994ef81462bb96d3f86cf14ff219e434906b1 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 04:22:59 +0300 Subject: [PATCH 26/33] fix(test): bind the QEMU audio device to an audiodev CI run 36364204960 failed live-health after 3 seconds: qemu-system-x86_64: -device hda-duplex: no default audio driver available guest never reached a login prompt live-health.sh declared '-audiodev none,id=nosnd' but never attached it to hda-duplex, so QEMU aborted at startup. On a host with no default audio backend - every GitHub runner - that is fatal. test-iso.sh had the same latent bug and would have aborted identically for any developer whose machine has no default QEMU audio backend; it now takes SIMOS_QEMU_AUDIODEV, defaulting to a silent-but-always-valid 'none'. live-health.sh now also distinguishes 'QEMU exited' from 'timed out'. They need completely different investigation: the first is a harness problem, the second may be emulation slowness on a runner without KVM. --- scripts/live-health.sh | 15 ++++++++++++--- scripts/test-iso.sh | 7 ++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/scripts/live-health.sh b/scripts/live-health.sh index aa3b808..fb3b8fd 100755 --- a/scripts/live-health.sh +++ b/scripts/live-health.sh @@ -56,7 +56,7 @@ qemu-system-x86_64 \ -display none -vga std \ -serial "pipe:$PIPE" \ -device virtio-net-pci,netdev=n0 -netdev user,id=n0 \ - -device intel-hda -device hda-duplex -audiodev none,id=nosnd \ + -audiodev none,id=nosnd -device intel-hda -device hda-duplex,audiodev=nosnd \ -drive "file=$ISO,media=cdrom,readonly=on" -boot order=d -no-reboot & QPID=$! # shellcheck disable=SC2064 @@ -79,8 +79,17 @@ wait_for() { # pattern, seconds } info "Waiting for the login prompt" -if ! wait_for 'simulationos login:|login:' "$TIMEOUT"; then - red "guest never reached a login prompt"; tail -30 "$LOG"; exit 1 +wait_for 'simulationos login:|login:' "$TIMEOUT"; rc=$? +if [ "$rc" -eq 2 ]; then + red "QEMU exited before the guest reached a login prompt." + red "This is usually a QEMU invocation problem, not a SimulationOS problem." + red "Serial output captured so far:"; tail -30 "$LOG" + exit 1 +elif [ "$rc" -ne 0 ]; then + red "timed out after ${TIMEOUT}s waiting for a login prompt" + red "(hosted runners have no KVM, so this runs under TCG emulation)" + tail -30 "$LOG" + exit 1 fi green " login prompt reached" diff --git a/scripts/test-iso.sh b/scripts/test-iso.sh index 0732f6c..3692142 100755 --- a/scripts/test-iso.sh +++ b/scripts/test-iso.sh @@ -31,6 +31,10 @@ usage: test-iso.sh [options] [path/to.iso] --boot-disk boot ONLY the virtual disk (no ISO attached) - verifies the installed system boots independently of the medium --ram MB guest memory (default 4096) + +Environment: + SIMOS_QEMU_AUDIODEV QEMU audio backend (default "none" = silent but always + works). Set to pa, alsa or coreaudio for real sound. --cpus N guest vCPUs (default 2) If no ISO path is given, the newest out/simulationos-*.iso is used. @@ -76,7 +80,8 @@ QEMU=(qemu-system-x86_64 -m "$RAM" -device virtio-vga-gl -display gtk,gl=on - -device intel-hda -device hda-duplex + -audiodev "${SIMOS_QEMU_AUDIODEV:-none},id=snd0" + -device intel-hda -device hda-duplex,audiodev=snd0 -device virtio-net-pci,netdev=n0 -netdev user,id=n0 -device qemu-xhci -device usb-tablet) From 38438d7b25482c69e10eb0d78f8bb40be7a0a4e1 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 04:58:54 +0300 Subject: [PATCH 27/33] fix(test): stop pagers and fixed sleeps corrupting the serial session CI run 36365762103 reached the login prompt and ran the checks, but 12 of 13 assertions failed and the log contained a 'less' help screen. Two harness defects, not SimulationOS defects: 1. systemctl and journalctl page by default. A pager took over the serial console, so every subsequent command was delivered as keystrokes INSIDE less rather than to the shell. The guest shell now exports PAGER=cat SYSTEMD_PAGER=cat SYSTEMD_COLORS=0 TERM=dumb. 2. Collection used fixed sleeps. Under TCG on a runner without KVM a command routinely takes longer than any sleep worth guessing, so replies landed in the middle of the next command and section markers never matched. Each command now waits for its own ##END marker with a bounded timeout, so slowness delays the run instead of corrupting it. Also fixes a malformed 'journalctl -g -i hyprland' (-g takes a pattern). The assertions themselves are unchanged: this makes the measurement reliable, it does not make the checks easier to pass. --- scripts/live-health.sh | 46 +++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/scripts/live-health.sh b/scripts/live-health.sh index fb3b8fd..8d6127c 100755 --- a/scripts/live-health.sh +++ b/scripts/live-health.sh @@ -97,32 +97,40 @@ green " login prompt reached" # the checks can be located unambiguously in the log. say ""; sleep 2 say "root"; sleep 8 -say "export PS1=; stty -echo 2>/dev/null; echo SIMOS_SHELL_READY" +say "export PS1= PAGER=cat SYSTEMD_PAGER=cat SYSTEMD_PAGERSECURE=0 SYSTEMD_COLORS=0 TERM=dumb; stty -echo 2>/dev/null; echo SIMOS_SHELL_READY" if ! wait_for 'SIMOS_SHELL_READY' 180; then red "serial root login failed"; tail -30 "$LOG"; exit 1 fi green " serial root shell obtained" -run() { say "echo '##BEGIN $1'; { $2 ; } 2>&1; echo '##END $1'"; sleep "${3:-6}"; } +# Send a command and wait for its END marker, so slow guests cannot cause +# commands to interleave. Never blocks forever. +run() { + local label="$1" cmd="$2" limit="${3:-120}" + say "echo '##BEGIN $label'; { $cmd ; } 2>&1; echo '##END $label'" + if ! wait_for "##END $label" "$limit"; then + printf ' (timed out collecting %s)\n' "$label" >&2 + fi +} info "Collecting live-system state" -run default-target 'systemctl get-default' 5 -run failed-units 'systemctl --failed --no-legend --plain' 8 -run graphical 'systemctl is-active graphical.target' 5 -run displaymanager 'systemctl is-active display-manager.service; systemctl is-active sddm.service' 6 -run sessions 'loginctl list-sessions --no-legend; loginctl list-users --no-legend' 6 -run liveuser 'id liveuser' 5 -run hyprland 'pgrep -a Hyprland || echo NO_HYPRLAND' 5 -run session-procs 'pgrep -a -f "waybar|hyprpaper|mako|hyprpolkitagent|nm-applet" || echo NO_SESSION_PROCS' 5 -run portals 'pgrep -a -f "xdg-desktop-portal" || echo NO_PORTALS' 5 -run networkmanager 'systemctl is-active NetworkManager; nmcli -t general status; nmcli -t device status' 8 -run dns 'getent hosts archlinux.org || echo DNS_FAIL' 10 -run pipewire 'runuser -u liveuser -- env XDG_RUNTIME_DIR=/run/user/1000 wpctl status 2>&1 | head -20 || echo WPCTL_FAIL' 8 -run calamares-bin 'test -x /usr/bin/calamares && echo CALAMARES_PRESENT || echo CALAMARES_MISSING' 5 -run calamares-cfg 'test -f /usr/share/simulationos/calamares/settings.conf && echo CALAMARES_CFG_PRESENT || echo CALAMARES_CFG_MISSING' 5 -run desktop-entry 'test -f /usr/share/applications/simulationos-install.desktop && echo DESKTOP_PRESENT || echo DESKTOP_MISSING' 5 -run sddm-journal 'journalctl -u sddm --no-pager -n 25 2>&1 | tail -25' 8 -run hypr-journal 'journalctl --no-pager -n 25 -g -i hyprland 2>&1 | tail -25 || true' 8 +run default-target 'systemctl get-default' +run failed-units 'systemctl --failed --no-legend --plain' +run graphical 'systemctl is-active graphical.target' +run displaymanager 'systemctl is-active display-manager.service; systemctl is-active sddm.service' +run sessions 'loginctl list-sessions --no-legend; loginctl list-users --no-legend' +run liveuser 'id liveuser' +run hyprland 'pgrep -a Hyprland || echo NO_HYPRLAND' +run session-procs 'pgrep -a -f "waybar|hyprpaper|mako|hyprpolkitagent|nm-applet" || echo NO_SESSION_PROCS' +run portals 'pgrep -a -f "xdg-desktop-portal" || echo NO_PORTALS' +run networkmanager 'systemctl is-active NetworkManager; nmcli -t general status; nmcli -t device status' +run dns 'getent hosts archlinux.org || echo DNS_FAIL' +run pipewire 'runuser -u liveuser -- env XDG_RUNTIME_DIR=/run/user/1000 wpctl status 2>&1 | head -20 || echo WPCTL_FAIL' +run calamares-bin 'test -x /usr/bin/calamares && echo CALAMARES_PRESENT || echo CALAMARES_MISSING' +run calamares-cfg 'test -f /usr/share/simulationos/calamares/settings.conf && echo CALAMARES_CFG_PRESENT || echo CALAMARES_CFG_MISSING' +run desktop-entry 'test -f /usr/share/applications/simulationos-install.desktop && echo DESKTOP_PRESENT || echo DESKTOP_MISSING' +run sddm-journal 'journalctl -u sddm --no-pager -n 25 2>&1 | tail -25' +run hypr-journal 'journalctl --no-pager -b -n 200 2>&1 | grep -i -m 20 hyprland || echo NO_HYPRLAND_JOURNAL' say "echo SIMOS_CHECKS_DONE" wait_for 'SIMOS_CHECKS_DONE' 240 || red "checks did not complete cleanly (log may be partial)" From 5647aaf1bcd73bc667da34874d270440658cfdeb Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 05:29:28 +0300 Subject: [PATCH 28/33] fix(test): markers must come from guest output, not terminal echo Inspecting the serial log from run 36368038012 showed the collected sections contained only ECHOED COMMAND TEXT, never command output. The serial line echoes what we type, and the command text itself contained the literal '##BEGIN x' and '##END x'. So: - wait_for matched the echo and returned immediately, before the command had run; - the assertions then matched against echoed command text, which happens to contain words like 'graphical', 'active' and 'Hyprland'. 11 of 13 checks were therefore FALSE PASSES. The job only went red at all because two patterns did not appear in any echoed line. A test that passes by matching its own input is worse than no test. Markers are now assembled at runtime with printf: the typed line contains '##BEG%s'/'##EN%s', so only genuine guest output can produce '##BEGIN label' and '##END label'. Section extraction anchors on whole lines, stty -echo is sent as its own command once the shell is ready and its effect is confirmed rather than assumed. No assertion was weakened; this makes the results real. --- scripts/live-health.sh | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/scripts/live-health.sh b/scripts/live-health.sh index 8d6127c..672fab1 100755 --- a/scripts/live-health.sh +++ b/scripts/live-health.sh @@ -97,18 +97,28 @@ green " login prompt reached" # the checks can be located unambiguously in the log. say ""; sleep 2 say "root"; sleep 8 -say "export PS1= PAGER=cat SYSTEMD_PAGER=cat SYSTEMD_PAGERSECURE=0 SYSTEMD_COLORS=0 TERM=dumb; stty -echo 2>/dev/null; echo SIMOS_SHELL_READY" -if ! wait_for 'SIMOS_SHELL_READY' 180; then +say "export PS1= PAGER=cat SYSTEMD_PAGER=cat SYSTEMD_PAGERSECURE=0 SYSTEMD_COLORS=0 TERM=dumb" +sleep 3 +say "stty -echo 2>/dev/null; printf 'SIMOS_SHELL_%s\\n' READY" +if ! wait_for '^SIMOS_SHELL_READY' 240; then red "serial root login failed"; tail -30 "$LOG"; exit 1 fi green " serial root shell obtained" # Send a command and wait for its END marker, so slow guests cannot cause # commands to interleave. Never blocks forever. +# The serial line echoes whatever we type. If the marker literals appeared in +# the command text, every marker would match the ECHO rather than the output: +# wait_for would return instantly and the assertions would match echoed +# command text instead of real results (a silent false pass). +# +# So the markers are ASSEMBLED AT RUNTIME with printf. The typed line contains +# "##BEG%s"/"##EN%s"; only genuine guest output can contain "##BEGIN label" +# and "##END label". run() { - local label="$1" cmd="$2" limit="${3:-120}" - say "echo '##BEGIN $label'; { $cmd ; } 2>&1; echo '##END $label'" - if ! wait_for "##END $label" "$limit"; then + local label="$1" cmd="$2" limit="${3:-180}" + say "printf '##BEG%s %s\\n' IN $label; { $cmd ; } 2>&1; printf '##EN%s %s\\n' D $label" + if ! wait_for "^##END $label" "$limit"; then printf ' (timed out collecting %s)\n' "$label" >&2 fi } @@ -141,7 +151,8 @@ kill "$QPID" 2>/dev/null; kill "$CATPID" 2>/dev/null # ------------------------------------------------------------------- verdict printf '\n== live system acceptance\n' ERRORS=0 -sect() { sed -n "/##BEGIN $1/,/##END $1/p" "$LOG" 2>/dev/null; } +sect() { tr -d '\000' < "$LOG" | sed 's/\x1b\[[0-9;?=]*[a-zA-Z]//g' \ + | sed -n "/^##BEGIN $1\$/,/^##END $1\$/p" 2>/dev/null; } assert() { # label, section, pattern if sect "$2" | grep -aqE "$3"; then green " ok $1"; else red " FAILED $1"; ERRORS=$((ERRORS+1)); fi } From 1e1aa4754f100745d162c0a92c86aed4d5ed00bc Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 06:01:09 +0300 Subject: [PATCH 29/33] fix(test): clean the console stream before matching, and attach OVMF Run 36370016973 reported 'serial root login failed' even though the guest had emitted SIMOS_SHELL_READY. Two defects: 1. wait_for grepped the RAW serial log while sect() grepped a cleaned copy. systemd emits OSC shell-integration sequences (ESC]3008;...) immediately before command output, so a '^'-anchored pattern could never match in the raw stream. Both now share one clean_log helper that strips NULs, CSI colour codes and OSC sequences and converts CR to LF, so line anchors mean what they say. 2. The OVMF firmware was located but never passed to QEMU, so the guest booted via SeaBIOS/ISOLINUX instead of UEFI. The pflash drives are now attached, making this a real UEFI live-session test. Incidentally the log confirms the BIOS path end to end: ISOLINUX 6.04 -> SimulationOS menu -> vmlinuz-linux-cachyos -> initramfs -> login prompt. --- scripts/live-health.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/scripts/live-health.sh b/scripts/live-health.sh index 672fab1..16fcaa0 100755 --- a/scripts/live-health.sh +++ b/scripts/live-health.sh @@ -54,6 +54,8 @@ info "Booting $(basename "$ISO") for live health checks (timeout ${TIMEOUT}s)" qemu-system-x86_64 \ -machine "q35,accel=$ACCEL" -cpu max -smp 2 -m "$RAM" \ -display none -vga std \ + -drive "if=pflash,format=raw,unit=0,readonly=on,file=$OVMF_CODE" \ + -drive "if=pflash,format=raw,unit=1,file=$VARS" \ -serial "pipe:$PIPE" \ -device virtio-net-pci,netdev=n0 -netdev user,id=n0 \ -audiodev none,id=nosnd -device intel-hda -device hda-duplex,audiodev=nosnd \ @@ -68,11 +70,21 @@ CATPID=$! exec 3>"$PIPE.in" say() { printf '%s\n' "$1" >&3; } +# Strip NULs, CSI colour codes, OSC sequences (systemd shell integration) +# and turn CR into LF so line anchors mean what they say. +clean_log() { + tr -d '\000' < "$LOG" \ + | sed -e 's/\x1b\][^\x07]*\x07//g' \ + -e 's/\x1b\][^\x1b]*\x1b\\//g' \ + -e 's/\x1b\[[0-9;?=]*[a-zA-Z]//g' \ + -e 's/\r/\n/g' +} + wait_for() { # pattern, seconds local deadline=$(( $(date +%s) + $2 )) while [ "$(date +%s)" -lt "$deadline" ]; do kill -0 "$QPID" 2>/dev/null || return 2 - grep -aqE "$1" "$LOG" && return 0 + clean_log | grep -aqE "$1" && return 0 sleep 3 done return 1 @@ -151,8 +163,7 @@ kill "$QPID" 2>/dev/null; kill "$CATPID" 2>/dev/null # ------------------------------------------------------------------- verdict printf '\n== live system acceptance\n' ERRORS=0 -sect() { tr -d '\000' < "$LOG" | sed 's/\x1b\[[0-9;?=]*[a-zA-Z]//g' \ - | sed -n "/^##BEGIN $1\$/,/^##END $1\$/p" 2>/dev/null; } +sect() { clean_log | sed -n "/^##BEGIN $1\$/,/^##END $1\$/p" 2>/dev/null; } assert() { # label, section, pattern if sect "$2" | grep -aqE "$3"; then green " ok $1"; else red " FAILED $1"; ERRORS=$((ERRORS+1)); fi } From b2df467a190fb8c4ebaf5626723aa75faa4c766a Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 09:17:13 +0300 Subject: [PATCH 30/33] fix(test): normalise markers; test portals by D-Bus activation Replaying run 36372041948's serial log with corrected extraction shows the live desktop genuinely works: sessions: 2 1000 liveuser seat0 674 user tty1 <- SDDM autologin hyprland: 772 Hyprland --watchdog-fd 4 session: 817 hyprpaper 818 waybar 819 mako 821 nm-applet 828 hyprpolkitagent 12 of 13 checks pass on real guest output. Two remaining harness issues: 1. Residual OSC text can precede a marker on the same line, so '^##BEGIN x' never matched (BEGIN=0 while END=17). clean_log now forces each marker onto its own line, making extraction independent of what precedes it. 2. The portal check was simply wrong. xdg-desktop-portal is D-Bus activated and is SUPPOSED to be absent until something requests a portal, so pgrep could only pass by accident. It now asks D-Bus to activate the portal and confirms it answers - a functional test rather than a liveness guess. --- scripts/live-health.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/live-health.sh b/scripts/live-health.sh index 16fcaa0..65f84fd 100755 --- a/scripts/live-health.sh +++ b/scripts/live-health.sh @@ -77,7 +77,8 @@ clean_log() { | sed -e 's/\x1b\][^\x07]*\x07//g' \ -e 's/\x1b\][^\x1b]*\x1b\\//g' \ -e 's/\x1b\[[0-9;?=]*[a-zA-Z]//g' \ - -e 's/\r/\n/g' + -e 's/\r/\n/g' \ + | awk '{gsub(/##BEGIN /,"\n##BEGIN "); gsub(/##END /,"\n##END "); print}' } wait_for() { # pattern, seconds @@ -144,7 +145,7 @@ run sessions 'loginctl list-sessions --no-legend; loginctl list-users -- run liveuser 'id liveuser' run hyprland 'pgrep -a Hyprland || echo NO_HYPRLAND' run session-procs 'pgrep -a -f "waybar|hyprpaper|mako|hyprpolkitagent|nm-applet" || echo NO_SESSION_PROCS' -run portals 'pgrep -a -f "xdg-desktop-portal" || echo NO_PORTALS' +run portals 'runuser -u liveuser -- env XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus busctl --user introspect org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop >/dev/null 2>&1 && echo PORTAL_ACTIVATES || echo PORTAL_NO_ACTIVATE; pgrep -a -f xdg-desktop-portal || true' run networkmanager 'systemctl is-active NetworkManager; nmcli -t general status; nmcli -t device status' run dns 'getent hosts archlinux.org || echo DNS_FAIL' run pipewire 'runuser -u liveuser -- env XDG_RUNTIME_DIR=/run/user/1000 wpctl status 2>&1 | head -20 || echo WPCTL_FAIL' From 6bcbb90114f4e1158398357b1dea3b4c71d2d920 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 09:52:04 +0300 Subject: [PATCH 31/33] fix(test): wait for the desktop to settle; match service states exactly Run 36385709091 exposed two defects in the checks themselves. 1. 'active' is a SUBSTRING of 'inactive'. The graphical.target section literally contained 'inactive' and the assertion passed anyway. Service state assertions now anchor on '^active$'. This was a false pass that would have been reported as a verified graphical target. 2. session-procs passed in run 36372041948 and failed in 36385709091 from the same ISO: the checks race the session still starting under TCG on a runner without KVM. The guest now waits for graphical.target AND waybar AND Hyprland before anything is asserted, up to 10 minutes, and emits DESKTOP_READY. That readiness is itself a new gate, so a desktop that never comes up fails loudly instead of producing a flaky mixture. The portal assertion is renamed to 'present' to describe what it checks, now that activation is attempted first. --- scripts/live-health.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/live-health.sh b/scripts/live-health.sh index 65f84fd..d9feb77 100755 --- a/scripts/live-health.sh +++ b/scripts/live-health.sh @@ -136,6 +136,9 @@ run() { fi } +info "Waiting for the graphical session to settle (inside the guest)" +run wait-desktop 'for i in $(seq 1 120); do if systemctl is-active --quiet graphical.target && pgrep -f waybar >/dev/null 2>&1 && pgrep -x Hyprland >/dev/null 2>&1; then echo DESKTOP_READY; break; fi; sleep 5; done; systemctl is-active graphical.target' 900 + info "Collecting live-system state" run default-target 'systemctl get-default' run failed-units 'systemctl --failed --no-legend --plain' @@ -170,14 +173,15 @@ assert() { # label, section, pattern } report() { printf ' ----- %s\n' "$1"; sect "$1" | sed '1d;$d' | sed 's/^/ /' | head -25; } +assert "desktop reached readiness" wait-desktop 'DESKTOP_READY' assert "default target is graphical" default-target 'graphical.target' -assert "graphical.target active" graphical '^active' -assert "display-manager active" displaymanager '^active' +assert "graphical.target active" graphical '^active$' +assert "display-manager active" displaymanager '^active$' assert "liveuser exists" liveuser 'uid=1000' assert "Hyprland running" hyprland 'Hyprland' assert "session components running" session-procs 'waybar|hyprpaper|mako|hyprpolkitagent' -assert "xdg-desktop-portal running" portals 'xdg-desktop-portal' -assert "NetworkManager active" networkmanager '^active' +assert "xdg-desktop-portal present" portals 'PORTAL_ACTIVATES|xdg-desktop-portal' +assert "NetworkManager active" networkmanager '^active$' assert "DNS resolves" dns 'archlinux\.org' assert "PipeWire running" pipewire 'PipeWire|Audio' assert "Calamares installed" calamares-bin 'CALAMARES_PRESENT' From 9ca4549186634c33e5e2f13ead311acb112861c8 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 11:38:16 +0300 Subject: [PATCH 32/33] chore: ignore downloaded CI artifacts artifacts/ holds ISOs pulled from GitHub Actions for local verification and must never be committed. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6dfb260..4585ba2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /out/ # local pacman package cache used by the containerised builder /.cache/ +/artifacts/ From 4a277a5ef2f593eda0077d9554538e22d23c80d8 Mon Sep 17 00:00:00 2001 From: Haider Bassem Date: Mon, 28 Sep 2026 11:38:34 +0300 Subject: [PATCH 33/33] chore: ignore .DS_Store --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4585ba2..1cf22f9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ # local pacman package cache used by the containerised builder /.cache/ /artifacts/ +.DS_Store