From 02172f5fc997dbcc4a589d0e05b88d451d79c2d7 Mon Sep 17 00:00:00 2001 From: Rdbo Date: Sat, 31 Oct 2026 01:02:35 +0000 Subject: [PATCH 1/6] rework the build system and many little issues Since we're here... I think we can do some improvements in the build system of this project. For example, as you may have noticed, the changes currently are to disable building the other packages, and stop after building. This is because at the time, I only wanted to build Linux, and install it in my actual system, instead of fully building sigma linux. (I already reset --hard so you wont be able to see the old changes but just take my word for it) That being said, I want some big changes to be done here. 1. Turn the building of this project into Ruby. I started with bash because it was simple, but I feel like bash gets in your way a lot after the project grows. 2. Make the build more modular. Much like I described above, sometimes you can't even do a simple task like telling it to build one package only and stopping, because the script was meant to just go and make the ISO. That changes now. Here's an idea, but you probably know better than me about this stuff: maybe making stages would be nice. And I can execute stages separately and individually if I want to, and maybe I can start the full build from a specific stage, maybe I can do only a subset of a stage, etc. Like I said: you be the judge here. I haven't actually measured the weight of this change, neither have I looked into alternatives or other options. Look at this problem with care, don't just take my word for it. 3. I want this to be beautiful. Not just a boring, ugly script, make it beautiful. You are allowed to re-architect existing choices regarding the build system, the local packages and other stuff you deem should change. This is mostly a "refactor"/"rework"/"rearchitect" sort of change anyways. You can also make changes to the dockerfile, if needed. Maybe even changes to improve caching and other stuff. One final change i want you to make also: make sure that the GRUB config updates correctly when there are kernel changes, but doesn't lose my grub configurations that are in this project (the letter colors, the wallpaper...) - everytime I update my kernel, a GRUB trigger reruns and fully overwrites my config. --- Now I'm asking quite a bit of stuff here, so I recommend you divide this into phases. Execute one phase at a time, divide according to a common goal of one or many tasks. When you finish a task, check it. When you finish a phase, check it. (or mark as done however you like). Make it in a PLAN.md at the root of the project. --- .dockerignore | 12 +- .github/actions/container.yml | 5 - .github/workflows/build-iso.yml | 37 +- .gitignore | 5 +- Dockerfile | 56 ++- PLAN.md | 312 ++++++++++++++++ README.md | 57 ++- build.sh | 38 -- {src => config}/repositories | 0 config/sigma.yml | 57 +++ docker-env.sh | 38 -- image/grub.cfg.erb | 21 ++ image/overlay/etc/conf.d/zram-init | 10 + image/overlay/etc/doas.conf | 2 + image/overlay/etc/fstab | 0 image/overlay/etc/issue | 2 + image/overlay/etc/modules | 14 + image/overlay/etc/motd | 8 + image/overlay/etc/network/interfaces | 2 + lib/sigma.rb | 30 ++ lib/sigma/apkbuild.rb | 63 ++++ lib/sigma/cli.rb | 209 +++++++++++ lib/sigma/config.rb | 153 ++++++++ lib/sigma/context.rb | 66 ++++ lib/sigma/docker.rb | 109 ++++++ lib/sigma/errors.rb | 35 ++ lib/sigma/fingerprint.rb | 39 ++ lib/sigma/local_packages.rb | 90 +++++ lib/sigma/mounts.rb | 64 ++++ lib/sigma/ownership.rb | 27 ++ lib/sigma/pipeline.rb | 186 +++++++++ lib/sigma/profile.rb | 64 ++++ lib/sigma/qemu.rb | 74 ++++ lib/sigma/selection.rb | 74 ++++ lib/sigma/shell.rb | 114 ++++++ lib/sigma/signing.rb | 43 +++ lib/sigma/stage.rb | 123 ++++++ lib/sigma/stages/iso.rb | 109 ++++++ lib/sigma/stages/packages.rb | 213 +++++++++++ lib/sigma/stages/rootfs.rb | 180 +++++++++ lib/sigma/stages/squashfs.rb | 37 ++ lib/sigma/state.rb | 47 +++ lib/sigma/ui.rb | 352 ++++++++++++++++++ {src/apk => packages}/biblegen/APKBUILD | 1 + {src/apk => packages}/initrdbo/APKBUILD | 0 .../initrdbo/initrdbo.trigger | 0 {src/apk => packages}/linux-sigma/APKBUILD | 0 .../linux-sigma/sigma.x86_64.config | 0 {src/apk => packages}/rdbo-archives/APKBUILD | 0 .../rdbo-archives/archiver.rb | 0 .../rdbo-archives/src/archive.rb | 0 .../rdbo-archives/src/archiver.rb | 0 {src/apk => packages}/sigma-conf/APKBUILD | 0 .../rootfs/etc/modules-load.d/sigma.conf | 0 .../etc/polkit-1/rules.d/99-network.rules | 0 .../sigma-conf/rootfs/etc/profile.d/xdg.sh | 0 .../sigma-conf/rootfs/etc/skel/.Xresources | 0 .../sigma-conf/rootfs/etc/skel/.bashrc | 0 .../etc/skel/.config/autostart/sigma.desktop | 0 .../v1/first_day_of_week | 0 .../v1/military_time | 0 .../v1/amplification_sink | 0 .../v1/amplification_source | 0 .../com.system76.CosmicBackground/v1/all | 0 .../v1/same-on-all | 0 .../com.system76.CosmicComp/v1/autotile | 0 .../v1/autotile_behavior | 0 .../v1/focus_follows_cursor | 0 .../v1/focus_follows_cursor_delay | 0 .../com.system76.CosmicComp/v1/xkb_config | 0 .../com.system76.CosmicPanel.Dock/v1/anchor | 0 .../v1/anchor_gap | 0 .../com.system76.CosmicPanel.Dock/v1/autohide | 0 .../v1/autohover_delay_ms | 0 .../v1/background | 0 .../v1/border_radius | 0 .../v1/exclusive_zone | 0 .../v1/expand_to_edges | 0 .../v1/keyboard_interactivity | 0 .../com.system76.CosmicPanel.Dock/v1/layer | 0 .../com.system76.CosmicPanel.Dock/v1/margin | 0 .../com.system76.CosmicPanel.Dock/v1/name | 0 .../com.system76.CosmicPanel.Dock/v1/opacity | 0 .../com.system76.CosmicPanel.Dock/v1/output | 0 .../com.system76.CosmicPanel.Dock/v1/padding | 0 .../v1/padding_overlap | 0 .../v1/plugins_center | 0 .../v1/plugins_wings | 0 .../com.system76.CosmicPanel.Dock/v1/size | 0 .../v1/size_center | 0 .../v1/size_wings | 0 .../com.system76.CosmicPanel.Dock/v1/spacing | 0 .../com.system76.CosmicPanel.Panel/v1/anchor | 0 .../v1/anchor_gap | 0 .../v1/autohide | 0 .../v1/autohover_delay_ms | 0 .../v1/background | 0 .../v1/border_radius | 0 .../v1/exclusive_zone | 0 .../v1/expand_to_edges | 0 .../v1/keyboard_interactivity | 0 .../com.system76.CosmicPanel.Panel/v1/layer | 0 .../com.system76.CosmicPanel.Panel/v1/margin | 0 .../com.system76.CosmicPanel.Panel/v1/name | 0 .../com.system76.CosmicPanel.Panel/v1/opacity | 0 .../com.system76.CosmicPanel.Panel/v1/output | 0 .../com.system76.CosmicPanel.Panel/v1/padding | 0 .../v1/padding_overlap | 0 .../v1/plugins_center | 0 .../v1/plugins_wings | 0 .../com.system76.CosmicPanel.Panel/v1/size | 0 .../v1/size_center | 0 .../v1/size_wings | 0 .../com.system76.CosmicPanel.Panel/v1/spacing | 0 .../com.system76.CosmicPanel/v1/entries | 0 .../com.system76.CosmicPortal/v1/screenshot | 0 .../v1/custom | 0 .../v1/system_actions | 0 .../v1/custom-images | 0 .../v1/accent | 0 .../v1/bg_color | 0 .../v1/text_tint | 0 .../com.system76.CosmicTheme.Dark/v1/accent | 0 .../v1/accent_button | 0 .../v1/background | 0 .../com.system76.CosmicTheme.Dark/v1/button | 0 .../v1/destructive | 0 .../v1/destructive_button | 0 .../v1/icon_button | 0 .../v1/link_button | 0 .../com.system76.CosmicTheme.Dark/v1/palette | 0 .../com.system76.CosmicTheme.Dark/v1/primary | 0 .../v1/secondary | 0 .../com.system76.CosmicTheme.Dark/v1/success | 0 .../v1/text_button | 0 .../com.system76.CosmicTheme.Dark/v1/warning | 0 .../v1/warning_button | 0 .../com.system76.CosmicTk/v1/interface_font | 0 .../com.system76.CosmicTk/v1/monospace_font | 0 .../rootfs/etc/skel/.config/foot/foot.ini | 0 .../rootfs/etc/skel/.config/fuzzel/fuzzel.ini | 0 .../etc/skel/.config/gtk-3.0/settings.ini | 0 .../rootfs/etc/skel/.config/helix/config.toml | 0 .../etc/skel/.config/helix/languages.toml | 0 .../rootfs/etc/skel/.config/helix/runtime | 0 .../etc/skel/.config/helix/themes/sigma.toml | 0 .../etc/skel/.config/hypr/hyprland.conf | 0 .../etc/skel/.config/keepassxc/keepassxc.ini | 0 .../rootfs/etc/skel/.config/kitty/kitty.conf | 0 .../etc/skel/.config/luakit/userconf.lua | 0 .../rootfs/etc/skel/.config/mako/config | 0 .../etc/skel/.config/mango/autostart.sh | 0 .../rootfs/etc/skel/.config/mango/config.conf | 0 .../etc/skel/.config/neofetch/config.conf | 0 .../rootfs/etc/skel/.config/niri/config.kdl | 0 .../rootfs/etc/skel/.config/rio/config.toml | 0 .../rootfs/etc/skel/.config/river/init | 0 .../rootfs/etc/skel/.config/sc-im/scimrc | 0 .../rootfs/etc/skel/.config/sigma/background | 0 .../rootfs/etc/skel/.config/sigma/startup.sh | 0 .../rootfs/etc/skel/.config/starship.toml | 0 .../rootfs/etc/skel/.config/swaylock/config | 0 .../etc/skel/.config/waybar/config.jsonc | 0 .../rootfs/etc/skel/.config/waybar/style.css | 0 .../rootfs/etc/skel/.config/xfe/xferc | 2 - .../rootfs/etc/skel/.config/yambar/config.yml | 0 .../sigma-conf/rootfs/etc/skel/.gtkrc-2.0 | 0 .../sigma-conf/rootfs/etc/skel/.inputrc | 0 .../etc/skel/.mozilla/firefox/installs.ini | 0 .../etc/skel/.mozilla/firefox/profiles.ini | 0 .../firefox/sigma.default-release/user.js | 0 .../rootfs/etc/skel/.newsboat/config | 0 .../sigma-conf/rootfs/etc/skel/.profile | 0 .../sigma-conf/rootfs/etc/skel/.tmux.conf | 0 .../rootfs/etc/skel/.vim/after/syntax/c.vim | 0 .../rootfs/etc/skel/.vim/after/syntax/cpp.vim | 0 .../sigma-conf/rootfs/etc/skel/.vimrc | 0 .../sigma-conf/rootfs/etc/skel/.zlogin | 0 .../sigma-conf/rootfs/etc/skel/.zshrc | 0 .../etc/vim/pack/themes/start/sigma/LICENSE | 0 .../etc/vim/pack/themes/start/sigma/README.md | 0 .../pack/themes/start/sigma/colors/sigma.vim | 0 .../vim/pack/vendor/start/nerdtree/.gitignore | 0 .../pack/vendor/start/nerdtree/.vintrc.yaml | 0 .../pack/vendor/start/nerdtree/CHANGELOG.md | 0 .../vim/pack/vendor/start/nerdtree/LICENCE | 0 .../vendor/start/nerdtree/README.markdown | 0 .../pack/vendor/start/nerdtree/_config.yml | 0 .../start/nerdtree/autoload/nerdtree.vim | 0 .../nerdtree/autoload/nerdtree/ui_glue.vim | 0 .../vendor/start/nerdtree/doc/NERDTree.txt | 0 .../start/nerdtree/lib/nerdtree/bookmark.vim | 0 .../start/nerdtree/lib/nerdtree/creator.vim | 0 .../start/nerdtree/lib/nerdtree/event.vim | 0 .../start/nerdtree/lib/nerdtree/flag_set.vim | 0 .../start/nerdtree/lib/nerdtree/key_map.vim | 0 .../nerdtree/lib/nerdtree/menu_controller.vim | 0 .../start/nerdtree/lib/nerdtree/menu_item.vim | 0 .../start/nerdtree/lib/nerdtree/nerdtree.vim | 0 .../start/nerdtree/lib/nerdtree/notifier.vim | 0 .../start/nerdtree/lib/nerdtree/opener.vim | 0 .../start/nerdtree/lib/nerdtree/path.vim | 0 .../nerdtree/lib/nerdtree/tree_dir_node.vim | 0 .../nerdtree/lib/nerdtree/tree_file_node.vim | 0 .../vendor/start/nerdtree/lib/nerdtree/ui.vim | 0 .../nerdtree_plugin/exec_menuitem.vim | 0 .../nerdtree/nerdtree_plugin/fs_menu.vim | 0 .../start/nerdtree/nerdtree_plugin/vcs.vim | 0 .../start/nerdtree/plugin/NERD_tree.vim | 0 .../pack/vendor/start/nerdtree/screenshot.png | Bin .../vendor/start/nerdtree/syntax/nerdtree.vim | 0 .../sigma-conf/rootfs/etc/vim/vimrc.local | 0 .../sigma-conf/rootfs/usr/bin/sigma-audio | 0 .../sigma-conf/rootfs/usr/bin/sigma-battery | 0 .../rootfs/usr/bin/sigma-change-kbdlayout | 0 .../rootfs/usr/bin/sigma-change-resolution | 0 .../sigma-conf/rootfs/usr/bin/sigma-cipher | 0 .../sigma-conf/rootfs/usr/bin/sigma-common | 0 .../sigma-conf/rootfs/usr/bin/sigma-cpu | 0 .../sigma-conf/rootfs/usr/bin/sigma-disk | 0 .../sigma-conf/rootfs/usr/bin/sigma-dmenu | 0 .../sigma-conf/rootfs/usr/bin/sigma-filemgr | 0 .../sigma-conf/rootfs/usr/bin/sigma-launcher | 0 .../rootfs/usr/bin/sigma-lock-screen | 0 .../sigma-conf/rootfs/usr/bin/sigma-memory | 0 .../sigma-conf/rootfs/usr/bin/sigma-nwif | 0 .../sigma-conf/rootfs/usr/bin/sigma-nwusage | 0 .../rootfs/usr/bin/sigma-power-menu | 0 .../rootfs/usr/bin/sigma-screenshot | 0 .../sigma-conf/rootfs/usr/bin/sigma-startup | 0 .../sigma-conf/rootfs/usr/bin/sigma-term | 0 .../rootfs/usr/bin/sigma-toggle-vkbd | 0 .../sigma-conf/rootfs/usr/bin/sigma-vkbd | 0 .../sigma-conf/rootfs/usr/sbin/setup-sigma | 23 +- .../rootfs/usr/sbin/sigma-grub-setup | 134 +++++++ .../usr/share/icons/default/index.theme | 0 .../usr/share/plymouth/themes/sigma/image.png | Bin .../plymouth/themes/sigma/sigma.plymouth | 0 .../share/plymouth/themes/sigma/sigma.script | 0 .../rootfs/usr/share/sigma/ascii/sigma | 0 .../usr/share/sigma/ascii/sigma-linux-logo | 0 .../usr/share/sigma/backgrounds/wallpaper.png | Bin .../rootfs/usr/share/sigma/grub/grub.conf | 34 ++ .../rootfs/usr/share/sigma/grub/wallpaper.png | Bin .../rootfs/usr/share/sigma/lore.cipher | 0 .../usr/share/sigma/luakit-welcome/index.html | 0 .../sigma/luakit-welcome/sigma-logo-min.svg | 0 {src/apk => packages}/sigma-dwl/APKBUILD | 0 {src/apk => packages}/sigma-dwl/config.h | 0 .../sigma-firacode-nerd/APKBUILD | 0 .../sigma-firacode-nerd/firacode.tar.xz | Bin .../sigma-flat-remix-gtk/APKBUILD | 0 .../flat-remix-gtk-green-dark.tar.xz | Bin .../sigma-material-symbols/APKBUILD | 0 .../material-symbols.tar.xz | Bin {src/apk => packages}/sigma-sent/APKBUILD | 0 {src/apk => packages}/sigma-sent/sent/LICENSE | 0 .../apk => packages}/sigma-sent/sent/Makefile | 0 .../sigma-sent/sent/README.md | 0 {src/apk => packages}/sigma-sent/sent/arg.h | 0 .../sigma-sent/sent/config.def.h | 0 .../apk => packages}/sigma-sent/sent/config.h | 0 .../sigma-sent/sent/config.mk | 0 {src/apk => packages}/sigma-sent/sent/drw.c | 0 {src/apk => packages}/sigma-sent/sent/drw.h | 0 {src/apk => packages}/sigma-sent/sent/example | 0 .../apk => packages}/sigma-sent/sent/nyan.png | Bin {src/apk => packages}/sigma-sent/sent/sent.1 | 0 {src/apk => packages}/sigma-sent/sent/sent.c | 0 .../sigma-sent/sent/transparent_test.ff | Bin {src/apk => packages}/sigma-sent/sent/util.c | 0 {src/apk => packages}/sigma-sent/sent/util.h | 0 {src/apk => packages}/sigma-st/APKBUILD | 0 {src/apk => packages}/sigma-vt323/APKBUILD | 0 .../apk => packages}/sigma-vt323/VT323.tar.xz | Bin {src/apk => packages}/sigma-wvkbd/APKBUILD | 0 {src/apk => packages}/simplex-chat/APKBUILD | 0 profiles/full.list | 73 ++++ src/pkglist.standard => profiles/minimal.list | 122 +----- profiles/standard.list | 116 ++++++ sensible_clean.sh | 5 - sigma | 7 + src/apk/sigma-vt323/src/VT323.tar.xz | 1 - src/apk/sigma-vt323/src/vt323.tar.xz | 1 - src/build_repo.sh | 29 -- src/config.sh | 56 --- src/make_iso.sh | 60 --- src/make_squashfs.sh | 213 ----------- src/pkglist.full | 273 -------------- src/pkglist.minimal | 304 --------------- src/setup_apks.sh | 92 ----- start.sh | 8 - start_uefi.sh | 9 - test/iso_test.rb | 59 +++ test/packages_test.rb | 38 ++ test/profile_test.rb | 36 ++ test/run.rb | 4 + test/selection_test.rb | 47 +++ test/stage_test.rb | 73 ++++ test/test_helper.rb | 16 + test/ui_test.rb | 26 ++ 301 files changed, 3721 insertions(+), 1318 deletions(-) delete mode 100644 .github/actions/container.yml create mode 100644 PLAN.md delete mode 100755 build.sh rename {src => config}/repositories (100%) create mode 100644 config/sigma.yml delete mode 100755 docker-env.sh create mode 100644 image/grub.cfg.erb create mode 100644 image/overlay/etc/conf.d/zram-init create mode 100644 image/overlay/etc/doas.conf create mode 100644 image/overlay/etc/fstab create mode 100644 image/overlay/etc/issue create mode 100644 image/overlay/etc/modules create mode 100644 image/overlay/etc/motd create mode 100644 image/overlay/etc/network/interfaces create mode 100644 lib/sigma.rb create mode 100644 lib/sigma/apkbuild.rb create mode 100644 lib/sigma/cli.rb create mode 100644 lib/sigma/config.rb create mode 100644 lib/sigma/context.rb create mode 100644 lib/sigma/docker.rb create mode 100644 lib/sigma/errors.rb create mode 100644 lib/sigma/fingerprint.rb create mode 100644 lib/sigma/local_packages.rb create mode 100644 lib/sigma/mounts.rb create mode 100644 lib/sigma/ownership.rb create mode 100644 lib/sigma/pipeline.rb create mode 100644 lib/sigma/profile.rb create mode 100644 lib/sigma/qemu.rb create mode 100644 lib/sigma/selection.rb create mode 100644 lib/sigma/shell.rb create mode 100644 lib/sigma/signing.rb create mode 100644 lib/sigma/stage.rb create mode 100644 lib/sigma/stages/iso.rb create mode 100644 lib/sigma/stages/packages.rb create mode 100644 lib/sigma/stages/rootfs.rb create mode 100644 lib/sigma/stages/squashfs.rb create mode 100644 lib/sigma/state.rb create mode 100644 lib/sigma/ui.rb rename {src/apk => packages}/biblegen/APKBUILD (98%) rename {src/apk => packages}/initrdbo/APKBUILD (100%) rename {src/apk => packages}/initrdbo/initrdbo.trigger (100%) rename {src/apk => packages}/linux-sigma/APKBUILD (100%) rename {src/apk => packages}/linux-sigma/sigma.x86_64.config (100%) rename {src/apk => packages}/rdbo-archives/APKBUILD (100%) rename {src/apk => packages}/rdbo-archives/archiver.rb (100%) rename {src/apk => packages}/rdbo-archives/src/archive.rb (100%) rename {src/apk => packages}/rdbo-archives/src/archiver.rb (100%) rename {src/apk => packages}/sigma-conf/APKBUILD (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/modules-load.d/sigma.conf (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/polkit-1/rules.d/99-network.rules (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/profile.d/xdg.sh (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.Xresources (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.bashrc (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/autostart/sigma.desktop (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/first_day_of_week (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/military_time (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_sink (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_source (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/all (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/same-on-all (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile_behavior (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor_delay (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/xkb_config (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor_gap (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohide (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohover_delay_ms (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/background (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/border_radius (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/exclusive_zone (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/expand_to_edges (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/keyboard_interactivity (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/layer (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/margin (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/name (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/opacity (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/output (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding_overlap (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_center (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_wings (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_center (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_wings (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/spacing (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor_gap (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohide (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohover_delay_ms (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/background (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/border_radius (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/exclusive_zone (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/expand_to_edges (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/keyboard_interactivity (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/layer (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/margin (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/name (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/opacity (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/output (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding_overlap (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_center (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_wings (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_center (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_wings (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/spacing (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel/v1/entries (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPortal/v1/screenshot (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/system_actions (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Wallpaper/v1/custom-images (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/accent (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/bg_color (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/text_tint (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent_button (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/background (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/button (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive_button (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/icon_button (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/link_button (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/palette (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/primary (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/secondary (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/success (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/text_button (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning_button (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/interface_font (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/monospace_font (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/foot/foot.ini (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/fuzzel/fuzzel.ini (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/gtk-3.0/settings.ini (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/helix/config.toml (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/helix/languages.toml (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/helix/runtime (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/helix/themes/sigma.toml (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/hypr/hyprland.conf (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/keepassxc/keepassxc.ini (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/kitty/kitty.conf (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/luakit/userconf.lua (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/mako/config (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/mango/autostart.sh (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/mango/config.conf (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/neofetch/config.conf (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/niri/config.kdl (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/rio/config.toml (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/river/init (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/sc-im/scimrc (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/sigma/background (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/sigma/startup.sh (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/starship.toml (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/swaylock/config (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/waybar/config.jsonc (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/waybar/style.css (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/xfe/xferc (99%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.config/yambar/config.yml (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.gtkrc-2.0 (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.inputrc (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.mozilla/firefox/installs.ini (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.mozilla/firefox/profiles.ini (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.mozilla/firefox/sigma.default-release/user.js (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.newsboat/config (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.profile (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.tmux.conf (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.vim/after/syntax/c.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.vim/after/syntax/cpp.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.vimrc (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.zlogin (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/skel/.zshrc (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/LICENSE (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/README.md (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/colors/sigma.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.gitignore (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.vintrc.yaml (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/CHANGELOG.md (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/LICENCE (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/README.markdown (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/_config.yml (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree/ui_glue.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/doc/NERDTree.txt (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/bookmark.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/creator.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/event.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/flag_set.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/key_map.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_controller.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_item.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/nerdtree.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/notifier.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/opener.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/path.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_dir_node.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_file_node.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/ui.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/exec_menuitem.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/fs_menu.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/vcs.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/plugin/NERD_tree.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/screenshot.png (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/syntax/nerdtree.vim (100%) rename {src/apk => packages}/sigma-conf/rootfs/etc/vim/vimrc.local (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-audio (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-battery (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-change-kbdlayout (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-change-resolution (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-cipher (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-common (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-cpu (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-disk (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-dmenu (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-filemgr (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-launcher (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-lock-screen (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-memory (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-nwif (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-nwusage (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-power-menu (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-screenshot (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-startup (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-term (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-toggle-vkbd (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/bin/sigma-vkbd (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/sbin/setup-sigma (86%) create mode 100755 packages/sigma-conf/rootfs/usr/sbin/sigma-grub-setup rename {src/apk => packages}/sigma-conf/rootfs/usr/share/icons/default/index.theme (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/image.png (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.plymouth (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.script (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/share/sigma/ascii/sigma (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/share/sigma/ascii/sigma-linux-logo (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/share/sigma/backgrounds/wallpaper.png (100%) create mode 100644 packages/sigma-conf/rootfs/usr/share/sigma/grub/grub.conf rename src/grub_wallpaper.png => packages/sigma-conf/rootfs/usr/share/sigma/grub/wallpaper.png (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/share/sigma/lore.cipher (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/index.html (100%) rename {src/apk => packages}/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/sigma-logo-min.svg (100%) rename {src/apk => packages}/sigma-dwl/APKBUILD (100%) rename {src/apk => packages}/sigma-dwl/config.h (100%) rename {src/apk => packages}/sigma-firacode-nerd/APKBUILD (100%) rename {src/apk => packages}/sigma-firacode-nerd/firacode.tar.xz (100%) rename {src/apk => packages}/sigma-flat-remix-gtk/APKBUILD (100%) rename {src/apk => packages}/sigma-flat-remix-gtk/flat-remix-gtk-green-dark.tar.xz (100%) rename {src/apk => packages}/sigma-material-symbols/APKBUILD (100%) rename {src/apk => packages}/sigma-material-symbols/material-symbols.tar.xz (100%) rename {src/apk => packages}/sigma-sent/APKBUILD (100%) rename {src/apk => packages}/sigma-sent/sent/LICENSE (100%) rename {src/apk => packages}/sigma-sent/sent/Makefile (100%) rename {src/apk => packages}/sigma-sent/sent/README.md (100%) rename {src/apk => packages}/sigma-sent/sent/arg.h (100%) rename {src/apk => packages}/sigma-sent/sent/config.def.h (100%) rename {src/apk => packages}/sigma-sent/sent/config.h (100%) rename {src/apk => packages}/sigma-sent/sent/config.mk (100%) rename {src/apk => packages}/sigma-sent/sent/drw.c (100%) rename {src/apk => packages}/sigma-sent/sent/drw.h (100%) rename {src/apk => packages}/sigma-sent/sent/example (100%) rename {src/apk => packages}/sigma-sent/sent/nyan.png (100%) rename {src/apk => packages}/sigma-sent/sent/sent.1 (100%) rename {src/apk => packages}/sigma-sent/sent/sent.c (100%) rename {src/apk => packages}/sigma-sent/sent/transparent_test.ff (100%) rename {src/apk => packages}/sigma-sent/sent/util.c (100%) rename {src/apk => packages}/sigma-sent/sent/util.h (100%) rename {src/apk => packages}/sigma-st/APKBUILD (100%) rename {src/apk => packages}/sigma-vt323/APKBUILD (100%) rename {src/apk => packages}/sigma-vt323/VT323.tar.xz (100%) rename {src/apk => packages}/sigma-wvkbd/APKBUILD (100%) rename {src/apk => packages}/simplex-chat/APKBUILD (100%) create mode 100644 profiles/full.list rename src/pkglist.standard => profiles/minimal.list (69%) create mode 100644 profiles/standard.list delete mode 100755 sensible_clean.sh create mode 100755 sigma delete mode 120000 src/apk/sigma-vt323/src/VT323.tar.xz delete mode 120000 src/apk/sigma-vt323/src/vt323.tar.xz delete mode 100755 src/build_repo.sh delete mode 100755 src/config.sh delete mode 100755 src/make_iso.sh delete mode 100755 src/make_squashfs.sh delete mode 100644 src/pkglist.full delete mode 100644 src/pkglist.minimal delete mode 100755 src/setup_apks.sh delete mode 100755 start.sh delete mode 100755 start_uefi.sh create mode 100644 test/iso_test.rb create mode 100644 test/packages_test.rb create mode 100644 test/profile_test.rb create mode 100644 test/run.rb create mode 100644 test/selection_test.rb create mode 100644 test/stage_test.rb create mode 100644 test/test_helper.rb create mode 100644 test/ui_test.rb diff --git a/.dockerignore b/.dockerignore index 2a1a7ec..a1d76ef 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,10 +1,2 @@ -.git/ -cache/ -out/ -aports/ -extra/ -screenshots/ -Dockerfile -.dockerignore -docker-env.sh -profile/repo/ +# The checkout is mounted at run time, never copied: send no build context. +* diff --git a/.github/actions/container.yml b/.github/actions/container.yml deleted file mode 100644 index 3917e98..0000000 --- a/.github/actions/container.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: 'Container' -description: 'Runs the Dockerfile for Sigma Linux' -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/workflows/build-iso.yml b/.github/workflows/build-iso.yml index 9e80fa6..f96952b 100644 --- a/.github/workflows/build-iso.yml +++ b/.github/workflows/build-iso.yml @@ -3,17 +3,26 @@ name: Build ISO on: [workflow_dispatch] jobs: - container-job: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - name: Container Build Step - uses: ./ - id: container - - name: Upload Build Artifacts - uses: actions/upload-artifact@v4 - with: - name: sigma-linux - path: ${{ github.workspace }}/cache/sigma-linux.iso - compression-level: 0 + iso: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + # A kernel build needs ~20 GB; the runner image ships with less free. + - name: Free disk space + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL + df -h / + + # The same command as locally: sigma builds the container image and + # runs the whole pipeline inside it (privileged, for mounts and chroot). + - name: Build + run: ./sigma build + + - name: Upload ISO + uses: actions/upload-artifact@v4 + with: + name: sigma-linux + path: out/sigma-linux.iso + compression-level: 0 diff --git a/.gitignore b/.gitignore index b1ec6d5..a9ccdfe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ *.swp -aports/ cache*/ out/ -profile/repo -profile/apk/sigma-rootfs/rootfs.tar.gz -src/config.override.sh +config/local.yml diff --git a/Dockerfile b/Dockerfile index 71f26dd..405c29e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,32 @@ +# Sigma Linux build environment. +# +# Nothing is copied in: `./sigma` mounts the checkout at /app and runs itself +# inside, and everything worth keeping (downloads, ccache, signing key, the +# local repository) lives in ./cache on the host, so this image stays a pure +# toolbox that is cheap to rebuild. FROM alpine:edge -# Setup packages -RUN printf "http://dl-cdn.alpinelinux.org/alpine/edge/main\nhttp://dl-cdn.alpinelinux.org/alpine/edge/community\nhttp://dl-cdn.alpinelinux.org/alpine/edge/testing\n" > /etc/apk/repositories -RUN apk update -RUN apk add gcc openssl-dev elfutils-dev git gpg make musl-dev flex bison linux-headers perl mtools netpbm netpbm-extras bc diffutils findutils installkernel python3 sed xz gmp-dev mpc1-dev mpfr-dev # Kernel dependencies -RUN apk add doas alpine-sdk # Alpine SDK and build tools -RUN apk add squashfs-tools xorriso grub grub-efi grub-bios # ISO dependencies -RUN apk add shadow -RUN apk add ruby tar -RUN apk add umount -# RUN apk add clang llvm lld # For kernel built with clang -RUN apk add libx11-dev libxft-dev harfbuzz-dev fontconfig-dev freetype-dev gd-dev glib-dev # xorg/suckless dependencies -RUN apk add libxkbcommon-dev glib-dev wayland-dev pango-dev cairo-dev # wvkbd dependencies -RUN apk add ruby-dev ruby-full ruby-rdoc ruby-bundler +RUN printf '%s\n' \ + https://dl-cdn.alpinelinux.org/alpine/edge/main \ + https://dl-cdn.alpinelinux.org/alpine/edge/community \ + https://dl-cdn.alpinelinux.org/alpine/edge/testing \ + > /etc/apk/repositories -# Create build user -RUN adduser -D build -G abuild -RUN addgroup build wheel +# Package builds pull their own makedepends (abuild -r) from the APKBUILDs, +# so only the tools around them are listed here. +RUN apk add --no-cache \ + ruby \ + alpine-sdk ccache doas openssl \ + coreutils tar shadow umount \ + squashfs-tools xorriso mtools grub grub-bios grub-efi -# Setup abuild for build user -RUN echo "permit nopass keepenv :wheel" > /etc/doas.conf -USER build -RUN abuild-keygen -i -a -n -# Fix for building helix -RUN git config --global http.postBuffer 1048576000 -RUN git config --global http.version HTTP/1.1 -USER root +# The unprivileged user abuild runs as. Matching the host user's UID keeps +# files it writes into ./cache owned by that user on the host. +ARG BUILD_UID=1000 +RUN adduser -D -u "$BUILD_UID" -G abuild build \ + && addgroup build wheel \ + && printf '%s\n' 'permit nopass root as build' 'permit nopass :wheel' > /etc/doas.conf -# Fix for building helix -RUN git config --global http.postBuffer 1048576000 -RUN git config --global http.version HTTP/1.1 - -CMD ["/bin/sh", "./build.sh"] +ENV SIGMA_CONTAINER=1 +WORKDIR /app +CMD ["./sigma", "build"] diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..bfeeb40 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,312 @@ +# Build system rework + +Rework of the Sigma Linux build system: Bash → Ruby, monolithic script → staged +pipeline, plus a fix for GRUB losing its theme on kernel updates. + +Legend: `[x]` done and verified · `[~]` done, verification pending · `[ ]` to do + +--- + +## 1. Where we start + +What the current build does, end to end (`build.sh`, run as root inside Docker): + +| Step | Script | Notes | +|---|---|---| +| Stage APKs | `src/setup_apks.sh` | One hand-written block per package; special cases tar `rootfs/` and `sent/` into source tarballs. Skips a package if its staging dir exists, so edits to an APKBUILD are **never** picked up until `cache/` is wiped. | +| Build repo | `src/build_repo.sh` | Skips a package if *any* `.apk` with its name prefix is in the repo, so version bumps are ignored too. `abuild -kK` keeps every build tree: **15 GB** in `cache/apk` today. | +| Rootfs + squashfs | `src/make_squashfs.sh` | Installs the whole profile with `--allow-untrusted --no-cache` (no signature checks on *Alpine's* packages, and re-downloads ~1-2 GB on every build). Skips installing if `/etc/apk` exists, so a rebuilt kernel never lands in the image. Config files written via heredocs. | +| ISO | `src/make_iso.sh` | Writes `grub.cfg` from a heredoc; theme values (gfxmode, wallpaper, VT colours) live only here. | + +Problems worth fixing beyond the language: + +- **No way to do part of the work.** You can't build one package and stop, rebuild the image without + rebuilding packages, or re-run just the ISO step, short of commenting code out. +- **Staleness everywhere.** Every "skip if exists" check is a stale-cache trap (see the table). +- **Nothing persists between containers.** abuild downloads (the 150 MB kernel tarball), the apk + package cache and the abuild signing key all live inside the container and vanish with it. +- **Root-owned files** in `cache/` on the host (hence the `chmod 777`). +- **CI is broken.** The workflow does `uses: ./`, which needs an `action.yml` at the repo root (there is + none), and Docker actions aren't privileged, so `mount --rbind` / `chroot` couldn't work anyway. +- Junk committed by in-place abuild runs (`src/apk/sigma-vt323/src/*.tar.xz` symlinks to `/app/...`). + +### The GRUB bug (root cause found) + +Alpine's grub package ships this trigger: + +```sh +if grep -q '^disable_trigger=1' /etc/update-grub.conf 2>/dev/null; then exit 0; fi +exec /usr/sbin/update-grub +``` + +The build writes **`/etc/upgrade-grub.conf`**: wrong file name, so the guard never applied, and every +kernel update ran a stock `grub-mkconfig` over the hand-made config. (This machine also has +`disable_trigger=yes`, which wouldn't match either.) + +Just fixing the file name would stop the overwrite, but it would also stop GRUB from ever picking up kernel +changes (for example a second kernel installed next to `linux-sigma`). Running stock +`grub-mkconfig` isn't an option either, because its output can't boot with initrdbo: + +- initrdbo **requires** `rootfstype=` on the command line, or it drops to a troubleshooting shell; +- on encrypted installs `root=` must name the **LUKS container** (initrdbo unlocks it itself), while + `grub-mkconfig` emits the UUID of the filesystem *inside* the container; +- the theme (640x480 gfxterm, wallpaper, `vt.*` colours) isn't in `/etc/default/grub` at all. + +**Fix:** make `grub-mkconfig` produce the Sigma config instead of fighting it (Phase 5). + +--- + +## 2. Decisions + +### Ruby, stdlib only +No gems. The tool must run on the host (to dispatch into Docker), inside the Alpine container, and in CI, +and Ruby's stdlib already covers it all: `optparse`, `yaml`, `json`, `digest`, `open3`, `erb`, `fileutils`. +Nothing to `bundle install`, nothing to break. + +### A purpose-built CLI, not Rake / Make / Thor +I weighed the obvious alternatives: + +- **Rake / Make**: give a dependency graph for free, but their freshness model is file mtimes. Our inputs + are directories, remote repositories and config values, so the stamp logic has to be custom anyway. + "Run just this stage" fights them (they always walk prerequisites), task arguments read badly + (`rake 'pkg[linux-sigma]'`), and the output can't be made pleasant. +- **Thor**: a gem dependency in exchange for option parsing we get from `optparse`. + +The pipeline itself is small (four stages, linear), so a purpose-built runner is less code than adapting a +general one, and it lets the output be designed rather than inherited. + +### Stages, steps and selections +The build is a linear pipeline of **stages**, each made of named **steps**: + +``` +packages ──▶ rootfs ──▶ squashfs ──▶ iso +``` + +| Stage | Produces | Steps | +|---|---|---| +| `packages` | signed local repo `cache/repo/` | one per local package (built on demand, in dependency order) | +| `rootfs` | `cache/rootfs/`, `cache/boot/` | `prepare`, `install`, `configure` | +| `squashfs` | `cache/rootfs.squashfs` | `compress` | +| `iso` | `out/sigma-linux.iso` | `stage`, `grub`, `image` | + +Selection syntax (one grammar for everything): + +``` +sigma build # whole pipeline, skipping stages that are up to date +sigma build rootfs # just that stage (named ⇒ always runs) +sigma build rootfs.. # rootfs and everything after it +sigma build ..squashfs # everything up to squashfs +sigma build packages:linux-sigma # a subset of a stage: one package… +sigma build rootfs:configure # …or one step +sigma pkg linux-sigma initrdbo # shortcut for packages: +``` + +**Freshness model.** Each stage has a fingerprint (hash of its inputs: source files, relevant config, and +the fingerprint of the stage before it) stored in `cache/state/`. A bare `sigma build` skips a stage whose +fingerprint matches and whose outputs exist. Naming a stage means "do it" and it always runs. Packages +are tracked individually: a package rebuilds when its directory content changes or its `.apk` files are +missing. That fixes the "edited the APKBUILD, nothing happened" trap. Remote Alpine updates are not +fingerprinted; `--force` or naming the stage covers that. + +**Local packages are built on demand.** The packages stage builds the local packages the selected profile +needs (plus their local dependencies), not everything under `packages/`. Unused recipes (`sigma-dwl`, +`sigma-st`, `simplex-chat`) stop costing build time but stay buildable by name. + +### Configuration: YAML, layered +`config/sigma.yml` (committed defaults) ← `config/local.yml` (git-ignored, replaces +`config.override.sh`) ← `--set key=value` on the command line. `sigma config` prints the merged result. + +### Docker by default, native when it makes sense +`./sigma …` on the host builds the image if needed and re-runs itself inside the container with the same +arguments. Inside the container (or with `--native`) it runs directly. `sigma shell` replaces +`docker-env.sh`. Building a single package natively on a Sigma host works too, since abuild doesn't need root. + +### Layout + +``` +sigma CLI entry point +lib/sigma/ the build system +config/sigma.yml build configuration +config/repositories Alpine repositories for the image (was src/repositories) +packages// local APKBUILDs (was src/apk/) +profiles/*.list package profiles with @include (were src/pkglist.*) +image/overlay/ files copied verbatim onto the rootfs (were heredocs) +image/grub.cfg.erb live ISO boot menu template +cache/ intermediate state (safe to delete) +out/ final artifacts (the ISO) +``` + +--- + +## 3. Phases + +### Phase 1: Core toolkit ✅ +The Ruby foundation everything else stands on: no build logic yet. + +- [x] `sigma` entry point and `lib/sigma.rb` loader +- [x] `UI`: palette (Sigma teal, truecolor → 256 → none), `NO_COLOR`, TTY detection, headers, + step lines with spinner + elapsed time + live last-output-line, failure box with log tail, summary +- [x] `Shell`: run commands with output captured to per-step log files, live or verbose streaming, + `as_builder` (drop to the abuild user when root), `chroot`, clear errors +- [x] `Config`: YAML defaults + local + `--set` overrides, deep merge, typed accessors, derived paths +- [x] `CLI`: subcommands (`build`, `pkg`, `status`, `clean`, `config`, `shell`, `run`), help text +- [x] **Check:** `sigma --help`, `sigma config`, `sigma config --set squashfs.compression=zstd`, and a UI + demo exercising success / failure / non-TTY output + +### Phase 2: Layout and data ✅ +Move sources into their new homes and turn heredocs into data. Content stays identical. + +- [x] `src/apk/` → `packages/` (git mv), drop committed abuild junk (`sigma-vt323/src/`) +- [x] Profiles: `minimal` ⊂ `standard` ⊂ `full` rewritten with `@include` and `-pkg` removals +- [x] **Check:** each new profile resolves to *exactly* the same package set as the old list + (minimal 155, standard 237, full 243 packages: identical) +- [x] Heredoc files → `image/overlay/` (`issue`, `motd`, `fstab`, `network/interfaces`, `doas.conf`, + `conf.d/zram-init`, `modules`); services, user and skel dirs → `config/sigma.yml` +- [x] **Check:** overlay files byte-identical to what the heredocs produced (shell-rendered and diffed) +- [x] The GRUB wallpaper moved into `sigma-conf` (`/usr/share/sigma/grub/`), ready for Phase 5 + +### Phase 3: The pipeline ✅ +Port the four stages onto the toolkit, with the selection grammar and freshness model. + +- [x] `Pipeline`: stage registry, selection parsing (`a`, `a..`, `..b`, `a..b`, `a:x,y`), fingerprints, + stamps in `cache/state/`, skip-if-fresh, end-of-run summary +- [x] `APKBUILD` reader (sources the file in `sh`, never `eval`s it in Ruby) and local package graph + (profile → needed packages via names / subpackages / provides, topological order) +- [x] Stage `packages`: generic staging (a `foo.tar.gz` source with a sibling `foo/` dir is tarred + reproducibly; no per-package code), `abuild checksum`, build, prune superseded `.apk`s, re-index, + remove build trees on success +- [~] Stage `rootfs`: fresh root each run, signed install (Alpine keys + our key, **no + `--allow-untrusted`**), persistent apk cache, overlay (before install for triggers, after to win), + services, user, merge-usr; mounts always torn down, even on failure or Ctrl-C +- [~] Stage `squashfs`, stage `iso` (boot entry per installed kernel, rendered from template) +- [x] `status` and `clean` commands +- [x] **Check:** selection grammar unit-tested; APKBUILD reader and package graph tested against the real + `packages/`; dry run (`--plan`) prints the right stages / steps for each selection. + `ruby test/run.rb`: 23 tests, including freshness (inputs / upstream token / partial runs) and the + ISO menu rendering byte-identical to the old `make_iso.sh` output. Stages marked `[~]` run for real in + Phase 4 (packages) and Phase 6 (rootfs, squashfs, iso). + +### Phase 4: Container and caching ✅ +- [x] Dockerfile: one documented package layer, `ccache`, build user with host UID, no keygen baked + into the image, `SIGMA_CONTAINER=1`, minimal build context +- [x] Persistent caches under `cache/`: abuild sources (`downloads/sources/`), apk packages (`downloads/apk/`), ccache, + abuild signing keys (`keys/`) +- [x] Docker dispatch from the host (`build` / `shell` / everything), cross-arch binfmt setup kept +- [x] Host-owned outputs (no more root-owned `cache/` / `out/` on the host) +- [~] CI workflow rewritten to run the same `./sigma build` in a privileged container (can't be run from + here; mirrors the local invocation exactly, plus freeing runner disk space) +- [x] **Check:** image builds; `sigma shell` works; `sigma pkg initrdbo` builds a package end to end + in the container; a second run is a no-op; editing the APKBUILD triggers a rebuild. + Also verified: a `pkgrel` bump builds `-r1` and prunes `-r0` from the signed index (and back); + `sigma-conf` / `sigma-sent` sources packed reproducibly by the generic rule; makedepends served from + the shared apk cache; outputs, logs, stamps and the (0600) key owned by the host user. + +### Phase 5: GRUB that survives kernel updates +- [x] `sigma-conf` ships the theme as data: `/usr/share/sigma/grub/grub.conf` (shell-sourceable + `GRUB_*` vars: gfxmode, gfxterm, background, font, VT colours, timeout, top-level kernel) and + `wallpaper.png`; the *same file* drives the ISO menu, so the theme lives in one place +- [x] `sigma-grub-setup` (installed system): writes `/etc/default/grub` that sources the theme and pins + `root=` / `rootfstype=` for initrdbo (LUKS-aware), installs the wallpaper on `/boot`, removes the + legacy guard files, runs `update-grub`. Re-runnable, so it also migrates existing installs. +- [x] `setup-sigma` uses it instead of copying and `sed`-patching the ISO's `grub.cfg`; it now + bind-mounts the live `/dev` into the chroot (grub-mkconfig must resolve `/dev/mapper/…` on LUKS) + and mounts `/sys` +- [x] Build: trigger correctly disabled *during* the image build only, removed before shipping +- [x] Menu layout matches the old one: the Sigma kernel is the plain "Sigma Linux" entry + (`GRUB_TOP_LEVEL`), other kernels go under "Advanced options for Sigma Linux" +- [~] **Check:** container + loop-device test, three scenarios (plain, LUKS, migrating an old-style install): + 48/48 checks passed, including apk's real grub trigger regenerating the menu on a kernel install + with theme, `root=UUID=`, `rootfstype` and VT colours intact, overrides + kept across re-runs, and grub's post-upgrade leaving the cmdline alone. After the last tweak (submenu + layout) the setup checks passed again, but the run was cut off by the crash below, before the + kernel-update part. **Remaining verification moves into the Phase 6 VM test; no more loop / dm + devices on the host kernel.** + +### Phase 6: Cut-over and end-to-end +- [x] Remove the Bash build (`build.sh`, `docker-env.sh`, `src/*.sh`, `start*.sh`, `sensible_clean.sh`); every + feature checked off against the Ruby port first (one deliberate difference: empty `/dev`, `/proc`, + `/boot` mountpoints stay in the image instead of being deleted; initrdbo and `setup-sigma` `mkdir -p` + them anyway) +- [x] README: build documentation +- [x] `sigma run --disk FILE [--no-iso]` to try the installer in QEMU (replaces `start*.sh`) +- [ ] **Check:** the runbook below, run by hand, one step at a time + +#### Runbook + +You run these; I don't. Nothing heavy runs in parallel. **Stop at the first step that doesn't look as +described** and send me the error box (plus `cache/logs/latest//.log` if it names one). +Steps 1-4 are light; 5 is the long one. + +0. **Protect the work.** Commit and push (see the chat), and ideally check the SSD's health: + `doas smartctl -a /dev/nvme0` (look at *Media and Data Integrity Errors*, *Percentage Used*, *Available Spare*). + +1. **No Docker yet.** `ruby test/run.rb` → `23 runs, … 0 failures`. Then `./sigma build --plan` → four + stages; `packages` says 11 packages, 8 to build (initrdbo, biblegen and sigma-sent were built before the + crash). `./sigma status` → a table, nothing broken. + +2. **Container, and leftovers from the crash.** `doas rc-service docker start`, then + `docker run --rm -v "$PWD/cache:/c" alpine:edge rm -rf /c/grubtest /c/build/sigma/linux-sigma` + (~7 GB of root-owned files). `./sigma shell` should drop you into `/app` as root; `exit`. + +3. **Small packages.** `./sigma pkg initrdbo sigma-conf biblegen` → `sigma-conf` builds (✓, seconds; it + has the new GRUB files); `initrdbo` and `biblegen` show ○ up to date. That proves the per-package + freshness survived the crash. + +4. **The rest except the kernel.** `./sigma pkg rdbo-archives sigma-firacode-nerd sigma-flat-remix-gtk + sigma-material-symbols sigma-sent sigma-vt323 sigma-wvkbd` → seven ✓. + +5. **The kernel, alone.** Nothing else running on the machine. For the first time, go easier than the + default: `./sigma pkg linux-sigma --set jobs=6`. Expect 30-90 minutes; it peaks around 15-20 GB of disk + (`df -h /` now and then is wise) and cleans its build tree up when it succeeds. If the desktop gets + sluggish, Ctrl-C is safe: nothing is mounted during package builds, and the next run starts the package over. + +6. **Root filesystem.** `./sigma build rootfs` → `prepare`, `install 237 packages` (downloads ~1-2 GB the + first time; cached after), `configure`. Then: `./sigma status` → packages and rootfs up to date. + +7. **Image.** `./sigma build squashfs..` → squashfs (several minutes with xz) and the ISO in `out/`. + +8. **Boot it** (needs QEMU on the host: `doas apk add qemu-system-x86_64 qemu-img qemu-ui-gtk ovmf`). + `./sigma run` (BIOS) and `./sigma run --uefi`: the Sigma menu (wallpaper, 640x480), boots to the live + desktop. Log in as `user` / `pass`. + +9. **Install test, in the VM only.** `./sigma run --uefi --disk cache/vm.qcow2` → in the live system: + `doas setup-sigma`, disk `/dev/nvme0n1` (the VM disk is NVMe, like your machine), full-disk encryption + **y**, random fill **n**, `YES`. Power off, then + `./sigma run --uefi --disk cache/vm.qcow2 --no-iso`: + - the menu shows the wallpaper and a plain **Sigma Linux** entry; the LUKS passphrase prompt appears; + the desktop comes up; + - `cat /proc/cmdline` has `root=UUID=…`, `rootfstype=ext4` and the `vt.default_*` colours; + - **the kernel-update test:** `doas apk add linux-lts`; the output shows the grub trigger running. + `doas reboot`: same themed menu, *Advanced options for Sigma Linux* now lists the lts kernel, and + the default entry still boots. + - Optional: repeat with encryption **n** on a fresh disk (`rm cache/vm.qcow2`). + +10. **Incremental behaviour.** `./sigma build` → "Everything is up to date". `./sigma build iso` → only the + iso stage runs. + +11. **Optional, last: migrate your own machine's GRUB.** This touches your real bootloader, so dry-run first: + 1. `./sigma pkg sigma-conf`, then trust the key once: `doas cp cache/keys/*.pub /etc/apk/keys/`. + 2. `doas apk add --repository "$PWD/cache/repo/sigma" sigma-conf` + 3. `doas sigma-grub-setup --no-update`, then read `/etc/default/grub`: `GRUB_DEVICE_UUID` must equal the + `root=UUID=` in your `/proc/cmdline`. + 4. `doas grub-mkconfig -o /tmp/grub.cfg.new` and compare with `/boot/grub/grub.cfg`: same root UUID, + `rootfstype=ext4`, the colours, the wallpaper. + 5. Only then `doas update-grub` (it keeps the old file as `grub.cfg.backup`). + +--- + +## 4. Risks and notes + +- **Incident during development: the build host became unresponsive** (I/O errors; a reboot recovered it + cleanly) while a kernel build using every core ran *alongside* a loop-device test. The cause couldn't be + proven; most likely the storage stack stalled under the combined load. Safeguards since: + - `jobs: auto` = CPUs − 1, capped at one job per 1.5 GiB of RAM; + - compiles and compression run under `nice`; + - anything that partitions, formats or opens LUKS runs inside a QEMU VM, never against the host kernel; + - one heavy job at a time. + +- **Disk space.** A kernel build peaks around 15-20 GB; the rootfs, squashfs and ISO add ~10 GB. `cache/grubtest/` and the interrupted + `cache/build/sigma/linux-sigma/` (~7 GB, root-owned) still need removing through a container. +- **CI can't be exercised locally**; the workflow is written to mirror the local container invocation. +- Kernel updates on an installed system: the initramfs name is per-flavor (`initramfs-sigma`), so the order + in which the initrdbo and grub triggers run doesn't matter for an existing kernel. diff --git a/README.md b/README.md index 6305e2c..11bf0a2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,67 @@ # Sigma Linux ## Elite operating system +# Building + +Everything goes through one command, `./sigma`. It needs Ruby (standard library only) and Docker: build commands run themselves inside the build container, so the host needs nothing else. + +```sh +./sigma build # build the ISO: out/sigma-linux.iso +./sigma run # boot it in QEMU (--uefi for UEFI firmware) +``` + +The build is a pipeline of four stages, and a bare `./sigma build` skips the stages that are already up to date: + +``` +packages ──▶ rootfs ──▶ squashfs ──▶ iso +``` + +| Stage | What it does | Output | +|---|---|---| +| `packages` | builds the local packages (`packages/`) the profile needs into a signed repository | `cache/repo/` | +| `rootfs` | installs the profile into a fresh root filesystem and configures it | `cache/rootfs/` | +| `squashfs` | compresses the root filesystem | `cache/rootfs.squashfs` | +| `iso` | assembles a bootable image with a menu entry per kernel | `out/sigma-linux.iso` | + +Naming a stage (or part of one) runs just that: + +```sh +./sigma pkg linux-sigma # build one package and stop +./sigma build rootfs.. # rootfs and everything after it +./sigma build ..squashfs # everything up to the squashfs +./sigma build rootfs:configure # one step of a stage +./sigma build --plan # show what would run +./sigma status # what is built, what is stale, and why +./sigma clean rootfs.. # delete outputs (downloads and built packages stay) +./sigma shell # a shell in the build container +``` + +Packages rebuild when anything in their `packages//` directory changes. To install a freshly built package on the machine you're on (a new kernel, say), `./sigma pkg` prints the `apk add` command. + +## Configuration + +`config/sigma.yml` holds the defaults: name, profile, users, services, compression and so on. Put personal overrides in `config/local.yml` (git-ignored, same shape), or pass them for one run with `--set key=value`. `./sigma config` prints the result. + +| Path | Contents | +|---|---| +| `packages/` | local APKBUILDs | +| `profiles/` | package lists: `minimal` ⊂ `standard` ⊂ `full` (`@include` composes them) | +| `image/overlay/` | files copied verbatim onto the root filesystem | +| `image/grub.cfg.erb` | the live ISO's boot menu | +| `cache/` | intermediate state, safe to delete; logs in `cache/logs/latest/` | + +The boot menu's look (resolution, wallpaper, console colours) lives in `packages/sigma-conf/rootfs/usr/share/sigma/grub/grub.conf`, shared by the ISO and installed systems. On an installed system, `sigma-grub-setup` hooks it into `/etc/default/grub`, so the menu keeps its theme when kernel updates regenerate it. Installs made before this fix can run it once to migrate: `doas sigma-grub-setup`. + +## Resources + +A full build needs about 35 GB of free disk space (the kernel alone takes 15-20 GB while it builds). `jobs: auto` uses one core less than the machine has, and at most one job per 1.5 GiB of RAM, so the system stays usable. Run the tests with `ruby test/run.rb`. + # License This project is licensed under the GNU AGPL-3.0 (no later versions allowed) All the files in this repository follow the license in the file `LICENSE` present in this directory, unless explicitly said otherwise (e.g through a CREDITS or another LICENSE file) -Note that packages under `src/apk` will have their license names noted at the `APKBUILD` file +Note that packages under `packages/` will have their license names noted at the `APKBUILD` file Packages installed from the package repository will have their own licenses as well diff --git a/build.sh b/build.sh deleted file mode 100755 index 405e507..0000000 --- a/build.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh - -set -e - -if [ "$(id -u)" != "0" ]; then - echo "[!] Run as root" - exit 1 -fi - -echo "[*] Sigma Linux Builder" - -echo "[*] Running config..." -. ./src/config.sh - -mkdir -p "$CACHE_DIR" -chmod 777 "$CACHE_DIR" # Allow read-write for build user - -# Setup APKs and build local repository -echo "[*] Setting up APKs..." -doas -u "$BUILD_USER" -- ./src/setup_apks.sh - -echo "[*] Building local repository..." -doas -u "$BUILD_USER" -- ./src/build_repo.sh - -# Skip making squashfs if rootfs.squashfs is found -if [ ! -e "$SQUASHFS_PATH" ]; then - # Make squashfs - echo "[*] Making squashfs..." - ./src/make_squashfs.sh -else - echo "[*] Skipped making squashfs, file '$SQUASHFS_PATH' exists" -fi - -# Build ISO -echo "[*] Making ISO..." -./src/make_iso.sh - -echo "[*] Build finished successfully" diff --git a/src/repositories b/config/repositories similarity index 100% rename from src/repositories rename to config/repositories diff --git a/config/sigma.yml b/config/sigma.yml new file mode 100644 index 0000000..20c4fdc --- /dev/null +++ b/config/sigma.yml @@ -0,0 +1,57 @@ +# Sigma Linux build configuration. +# +# Personal overrides go in config/local.yml (git-ignored, same shape as this +# file), and one-off ones on the command line: sigma build --set profile=full +# `sigma config` prints the merged result. + +name: Sigma Linux # shown in the boot menu +id: sigma # hostname and image file names +arch: x86_64 +profile: standard # profiles/.list + +jobs: auto # parallel build jobs; auto = CPUs - 1, capped at 1 per 1.5 GiB RAM + +# Where intermediate state (cache) and finished artifacts (out) are written, +# relative to the repository. Point cache somewhere roomy: a kernel build +# alone needs 15-20 GB. +paths: + cache: cache + out: out + +packages: + builder: build # unprivileged user abuild runs as when we are root + ccache: true # speeds up rebuilds, the kernel's especially + keep_build_trees: false # keep abuild's src/ and pkg/ dirs for debugging + +rootfs: + repositories: config/repositories + overlay: image/overlay # copied verbatim onto the root filesystem + + user: + name: user + password: pass + shell: /bin/bash + groups: [wheel, audio, input, video, seat, dialout, netdev] + + # Created in /etc/skel, so every new user starts with them. + skel_dirs: [Downloads, Documents, Pictures, Videos, Music] + + # OpenRC services, by runlevel. + # udev* set up /dev and device ownership (e.g. /dev/dri/cardN), which is + # what lets users reach the GPU and input devices without root + # local runs start scripts + # modloop is deliberately absent: Sigma Linux doesn't need it + services: + sysinit: [devfs, dmesg, mdev, hwdrivers, udev, udev-trigger, udev-settle] + boot: [hwclock, modules, sysctl, bootmisc, syslog, hostname, zram-init] + default: [udev-postmount, networking, earlyoom, polkit, networkmanager, dbus, seatd, bluetooth, local] + +squashfs: + compression: xz # xz is smallest; zstd builds much faster for testing + args: [] # extra mksquashfs arguments + +iso: + volume_id: sigma-linux-cdrom + +docker: + image: sigma-linux diff --git a/docker-env.sh b/docker-env.sh deleted file mode 100755 index c7dbd08..0000000 --- a/docker-env.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -set -e - -echo "[*] Loading config..." -. src/config.sh >& /dev/null - -set_docker_arch() { - local osarch="$1" - local dockerarch="" - case "$osarch" in - x86_64) dockerarch="linux/amd64";; - aarch64) dockerarch="linux/aarch64";; - *) echo "[!] Failed to start docker for cross compilation target: unknown OS arch"; exit 1;; - esac - - export DOCKER_DEFAULT_PLATFORM="$dockerarch" - echo "DOCKER_DEFAULT_PLATFORM=$DOCKER_DEFAULT_PLATFORM" -} - -if [ "$DISTRO_TARGET_ARCH" != "$(uname -m)" ]; then - if [ ! -e "/proc/sys/fs/binfmt_misc/qemu-$DISTRO_TARGET_ARCH" ]; then - # Install binfmt - echo "[*] Setting up binfmt for cross compilation..." - doas modprobe binfmt_misc - doas mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc/ - docker run --privileged --rm tonistiigi/binfmt --install all - fi - - echo "[*] Setting up cross target docker..." - set_docker_arch "$DISTRO_TARGET_ARCH" -fi - -echo "[*] Building Dockerfile..." -docker build -t sigma-linux . - -echo "[*] Starting Docker environment..." -docker run --privileged -v "$(pwd):/app" --workdir /app -it sigma-linux sh diff --git a/image/grub.cfg.erb b/image/grub.cfg.erb new file mode 100644 index 0000000..6b37c4d --- /dev/null +++ b/image/grub.cfg.erb @@ -0,0 +1,21 @@ +<%# Live ISO boot menu. Theme values come from sigma-conf's grub.conf, which installed systems share. -%> +set timeout=<%= theme.fetch("GRUB_TIMEOUT", "5") %> + +insmod all_video +loadfont /boot/grub/fonts/unicode.pf2 + +insmod gfxterm +set gfxmode=<%= theme.fetch("GRUB_GFXMODE", "auto") %> +terminal_output gfxterm + +insmod png +background_image /boot/grub/wallpaper.png +<% kernels.each do |kernel| -%> + +menuentry "<%= kernel.title %>" { + echo "Loading vmlinuz..." + linux /boot/<%= kernel.vmlinuz %> root=LABEL=<%= volume_id %> rootfstype=iso9660 rd.live=/rootfs.squashfs <%= cmdline %> + echo "Loading initrd..." + initrd /boot/<%= kernel.initramfs %> +} +<% end -%> diff --git a/image/overlay/etc/conf.d/zram-init b/image/overlay/etc/conf.d/zram-init new file mode 100644 index 0000000..ee12fd5 --- /dev/null +++ b/image/overlay/etc/conf.d/zram-init @@ -0,0 +1,10 @@ +load_on_start="yes" +unload_on_stop="yes" +num_devices="1" + +type0="swap" +flag0= +size0=`LC_ALL=C free -m | awk '/^Mem:/{print int($2/2)}'` # 50% of memory reserved for zram +maxs0=1 +algo0=zstd +labl0=zram_swap diff --git a/image/overlay/etc/doas.conf b/image/overlay/etc/doas.conf new file mode 100644 index 0000000..bb2ee27 --- /dev/null +++ b/image/overlay/etc/doas.conf @@ -0,0 +1,2 @@ +permit persist :wheel +permit nopass root diff --git a/image/overlay/etc/fstab b/image/overlay/etc/fstab new file mode 100644 index 0000000..e69de29 diff --git a/image/overlay/etc/issue b/image/overlay/etc/issue new file mode 100644 index 0000000..1ded3c5 --- /dev/null +++ b/image/overlay/etc/issue @@ -0,0 +1,2 @@ +Welcome to Sigma Linux (made by rdbo) +Kernel \r on an \m (\l) diff --git a/image/overlay/etc/modules b/image/overlay/etc/modules new file mode 100644 index 0000000..786156a --- /dev/null +++ b/image/overlay/etc/modules @@ -0,0 +1,14 @@ +ahci libahci +ata_generic libata +ata_piix +nvme_core nvme +usbcore +xhci_pci ehci_pci uhci_hcd +usb_common usb_storage uas + +scsi_mod sd_mod sr_mod + +hid hid_generic usbhid + +af_packet +ipv6 diff --git a/image/overlay/etc/motd b/image/overlay/etc/motd new file mode 100644 index 0000000..5f9d681 --- /dev/null +++ b/image/overlay/etc/motd @@ -0,0 +1,8 @@ +Welcome to Sigma Linux! + +To install the system, run the following command: setup-sigma + +For more information about the distribution, see: + - https://github.com/rdbo/sigma-linux + - https://wiki.alpinelinux.org + diff --git a/image/overlay/etc/network/interfaces b/image/overlay/etc/network/interfaces new file mode 100644 index 0000000..f1bd92e --- /dev/null +++ b/image/overlay/etc/network/interfaces @@ -0,0 +1,2 @@ +auto lo +iface lo inet loopback diff --git a/lib/sigma.rb b/lib/sigma.rb new file mode 100644 index 0000000..d86ba7f --- /dev/null +++ b/lib/sigma.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# Sigma Linux build system. See `./sigma --help` and PLAN.md. +module Sigma + ROOT = File.expand_path("..", __dir__) +end + +require_relative "sigma/errors" +require_relative "sigma/ui" +require_relative "sigma/shell" +require_relative "sigma/config" +require_relative "sigma/profile" +require_relative "sigma/fingerprint" +require_relative "sigma/state" +require_relative "sigma/mounts" +require_relative "sigma/signing" +require_relative "sigma/ownership" +require_relative "sigma/apkbuild" +require_relative "sigma/local_packages" +require_relative "sigma/context" +require_relative "sigma/selection" +require_relative "sigma/stage" +require_relative "sigma/stages/packages" +require_relative "sigma/stages/rootfs" +require_relative "sigma/stages/squashfs" +require_relative "sigma/stages/iso" +require_relative "sigma/pipeline" +require_relative "sigma/docker" +require_relative "sigma/qemu" +require_relative "sigma/cli" diff --git a/lib/sigma/apkbuild.rb b/lib/sigma/apkbuild.rb new file mode 100644 index 0000000..741dc95 --- /dev/null +++ b/lib/sigma/apkbuild.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Sigma + # Metadata of one APKBUILD. Read by sourcing the file in sh, as abuild does, + # so variables built from other variables come out right. APKBUILDs are + # shell scripts; we never interpret them ourselves. + class Apkbuild + FIELDS = %w[pkgname pkgver pkgrel arch depends makedepends checkdepends subpackages provides source].freeze + + attr_reader :dir, :name, :pkgver, :pkgrel, :arch, :depends, :makedepends, :checkdepends, + :subpackages, :provides, :sources + + def self.read(dir, shell:, carch:) + script = <<~SH + . ./APKBUILD || exit 1 + #{FIELDS.map { |f| %(printf '%s\\0' "$#{f}") }.join("\n")} + SH + env = { "CARCH" => carch, "CBUILD_ARCH" => carch, "CTARGET_ARCH" => carch, + "startdir" => dir, "srcdir" => File.join(dir, "src"), "pkgdir" => File.join(dir, "pkg") } + values = shell.capture("sh", "-c", script, env: env, chdir: dir).split("\0", -1) + new(dir, FIELDS.zip(values).to_h) + rescue CommandFailed => e + raise Error, "#{File.basename(dir)}/APKBUILD: could not be read (#{e.tail.last || e.message})" + end + + def initialize(dir, fields) + @dir = dir + @name = fields["pkgname"] + @pkgver = fields["pkgver"] + @pkgrel = fields["pkgrel"] + @arch = words(fields["arch"]) + @depends = names(fields["depends"]) + @makedepends = names(fields["makedepends"]) + @checkdepends = names(fields["checkdepends"]) + @subpackages = words(fields["subpackages"]).map { |s| s.split(":").first } + @provides = words(fields["provides"]).map { |p| p.split(/[<>=~]/).first } + @sources = words(fields["source"]) + raise Error, "#{File.basename(dir)}/APKBUILD: pkgname/pkgver/pkgrel missing" if [@name, @pkgver, @pkgrel].any?(&:empty?) + end + + def version = "#{pkgver}-r#{pkgrel}" + + # Every package name this APKBUILD produces. + def outputs = [name, *subpackages] + + # Names this APKBUILD answers to when something depends on it. + def aliases = [*outputs, *provides] + + # Local files abuild expects next to the APKBUILD (not URLs). + def local_sources + sources.reject { |s| s.include?("://") }.map { |s| s.split("::").last } + end + + private + + def words(value) = value.to_s.split + + # Dependency names without version constraints; conflicts (!foo) dropped. + def names(value) + words(value).reject { |d| d.start_with?("!") }.map { |d| d.split(/[<>=~]/).first } + end + end +end diff --git a/lib/sigma/cli.rb b/lib/sigma/cli.rb new file mode 100644 index 0000000..e0cda1f --- /dev/null +++ b/lib/sigma/cli.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +require "optparse" + +module Sigma + # Command-line front end: parses arguments, decides whether to hop into the + # build container, and renders errors. + class CLI + # Commands that touch the build environment run inside the container + # unless we are already there or --native is given. + CONTAINERIZED = %w[build pkg clean shell].freeze + + COMMANDS = { + "build" => "Run the pipeline, skipping stages that are up to date", + "pkg" => "Build local packages (same as build packages:NAME,…)", + "status" => "Show what is built and what is stale", + "clean" => "Delete stage outputs (every stage by default)", + "shell" => "Open a shell in the build container", + "run" => "Boot the ISO (or an installed disk) in QEMU", + "config" => "Print the effective configuration" + }.freeze + + def self.start(argv) = new.start(argv) + + def initialize + @options = { verbose: false, native: false, overrides: [], color: nil, force: false, plan: false } + end + + def start(argv) + @original_argv = argv.dup + argv = argv.dup + command = argv.first&.start_with?("-") ? nil : argv.shift + parser = option_parser(command) + args = parser.parse(argv) + @ui = UI.new(verbose: @options[:verbose], color: @options[:color]) + + return help(parser) if command.nil? || command == "help" + raise Error.new("unknown command `#{command}`", hint: "See `sigma --help`.") unless COMMANDS.key?(command) + + @config = Config.load(ROOT, overrides: @options[:overrides]) + return Docker.new(@config, @ui).dispatch(@original_argv) if containerize?(command) + + public_send("cmd_#{command}", args) + 0 + rescue OptionParser::ParseError => e + render_error(Error.new(e.message, hint: "See `sigma #{command} --help`.")) + rescue CommandFailed, Error => e + render_error(e) + rescue Interrupt + ui = @ui || UI.new + ui.blank + ui.say " #{ui.paint("Interrupted.", :warn)}" + 130 + end + + # -- commands --------------------------------------------------------------- + + def cmd_build(selections) + Pipeline.new(context).build(selections, force: @options[:force], plan: @options[:plan]) + end + + def cmd_pkg(names) + raise Error.new("which packages?", hint: "e.g. `sigma pkg linux-sigma`") if names.empty? + + cmd_build(["packages:#{names.join(",")}"]) + end + + def cmd_status(_args) + Pipeline.new(context).status + end + + def cmd_clean(selections) + Pipeline.new(context).clean(selections, everything: @options[:everything]) + end + + def cmd_shell(_args) + # Only reached with --native or inside the container already. + exec(ENV.fetch("SHELL", "/bin/sh")) + end + + def cmd_run(extra) + Qemu.new(@config, @ui).boot(uefi: @options[:uefi], disk: @options[:disk], iso: @options.fetch(:iso, true), extra: extra) + end + + def cmd_config(keys) + if keys.empty? + @ui.banner("Configuration", @config.sources) + @ui.table(@config.flatten.map { |k, v| [k, format_value(v)] }) + @ui.blank + else + keys.each { |key| @ui.say format_value(@config.fetch(key)) } + end + end + + private + + def context + @context ||= Context.new(@config, @ui) + end + + # --plan only reads files, so it never needs the container. + def containerize?(command) + CONTAINERIZED.include?(command) && !@options[:native] && !@options[:plan] && !Docker.inside? + end + + def format_value(value) + case value + when Array then value.join(" ") + when nil then @ui.paint("(unset)", :muted) + else value.to_s + end + end + + def option_parser(command) + OptionParser.new do |o| + o.banner = command ? "Usage: sigma #{command} #{usage_args(command)}" : "Usage: sigma [options]" + o.on("-v", "--verbose", "Stream command output instead of a live summary") { @options[:verbose] = true } + o.on("-s", "--set KEY=VALUE", "Override a config value (repeatable)") { |v| @options[:overrides] << v } + o.on("--native", "Run here instead of in the build container") { @options[:native] = true } + o.on("--no-color", "Disable colour") { @options[:color] = false } + case command + when "build", "pkg" + o.on("-f", "--force", "Rebuild even what is up to date") { @options[:force] = true } + o.on("--plan", "Show what would run, and stop") { @options[:plan] = true } + when "clean" + o.on("--everything", "Also delete downloads, ccache, signing keys and out/") { @options[:everything] = true } + when "run" + o.on("--uefi", "Boot with UEFI firmware (OVMF) instead of BIOS") { @options[:uefi] = true } + o.on("--disk FILE", "Attach a virtual disk (qcow2, created if missing)") { |f| @options[:disk] = File.expand_path(f) } + o.on("--no-iso", "Boot only the disk (an installed system; needs --uefi)") { @options[:iso] = false } + end + o.on("-h", "--help", "Show this help") do + help(o, command) + exit 0 + end + end + end + + def usage_args(command) + { + "build" => "[SELECTION…] [options]", "pkg" => "NAME… [options]", "clean" => "[SELECTION…] [options]", + "run" => "[options] [-- QEMU ARGS]", "config" => "[KEY…]" + }.fetch(command, "[options]") + end + + def help(parser, command = nil) + ui = @ui || UI.new + if command + ui.say parser.help + ui.blank + selection_help(ui) if %w[build clean].include?(command) + return 0 + end + + ui.banner("Sigma Linux builder") + ui.say " #{ui.paint("Usage", :accent, bold: true)} sigma [options]" + ui.blank + width = COMMANDS.keys.map(&:length).max + COMMANDS.each { |name, text| ui.say " #{ui.paint(name.ljust(width), :text, bold: true)} #{ui.paint(text, :muted)}" } + ui.blank + selection_help(ui) + ui.say " #{ui.paint("Options", :accent, bold: true)}" + parser.summarize { |line| ui.say " #{line}" } + ui.blank + ui.say " #{ui.paint("`sigma --help` for command options.", :muted)}" + ui.blank + 0 + end + + def selection_help(ui) + ui.say " #{ui.paint("Selections", :accent, bold: true)} #{ui.paint("stages: #{Pipeline::STAGE_NAMES.join(" → ")}", :muted)}" + [ + ["rootfs", "one stage (always runs)"], + ["rootfs..", "rootfs and every stage after it"], + ["..squashfs", "every stage up to squashfs"], + ["packages:linux-sigma,initrdbo", "part of a stage: packages…"], + ["rootfs:configure", "…or steps"] + ].each { |sel, text| ui.say " #{sel.ljust(30)} #{ui.paint(text, :muted)}" } + ui.say " #{ui.paint("(none: the whole pipeline, skipping what is up to date)", :muted)}" + ui.blank + end + + def render_error(error) + ui = @ui || UI.new + room = ui.width - 12 + lines = ui.wrap(error.message, room).map { |l| ui.paint(l, :text, bold: true) } + if error.is_a?(CommandFailed) + unless error.tail.empty? + lines << "" + lines.concat(error.excerpt.map { |l| ui.paint(l, :muted) }) + end + lines << "" << "#{ui.paint("log", :muted)} #{relative(error.log_path)}" if error.log_path + end + if error.hint + hint = ui.wrap(error.hint, room - 5) + lines << "" << "#{ui.paint("hint", :accent)} #{hint.first}" + lines.concat(hint.drop(1).map { |l| " #{l}" }) + end + ui.blank + ui.box(lines, title: ui.paint("error", :error, bold: true), role: :error) + ui.blank + 1 + end + + def relative(path) + path.to_s.delete_prefix("#{ROOT}/") + end + end +end diff --git a/lib/sigma/config.rb b/lib/sigma/config.rb new file mode 100644 index 0000000..cd11a3d --- /dev/null +++ b/lib/sigma/config.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +require "yaml" +require "etc" + +module Sigma + # Build configuration, layered (later wins): + # + # config/sigma.yml committed defaults + # config/local.yml personal overrides (git-ignored) + # --set key=value one-off overrides from the command line + class Config + DEFAULTS = "config/sigma.yml" + LOCAL = "config/local.yml" + + # Where everything lives. Intermediate state goes under cache/ (always safe + # to delete); finished artifacts go under out/. + Paths = Struct.new( + :root, :cache, :out, + :state, :logs, :keys, :ccache, + :sources, # abuild source downloads (SRCDEST) + :apk_cache, # apk package downloads for the image + :staging, # abuild working copies of packages/ + :repo, # local repository (abuild REPODEST) + :rootfs, :boot, :iso_tree, :squashfs, :iso, + keyword_init: true + ) + + REPO_NAME = "sigma" # abuild names the repo after the staging dir's parent + + attr_reader :root, :data, :sources + + def self.load(root, overrides: []) + new(root, overrides) + end + + def initialize(root, overrides) + @root = root + @sources = [] + @data = {} + [DEFAULTS, LOCAL].each do |relative| + path = File.join(root, relative) + next unless File.exist?(path) + + @data = deep_merge(@data, read_yaml(path)) + @sources << relative + end + raise Error.new("missing #{DEFAULTS}", hint: "Run sigma from the repository checkout.") if @sources.empty? + + overrides.each { |assignment| apply_override(assignment) } + @sources << "--set" unless overrides.empty? + end + + # dig("squashfs", "compression") or dig("squashfs.compression") + def dig(*keys) + keys = keys.flat_map { |k| k.to_s.split(".") } + keys.reduce(@data) { |node, key| node.is_a?(Hash) ? node[key] : nil } + end + + def fetch(path) + value = dig(path) + raise Error.new("config: `#{path}` is not set", hint: "Add it to #{LOCAL} or pass --set #{path}=…") if value.nil? + + value + end + + def name = fetch("name") + def id = fetch("id") + def arch = fetch("arch") + def profile = fetch("profile") + def builder = fetch("packages.builder") + + # `auto` leaves headroom so a build can't starve the machine: one core + # stays free, and each job gets ~1.5 GiB of RAM (roughly what one kernel + # compiler process peaks at). Using every core once made a build host + # unresponsive. + def jobs + value = dig("jobs") + return Integer(value) unless value.nil? || value == "auto" + + by_cpu = Etc.nprocessors - 1 + by_memory = (memory_gib / GIB_PER_JOB).floor + [by_cpu, by_memory].min.clamp(1, Etc.nprocessors) + end + + GIB_PER_JOB = 1.5 + + def memory_gib + File.read("/proc/meminfo")[/^MemTotal:\s+(\d+) kB/, 1].to_i / 1024.0 / 1024 + rescue SystemCallError + GIB_PER_JOB * 2 + end + + def paths + @paths ||= begin + cache = File.expand_path(dig("paths.cache") || "cache", root) + out = File.expand_path(dig("paths.out") || "out", root) + Paths.new( + root: root, cache: cache, out: out, + state: File.join(cache, "state"), + logs: File.join(cache, "logs"), + keys: File.join(cache, "keys"), + ccache: File.join(cache, "ccache"), + sources: File.join(cache, "downloads", "sources"), + apk_cache: File.join(cache, "downloads", "apk"), + staging: File.join(cache, "build", REPO_NAME), + repo: File.join(cache, "repo"), + rootfs: File.join(cache, "rootfs"), + boot: File.join(cache, "boot"), + iso_tree: File.join(cache, "iso"), + squashfs: File.join(cache, "rootfs.squashfs"), + iso: File.join(out, "#{id}-linux.iso") + ) + end + end + + # The local repository as apk sees it (REPODEST/). + def repo_dir = File.join(paths.repo, REPO_NAME) + + # Flattened "a.b.c" => value pairs, for display. + def flatten(node = @data, prefix = nil) + node.flat_map do |key, value| + path = [prefix, key].compact.join(".") + value.is_a?(Hash) ? flatten(value, path) : [[path, value]] + end + end + + private + + def read_yaml(path) + YAML.safe_load_file(path) || {} + rescue Psych::SyntaxError => e + raise Error, "#{path}: #{e.message}" + end + + def apply_override(assignment) + key, value = assignment.split("=", 2) + raise Error.new("bad --set `#{assignment}`", hint: "Use --set key.path=value") if value.nil? || key.empty? + + *parents, leaf = key.split(".") + node = parents.reduce(@data) { |n, k| n[k] = n[k].is_a?(Hash) ? n[k] : {} } + node[leaf] = YAML.safe_load(value) + rescue Psych::SyntaxError + node[leaf] = value + end + + def deep_merge(base, other) + base.merge(other) do |_, a, b| + a.is_a?(Hash) && b.is_a?(Hash) ? deep_merge(a, b) : b + end + end + end +end diff --git a/lib/sigma/context.rb b/lib/sigma/context.rb new file mode 100644 index 0000000..56251d4 --- /dev/null +++ b/lib/sigma/context.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "etc" +require "fileutils" + +module Sigma + # What a single invocation shares across stages: configuration, the UI, the + # command runner, and this run's log directory. + class Context + KEEP_LOGS = 10 # runs + + attr_reader :config, :ui, :shell, :run_id + + def initialize(config, ui) + @config = config + @ui = ui + @shell = Shell.new(builder: config.builder) + @run_id = Time.now.strftime("%Y-%m-%d_%H%M%S") + end + + def paths = config.paths + + # Opens the log for one task: cache/logs///.log, and + # points cache/logs/latest at this run. + def log(stage, name) + dir = File.join(paths.logs, run_id, stage.to_s) + FileUtils.mkdir_p(dir) + link_latest + File.open(File.join(dir, "#{name.to_s.gsub(/[^\w.-]+/, "_")}.log"), "a").tap { |f| f.sync = true } + end + + # Runs a UI task with its own log, closing the log afterwards. + def task(stage, name, label: name, &block) + log = log(stage, name) + ui.task(label, log: log, &block) + ensure + log&.close + end + + # As root, the unprivileged builder must own what abuild writes to. + def give_to_builder(path, recursive: false) + return unless shell.root? + + user = Etc.getpwnam(config.builder) + recursive ? FileUtils.chown_R(user.uid, user.gid, path) : File.chown(user.uid, user.gid, path) + rescue ArgumentError + raise Error.new("build user `#{config.builder}` doesn't exist", + hint: "The container creates it; natively, set packages.builder to an abuild user.") + end + + private + + # On the first log of a run: point cache/logs/latest here and drop the + # logs of all but the last KEEP_LOGS runs. + def link_latest + return if @linked + + latest = File.join(paths.logs, "latest") + FileUtils.rm_f(latest) + File.symlink(run_id, latest) + runs = Dir.children(paths.logs).reject { |d| d == "latest" }.sort + runs.first([runs.size - KEEP_LOGS, 0].max).each { |old| FileUtils.rm_rf(File.join(paths.logs, old)) } + @linked = true + end + end +end diff --git a/lib/sigma/docker.rb b/lib/sigma/docker.rb new file mode 100644 index 0000000..80a7b74 --- /dev/null +++ b/lib/sigma/docker.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "digest" +require "etc" +require "fileutils" + +module Sigma + # Runs sigma inside the build container: `./sigma build` on the host builds + # the image if the Dockerfile changed, then re-runs itself in a container + # with the checkout mounted at /app and the same arguments. + class Docker + MOUNT = "/app" + LABEL = "org.sigma-linux.dockerfile" + PLATFORMS = { "x86_64" => "linux/amd64", "aarch64" => "linux/arm64" }.freeze + + def self.inside? = ENV["SIGMA_CONTAINER"] == "1" + + def initialize(config, ui) + @config = config + @ui = ui + @shell = Shell.new(builder: config.builder) + end + + def dispatch(argv) + check_docker + setup_binfmt if cross? + build_image + shell = argv.first == "shell" + exec(*run_command(shell ? ["/bin/sh"] : ["./sigma", *argv], stdin: shell)) + end + + private + + def image = @config.fetch("docker.image") + def dockerfile = File.join(@config.root, "Dockerfile") + def platform = PLATFORMS.fetch(@config.arch) { raise Error, "no container platform known for arch #{@config.arch}" } + def cross? = @config.arch != Etc.uname[:machine] + + # The UID the image's build user gets. Root on the host (some CI) has no + # meaningful UID to match, so fall back to the usual first user. + def build_uid = Process.uid.zero? ? 1000 : Process.uid + + def check_docker + unless @shell.available?("docker") + raise Error.new("docker is not installed", + hint: "Install Docker, or run natively inside an Alpine environment with --native.") + end + @shell.capture("docker", "info", "--format", "{{.ServerVersion}}") + rescue CommandFailed => e + raise Error.new("can't reach the Docker daemon (#{e.tail.last || "docker info failed"})", + hint: "Start it (e.g. `doas rc-service docker start`) and make sure you can use its socket.") + end + + # The image is rebuilt only when the Dockerfile (or the build UID) changes; + # the label records what it was built from. + def build_image + wanted = Digest::SHA256.hexdigest(File.read(dockerfile) + build_uid.to_s + platform)[0, 16] + current = begin + @shell.capture("docker", "image", "inspect", image, "--format", "{{index .Config.Labels \"#{LABEL}\"}}").strip + rescue CommandFailed + nil + end + return if current == wanted + + context = Context.new(@config, @ui) + @ui.blank + context.task(:container, "image", label: "build container image") do |task| + @shell.run(task, "docker", "build", "--platform", platform, "--build-arg", "BUILD_UID=#{build_uid}", + "--label", "#{LABEL}=#{wanted}", "-t", image, "-f", dockerfile, @config.root) + task.detail = image + end + end + + def setup_binfmt + return if File.exist?("/proc/sys/fs/binfmt_misc/qemu-#{@config.arch}") + unless File.exist?("/proc/sys/fs/binfmt_misc/register") + raise Error.new("building for #{@config.arch} needs binfmt_misc, which isn't mounted", + hint: "doas modprobe binfmt_misc && doas mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc") + end + + Context.new(@config, @ui).task(:container, "binfmt", label: "enable #{@config.arch} emulation") do |task| + @shell.run(task, "docker", "run", "--privileged", "--rm", "tonistiigi/binfmt", "--install", @config.arch) + end + end + + # cache/ and out/ may be configured to live outside the checkout (another + # disk, say); those are mounted at the same path so the config still works. + def outside_mounts + [@config.paths.cache, @config.paths.out].reject { |p| p.start_with?("#{@config.root}/") }.flat_map do |path| + FileUtils.mkdir_p(path) + ["-v", "#{path}:#{path}"] + end + end + + # --privileged: building the image mounts and chroots. --init: so signals + # (Ctrl-C) reach sigma, which then unmounts before exiting. + def run_command(command, stdin: false) + interactive = if $stdin.tty? then ["-it"] + elsif stdin then ["-i"] + else [] + end + env = { "SIGMA_HOST_UID" => Process.uid, "SIGMA_HOST_GID" => Process.gid } + %w[TERM COLORTERM NO_COLOR FORCE_COLOR].each { |name| env[name] = ENV[name] if ENV[name] } + ["docker", "run", "--rm", "--init", "--privileged", *interactive, "--platform", platform, + "-v", "#{@config.root}:#{MOUNT}", *outside_mounts, "-w", MOUNT, + *env.flat_map { |k, v| ["-e", "#{k}=#{v}"] }, image, *command] + end + end +end diff --git a/lib/sigma/errors.rb b/lib/sigma/errors.rb new file mode 100644 index 0000000..49de4cf --- /dev/null +++ b/lib/sigma/errors.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Sigma + # An error meant for the user: rendered as a box with an optional hint, + # never as a Ruby backtrace. + class Error < StandardError + attr_reader :hint + + def initialize(message, hint: nil) + super(message) + @hint = hint + end + end + + # A command exited non-zero. Carries what is needed to explain it: the last + # lines it printed and where the full log is. + class CommandFailed < Error + attr_reader :command, :status, :log_path, :tail + + # The lines worth showing: abuild prints cleanup after its ">>> ERROR" + # line, so when there is one, the window ends there instead. + def excerpt(count = 15) + marker = tail.rindex { |line| line.start_with?(">>> ERROR") } + marker ? tail[[marker - count + 1, 0].max..marker] : tail.last(count) + end + + def initialize(command, status, log_path: nil, tail: [], hint: nil) + @command = command + @status = status + @log_path = log_path + @tail = tail + super("`#{command}` exited with status #{status}", hint: hint) + end + end +end diff --git a/lib/sigma/fingerprint.rb b/lib/sigma/fingerprint.rb new file mode 100644 index 0000000..a09b75d --- /dev/null +++ b/lib/sigma/fingerprint.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "digest" +require "json" + +module Sigma + # Content hashes that decide whether something needs rebuilding. + module Fingerprint + module_function + + # Hash of arbitrary values (strings, arrays, hashes, numbers). + def of(*parts) + Digest::SHA256.hexdigest(JSON.generate(parts)) + end + + # Hash of a file or directory tree: relative paths, contents, symlink + # targets and the executable bit. Timestamps are ignored on purpose, so a + # fresh checkout of the same content is "unchanged". + def tree(path) + digest = Digest::SHA256.new + return digest.hexdigest unless File.exist?(path) || File.symlink?(path) + return Digest::SHA256.file(path).hexdigest if File.file?(path) && !File.symlink?(path) + + Dir.glob("**/*", File::FNM_DOTMATCH, base: path).sort.each do |relative| + next if relative.end_with?("/.", "/..") || relative == "." + + full = File.join(path, relative) + if File.symlink?(full) + digest << "L #{relative} -> #{File.readlink(full)}\0" + elsif File.file?(full) + digest << "F #{relative} #{File.executable?(full) ? "x" : "-"} #{Digest::SHA256.file(full).hexdigest}\0" + elsif File.directory?(full) + digest << "D #{relative}\0" + end + end + digest.hexdigest + end + end +end diff --git a/lib/sigma/local_packages.rb b/lib/sigma/local_packages.rb new file mode 100644 index 0000000..acf544f --- /dev/null +++ b/lib/sigma/local_packages.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module Sigma + # The recipes under packages/, and which of them a build needs. + # + # Local packages are built on demand: a profile pulls in the ones it names + # (directly, through a subpackage like linux-sigma-dev, or through a provided + # name like initramfs-generator) plus their local dependencies. Recipes no + # profile uses cost nothing, but can still be built by name. + class LocalPackages + include Enumerable + + def initialize(dir, shell:, carch:) + @dir = dir + @shell = shell + @carch = carch + end + + def each(&) = all.each(&) + + def all + @all ||= Dir[File.join(@dir, "*", "APKBUILD")].sort.map do |path| + Apkbuild.read(File.dirname(path), shell: @shell, carch: @carch) + end + end + + def names = all.map(&:name) + + # Finds the recipe answering to +name+ (a pkgname, subpackage or provide). + def [](name) = index[name] + + # Local packages the given profile packages need, in build order. + def for_profile(packages) + seeds = packages.filter_map { |p| self[p] } + order(closure(seeds) { |pkg| pkg.depends + pkg.makedepends + pkg.checkdepends }) + end + + # The named packages plus what they need to *build*, in build order. + def for_names(requested) + seeds = requested.map do |name| + self[name] or raise Error.new("no local package `#{name}`", + hint: "Local packages: #{names.join(", ")}") + end + order(closure(seeds) { |pkg| pkg.makedepends + pkg.checkdepends }) + end + + private + + def index + @index ||= all.each_with_object({}) do |pkg, map| + pkg.aliases.each { |alias_name| map[alias_name] ||= pkg } + end + end + + def closure(seeds) + seen = {} + queue = seeds.dup + until queue.empty? + pkg = queue.shift + next if seen[pkg.name] + + seen[pkg.name] = pkg + yield(pkg).each { |dep| (local = self[dep]) && queue << local } + end + seen.values + end + + # Topological order over local dependencies; ties keep alphabetical order + # so builds are predictable. + def order(packages) + members = packages.to_h { |p| [p.name, p] } + sorted = [] + state = {} + visit = lambda do |pkg, path| + return if state[pkg.name] == :done + raise Error, "dependency cycle between local packages: #{(path + [pkg.name]).join(" → ")}" if state[pkg.name] == :visiting + + state[pkg.name] = :visiting + (pkg.depends + pkg.makedepends + pkg.checkdepends).each do |dep| + local = self[dep] + visit.call(local, path + [pkg.name]) if local && members[local.name] && local != pkg + end + state[pkg.name] = :done + sorted << pkg + end + members.values.sort_by(&:name).each { |pkg| visit.call(pkg, []) } + sorted + end + end +end diff --git a/lib/sigma/mounts.rb b/lib/sigma/mounts.rb new file mode 100644 index 0000000..7f144a1 --- /dev/null +++ b/lib/sigma/mounts.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "fileutils" + +module Sigma + # Bind mounts for chroot-style work, and the safety net around them. + # + # The image root gets the host's /dev and /proc bind-mounted into it while + # packages install. Deleting that root while anything is still mounted would + # recurse into the real /dev, so every removal goes through #remove_tree, + # which refuses to touch a path with live mounts underneath. + module Mounts + module_function + + # Mount points at or below +path+, deepest first. + def under(path) + path = File.expand_path(path) + File.readlines("/proc/self/mountinfo").filter_map do |line| + point = line.split(" ")[4].gsub(/\\([0-7]{3})/) { Regexp.last_match(1).to_i(8).chr } + point if point == path || point.start_with?("#{path}/") + end.uniq.sort_by { |p| -p.length } + rescue Errno::ENOENT + [] + end + + def mounted?(path) = under(path).include?(File.expand_path(path)) + + # Recursive bind mount, made a slave so unmounting it later can never + # propagate back to the source (the same thing arch-chroot does). + def bind(shell, task, source, target) + FileUtils.mkdir_p(target) + shell.run(task, "mount", "--rbind", source, target) + shell.run(task, "mount", "--make-rslave", target) + end + + # Unmounts everything below +path+, falling back to a lazy unmount for + # anything still busy. + def release(shell, task, path) + under(path).each do |point| + next unless mounted?(point) # a recursive unmount may already have taken it + + begin + shell.run(task, "umount", "-R", point) + rescue CommandFailed + shell.run(task, "umount", "-R", "-l", point) + end + end + return if under(path).empty? + + raise Error.new("could not unmount #{under(path).join(", ")}", + hint: "Unmount them by hand (umount -R -l ) before trying again.") + end + + def remove_tree(path) + live = under(path) + unless live.empty? + raise Error.new("refusing to delete #{path}: still mounted at #{live.join(", ")}", + hint: "Run `sigma clean rootfs`, which unmounts first, or unmount by hand.") + end + + FileUtils.rm_rf(path) + end + end +end diff --git a/lib/sigma/ownership.rb b/lib/sigma/ownership.rb new file mode 100644 index 0000000..61bc4d2 --- /dev/null +++ b/lib/sigma/ownership.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "fileutils" + +module Sigma + # The container runs as root, but the checkout belongs to someone on the + # host. After a run, what root wrote that the host user deals with (the ISO, + # logs, stamps, keys, downloads) is handed back to them, so out/ and friends + # never end up root-owned. The root filesystem itself must stay root's. + class Ownership + def initialize(context) + @context = context + end + + def restore + uid = Integer(ENV.fetch("SIGMA_HOST_UID", ""), exception: false) + gid = Integer(ENV.fetch("SIGMA_HOST_GID", ""), exception: false) + return unless uid && @context.shell.root? + + paths = @context.paths + [paths.out, paths.logs, paths.state, paths.keys, paths.apk_cache].each do |path| + FileUtils.chown_R(uid, gid, path) if File.exist?(path) + end + File.chown(uid, gid, paths.cache) if File.exist?(paths.cache) + end + end +end diff --git a/lib/sigma/pipeline.rb b/lib/sigma/pipeline.rb new file mode 100644 index 0000000..0e7f0b9 --- /dev/null +++ b/lib/sigma/pipeline.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true + +require "fileutils" + +module Sigma + # The staged build: packages → rootfs → squashfs → iso + class Pipeline + STAGES = [Stages::Packages, Stages::Rootfs, Stages::Squashfs, Stages::Iso].freeze + STAGE_NAMES = STAGES.map(&:stage_name).freeze + + STATUS_GLYPH = { fresh: [:ok, :ok], stale: [:warn, :warn], missing: [:skip, :muted] }.freeze + + attr_reader :state + + def initialize(context) + @context = context + @state = State.new(context.paths.state) + @stages = STAGES.map { |klass| klass.new(context, self) } + end + + def stage(name) = @stages.find { |s| s.name == name } + def before(stage) = (index = @stages.index(stage)).positive? ? @stages[index - 1] : nil + + def profile + @profile ||= Profile.new(File.join(@context.paths.root, "profiles"), config.profile) + end + + # -- sigma build ------------------------------------------------------------ + + def build(args, force: false, plan: false) + selection = Selection.parse(args, STAGE_NAMES) + selection.entries.each { |entry| stage(entry.stage).validate(entry.items) if entry.items } + banner(plan ? "Plan" : "Build") + return show_plan(selection, force) if plan + + started = ui.clock + ran = selection.entries.filter_map do |entry| + current = stage(entry.stage) + if selection.incremental? && !force && current.fresh? + ui.stage_skipped(current.name, "up to date") + next + end + + current.check_prerequisites + ui.stage(current.name, current.description) { current.run(entry.items, force: force) } + current + end + finish(ran, ui.clock - started) + ensure + Ownership.new(@context).restore + end + + # -- sigma status ----------------------------------------------------------- + + def status + banner("Status") + width = STAGE_NAMES.map(&:length).max + 2 + @stages.each do |current| + state_now, reason = current.status + glyph, role = STATUS_GLYPH.fetch(state_now) + stamp = current.stamp + built = stamp ? "built #{ago(stamp["finished_at"])} in #{ui.duration(stamp["duration"].to_f)}" : nil + text = state_now == :fresh ? "up to date" : reason + ui.say " #{ui.paint(UI::GLYPHS[glyph], role)} #{ui.paint(current.name.ljust(width), :accent)}" \ + "#{ui.paint(text.ljust(22), role)}#{ui.paint(built.to_s, :muted)}" + end + ui.blank + package_status + artifact_status + end + + # -- sigma clean ------------------------------------------------------------ + + def clean(args, everything: false) + banner("Clean") + return clean_everything if everything + + selection = Selection.parse(args, STAGE_NAMES) + selection.entries.reverse_each do |entry| + current = stage(entry.stage) + freed = current.outputs.sum { |path| disk_usage(path) } + freed += disk_usage(@context.paths.repo) + disk_usage(File.dirname(@context.paths.staging)) if current.name == "packages" && entry.items.nil? + current.clean(entry.items) + what = entry.items ? "#{current.name}:#{entry.items.join(",")}" : current.name + ui.line(:ok, what, freed.positive? ? "freed #{ui.bytes(freed)}" : "nothing to remove", role: :ok) + end + ui.blank + ui.say " #{ui.paint("Kept: downloads, ccache and the signing key (`sigma clean --everything` removes them).", :muted)}" + ui.blank + ensure + Ownership.new(@context).restore + end + + private + + def config = @context.config + def ui = @context.ui + + def banner(action) + runner = Docker.inside? ? "container" : "native" + ui.banner("#{config.name} · #{action.downcase}", + ["profile #{config.profile}", config.arch, "#{config.jobs} jobs", runner]) + end + + def show_plan(selection, force) + width = STAGE_NAMES.map(&:length).max + 2 + selection.entries.each do |entry| + current = stage(entry.stage) + if selection.incremental? && !force && current.fresh? + ui.say " #{ui.paint(UI::GLYPHS[:skip], :muted)} #{ui.paint(current.name.ljust(width), :muted)}#{ui.paint("up to date, skipped", :muted)}" + else + ui.say " #{ui.paint(UI::GLYPHS[:run], :accent)} #{ui.paint(current.name.ljust(width), :accent, bold: true)}#{current.plan(entry.items)}" + end + end + ui.blank + end + + def finish(ran, elapsed) + if ran.empty? + ui.box(["#{ui.paint(UI::GLYPHS[:ok], :ok, bold: true)} Everything is up to date."], role: :muted) + else + lines = ["#{ui.paint(UI::GLYPHS[:ok], :ok, bold: true)} #{ui.paint("Done in #{ui.duration(elapsed)}", :text, bold: true)}" \ + "#{ui.paint(" #{ran.map(&:name).join(" → ")}", :muted)}"] + iso = @context.paths.iso + lines << " #{relative(iso)} #{ui.paint(ui.bytes(File.size(iso)), :muted)}" if ran.any? { |s| s.name == "iso" } + lines << " #{ui.paint("try it:", :muted)} ./sigma run" if ran.any? { |s| s.name == "iso" } + ui.box(lines, role: :ok) + end + ui.blank + end + + def package_status + packages = stage("packages") + list = packages.selected + return if list.empty? + + ui.say " #{ui.paint("packages", :accent, bold: true)} #{ui.paint("needed by profile #{config.profile}", :muted)}" + list.each do |pkg| + state_now, reason = packages.package_status(pkg) + glyph, role = STATUS_GLYPH.fetch(state_now) + ui.say " #{ui.paint(UI::GLYPHS[glyph], role)} #{ui.pad("#{pkg.name} #{pkg.version}", 36)}#{ui.paint(reason || "up to date", role == :ok ? :muted : role)}" + end + others = packages.local.names - list.map(&:name) + ui.say " #{ui.paint("not in profile: #{others.join(", ")}", :muted)}" unless others.empty? + ui.blank + end + + def artifact_status + iso = @context.paths.iso + return unless File.exist?(iso) + + ui.say " #{ui.paint("image", :accent, bold: true)} #{relative(iso)} #{ui.paint("#{ui.bytes(File.size(iso))}, #{ago(File.mtime(iso).to_i)}", :muted)}" + ui.blank + end + + def clean_everything + paths = @context.paths + Mounts.under(paths.rootfs).empty? || stage("rootfs").clean + [paths.cache, paths.out].each do |dir| + freed = disk_usage(dir) + Mounts.remove_tree(dir) + ui.line(:ok, relative(dir), freed.positive? ? "freed #{ui.bytes(freed)}" : "nothing to remove", role: :ok) + end + ui.blank + end + + def disk_usage(path) + return 0 unless File.exist?(path) + + @context.shell.capture("du", "-sk", path).split.first.to_i * 1024 + rescue CommandFailed + 0 + end + + def ago(epoch) + seconds = Time.now.to_i - epoch.to_i + return "just now" if seconds < 60 + return "#{seconds / 60}m ago" if seconds < 3600 + return "#{seconds / 3600}h ago" if seconds < 86_400 + + "#{seconds / 86_400}d ago" + end + + def relative(path) = path.to_s.delete_prefix("#{@context.paths.root}/") + end +end diff --git a/lib/sigma/profile.rb b/lib/sigma/profile.rb new file mode 100644 index 0000000..dc44ee1 --- /dev/null +++ b/lib/sigma/profile.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Sigma + # A package profile: profiles/.list. + # + # # comment ignored, also after packages on a line + # foo bar packages to install + # @include minimal everything another profile resolves to, in place + # -foo -linux-fw-* remove packages added so far (globs allowed) + # + # Lines are applied in order, so a removal only affects what came before it. + class Profile + attr_reader :name, :packages, :files + + def self.names(dir) + Dir[File.join(dir, "*.list")].map { |f| File.basename(f, ".list") }.sort + end + + def initialize(dir, name) + @dir = dir + @name = name + @files = [] + @packages = resolve(name, []) + end + + def include?(package) = @packages.include?(package) + + private + + def resolve(name, stack) + path = File.join(@dir, "#{name}.list") + unless File.exist?(path) + raise Error.new("no profile `#{name}`", + hint: "Profiles: #{Profile.names(@dir).join(", ")} (#{relative(@dir)}/.list)") + end + raise Error, "profile include cycle: #{(stack + [name]).join(" → ")}" if stack.include?(name) + + @files << path + File.readlines(path, chomp: true).each_with_index.reduce([]) do |packages, (line, index)| + line = line.sub(/#.*/, "").strip + next packages if line.empty? + + if line.start_with?("@") + directive(packages, line, stack + [name], "#{relative(path)}:#{index + 1}") + else + line.split.reduce(packages) do |acc, token| + token.start_with?("-") ? acc.reject { |p| File.fnmatch?(token[1..], p) } : acc | [token] + end + end + end + end + + def directive(packages, line, stack, where) + keyword, argument, *rest = line.split + unless keyword == "@include" && argument && rest.empty? + raise Error.new("#{where}: don't understand `#{line}`", hint: "The only directive is `@include `.") + end + + packages | resolve(argument, stack) + end + + def relative(path) = path.delete_prefix("#{ROOT}/") + end +end diff --git a/lib/sigma/qemu.rb b/lib/sigma/qemu.rb new file mode 100644 index 0000000..3ad5cdb --- /dev/null +++ b/lib/sigma/qemu.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "open3" + +module Sigma + # Boots the built ISO in a VM, for a quick look at the result. + class Qemu + OVMF = %w[ + /usr/share/OVMF/OVMF.fd + /usr/share/ovmf/OVMF.fd + /usr/share/ovmf/x64/OVMF.fd + /usr/share/edk2/x64/OVMF.fd + /usr/share/qemu/OVMF.fd + ].freeze + + def initialize(config, ui) + @config = config + @ui = ui + end + + DISK_SIZE = "20G" + + # Boots the ISO, optionally with a virtual disk attached (created on first + # use) to try the installer on. With iso: false only the disk boots, + # which needs UEFI: setup-sigma installs GRUB for EFI only. + def boot(uefi: false, disk: nil, iso: true, extra: []) + raise Error.new("nothing to boot", hint: "--no-iso needs --disk.") unless iso || disk + raise Error.new("an installed system boots with UEFI only", hint: "Add --uefi.") if !iso && !uefi + + cmd = ["qemu-system-#{@config.arch}", "-smp", "4", "-m", "2048"] + if iso + image = @config.paths.iso + raise Error.new("no ISO at #{relative(image)}", hint: "Build one with `sigma build`.") unless File.exist?(image) + + cmd += ["-cdrom", image] + end + if disk + create_disk(disk) + # NVMe, like real hardware: virtio_blk isn't in the initramfs's + # module list, so an installed system on a virtio disk can't find /. + cmd += ["-drive", "file=#{disk},format=qcow2,if=none,id=sigmadisk", "-device", "nvme,serial=sigma,drive=sigmadisk"] + end + cmd += ["-enable-kvm"] if File.writable?("/dev/kvm") + cmd += ["-bios", firmware] if uefi + cmd += extra + + @ui.banner("Booting #{iso ? File.basename(@config.paths.iso) : File.basename(disk)}", + [uefi ? "UEFI" : "BIOS", File.writable?("/dev/kvm") ? "KVM" : "no KVM (slow)", + disk && iso ? "disk #{relative(disk)}" : nil].compact) + exec(*cmd) + rescue Errno::ENOENT => e + raise Error.new("#{e.message.split(" - ").last} not found", hint: "Install QEMU on this machine (apk add qemu-system-#{@config.arch} qemu-img).") + end + + private + + def firmware + OVMF.find { |path| File.exist?(path) } or + raise Error.new("no UEFI firmware found", hint: "Install OVMF (apk add ovmf), looked in: #{OVMF.join(", ")}") + end + + # A sparse qcow2 image: it only takes the space the guest writes. + def create_disk(path) + return if File.exist?(path) + + out, status = Open3.capture2e("qemu-img", "create", "-f", "qcow2", path, DISK_SIZE) + raise Error, "could not create #{path}: #{out.strip}" unless status.success? + + @ui.say " #{@ui.paint("created #{relative(path)} (#{DISK_SIZE}, sparse)", :muted)}" + end + + def relative(path) = path.delete_prefix("#{@config.root}/") + end +end diff --git a/lib/sigma/selection.rb b/lib/sigma/selection.rb new file mode 100644 index 0000000..7e92f7a --- /dev/null +++ b/lib/sigma/selection.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "did_you_mean" + +module Sigma + # Parses what the user asked the pipeline to do. + # + # (nothing) every stage, incrementally (up-to-date ones skipped) + # rootfs one stage + # rootfs.. ..squashfs open ranges + # rootfs..iso closed range + # packages:a,b part of a stage (packages, or steps) + # + # Arguments combine; the result is always in pipeline order, and a stage + # asked for both whole and in part runs whole. + class Selection + Entry = Struct.new(:stage, :items) # items: nil means the whole stage + + attr_reader :entries + + def self.parse(args, stages) = new(args, stages) + + def initialize(args, stages) + @stages = stages + @incremental = args.empty? + wanted = {} + (args.empty? ? stages : []).each { |s| wanted[s] = nil } + args.each { |arg| add(wanted, arg) } + @entries = stages.filter_map { |s| Entry.new(s, wanted[s]) if wanted.key?(s) } + end + + # True for a bare `sigma build`: skip stages that are up to date. + def incremental? = @incremental + + def stages = entries.map(&:stage) + + private + + def add(wanted, arg) + if arg.include?(":") && arg.include?("..") + raise Error.new("`#{arg}` mixes a range with parts of a stage", hint: "Use separate arguments, e.g. `rootfs:configure squashfs..`.") + end + + case arg + when /\A([\w-]*)\.\.([\w-]*)\z/ + from = Regexp.last_match(1).empty? ? @stages.first : stage(Regexp.last_match(1)) + to = Regexp.last_match(2).empty? ? @stages.last : stage(Regexp.last_match(2)) + range = @stages[@stages.index(from)..@stages.index(to)] + raise Error.new("`#{arg}` is backwards", hint: "Stages run #{@stages.join(" → ")}.") if range.nil? || range.empty? + + range.each { |s| wanted[s] = nil } + when /\A([\w-]+):(.+)\z/ + name = stage(Regexp.last_match(1)) + items = Regexp.last_match(2).split(",").map(&:strip).reject(&:empty?) + raise Error, "`#{arg}`: nothing after the colon" if items.empty? + return if wanted.key?(name) && wanted[name].nil? # already whole + + wanted[name] = ((wanted[name] || []) + items).uniq + when /\A[\w-]+\z/ + wanted[stage(arg)] = nil + else + raise Error.new("can't read selection `#{arg}`", hint: "See `sigma build --help`.") + end + end + + def stage(name) + return name if @stages.include?(name) + + guess = DidYouMean::SpellChecker.new(dictionary: @stages).correct(name).first + raise Error.new("no stage `#{name}`#{guess ? ", did you mean `#{guess}`?" : ""}", + hint: "Stages: #{@stages.join(" → ")}") + end + end +end diff --git a/lib/sigma/shell.rb b/lib/sigma/shell.rb new file mode 100644 index 0000000..372e64f --- /dev/null +++ b/lib/sigma/shell.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require "open3" + +module Sigma + # Runs external commands. Output never goes straight to the terminal: it is + # fed line by line to the running UI task, which writes it to the task's log + # and shows the latest line (or every line, in verbose mode). + class Shell + TAIL_LINES = 80 + + def initialize(builder:) + @builder = builder + end + + def root? = Process.uid.zero? + + # Runs +cmd+ as part of +task+. Raises CommandFailed on a non-zero exit. + # + # as: :builder run as the unprivileged abuild user (only matters as root) + # input: string written to the command's stdin + # heavy: long CPU-bound work (compiling, compressing); runs at + # lower CPU priority so the machine stays usable + def run(task, *cmd, env: {}, chdir: nil, as: nil, input: nil, heavy: false) + cmd = cmd.flatten.map(&:to_s) + argv, spawn_env = wrap(heavy ? ["nice", "-n", "10", *cmd] : cmd, env, as) + task.log&.puts("$ #{display(cmd, env)}") + tail = [] + + status = Open3.popen2e(spawn_env, *argv, **spawn_options(chdir)) do |stdin, out, wait| + stdin.write(input) if input + stdin.close + each_line(out) do |line| + task.output(line) + tail << line + tail.shift if tail.size > TAIL_LINES + end + wait.value + end + + return if status.success? + + # The log has the full command with its environment; the error keeps + # to the command itself. + raise CommandFailed.new(display(cmd, {}), status.exitstatus || status.termsig, + log_path: task.log&.path, tail: tail) + rescue Errno::ENOENT + raise Error.new("command not found: #{cmd.first}", + hint: "Is it installed? Building inside the container (the default) provides every tool.") + end + + # Runs a query and returns its stdout. For short commands whose output we + # parse; failures raise with stderr attached. + def capture(*cmd, env: {}, chdir: nil, as: nil) + cmd = cmd.flatten.map(&:to_s) + argv, spawn_env = wrap(cmd, env, as) + out, err, status = Open3.capture3(spawn_env, *argv, **spawn_options(chdir)) + return out if status.success? + + raise CommandFailed.new(display(cmd, env), status.exitstatus, tail: err.lines.last(TAIL_LINES).map(&:chomp)) + rescue Errno::ENOENT + raise Error, "command not found: #{cmd.first}" + end + + def available?(command) + ENV.fetch("PATH", "").split(":").any? { |dir| File.executable?(File.join(dir, command)) } + end + + private + + # Dropping privileges goes through doas (configured in the Dockerfile). + # doas resets the environment to the target user's, so ours is passed + # explicitly via env(1). + def wrap(cmd, env, as) + if as == :builder && root? + pairs = env.map { |k, v| "#{k}=#{v}" } + [["doas", "-u", @builder, "--", "env", *pairs, *cmd], {}] + else + [cmd, env.transform_keys(&:to_s).transform_values(&:to_s)] + end + end + + def spawn_options(chdir) + chdir ? { chdir: chdir.to_s } : {} + end + + # A copy-pasteable rendering of a command, quoted for humans. + def display(cmd, env) + (env.map { |k, v| "#{k}=#{quote(v.to_s)}" } + cmd.map { |c| quote(c) }).join(" ") + end + + def quote(arg) + return arg if arg.match?(%r{\A[\w./:=@%+,-]+\z}) + + "'#{arg.gsub("'") { %q('\\'') }}'" + end + + # Splits on both \n and \r so progress bars (curl, wget) become lines. + def each_line(io) + buffer = +"" + loop do + buffer << io.readpartial(65_536).force_encoding(Encoding::UTF_8).scrub + while (match = buffer.match(/\r\n|\r|\n/)) + # A trailing lone \r may be the first half of a \r\n still in flight. + break if match[0] == "\r" && match.end(0) == buffer.length + + yield buffer.slice!(0, match.end(0)).chomp + end + end + rescue EOFError + yield buffer unless buffer.empty? + end + end +end diff --git a/lib/sigma/signing.rb b/lib/sigma/signing.rb new file mode 100644 index 0000000..8e0b442 --- /dev/null +++ b/lib/sigma/signing.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "fileutils" +require "securerandom" + +module Sigma + # The key that signs the local repository. It lives in cache/keys/ so it + # survives container rebuilds, which means the image can verify our packages + # like any other instead of installing them --allow-untrusted. + class Signing + def initialize(context) + @context = context + @dir = context.paths.keys + end + + def private_key = Dir[File.join(@dir, "*.rsa")].min + def public_key = private_key && "#{private_key}.pub" + + # Creates the key pair on first use. apk finds a public key by the file + # name abuild-sign embeds, so the name must stay stable once generated. + def ensure(task) + return private_key if private_key + + FileUtils.mkdir_p(@dir) + @context.give_to_builder(@dir) + key = File.join(@dir, "#{@context.config.id}-#{SecureRandom.hex(4)}.rsa") + # Generated as the builder, who signs with it; private stays 0600. + @context.shell.run(task, "openssl", "genrsa", "-out", key, "4096", as: :builder) + @context.shell.run(task, "openssl", "rsa", "-in", key, "-pubout", "-out", "#{key}.pub", as: :builder) + File.chmod(0o600, key) + key + end + + # Lets this machine's apk (abuild -r installing local makedepends) trust + # the repository. Only possible, and only needed, as root in the container. + def trust_here + return unless @context.shell.root? && public_key + + FileUtils.mkdir_p("/etc/apk/keys") + FileUtils.cp(public_key, "/etc/apk/keys/") + end + end +end diff --git a/lib/sigma/stage.rb b/lib/sigma/stage.rb new file mode 100644 index 0000000..08d7ceb --- /dev/null +++ b/lib/sigma/stage.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +module Sigma + # A pipeline stage: a named list of steps, the inputs that decide whether it + # is up to date, and the outputs it leaves behind. + # + # A stage is fresh when its stamp matches its current inputs, it was built + # against the current output of the stage before it, and its outputs exist. + class Stage + class << self + attr_reader :stage_name, :description, :steps + + def stage(name, description, steps: []) + @stage_name = name + @description = description + @steps = steps + end + end + + attr_reader :context + + def initialize(context, pipeline) + @context = context + @pipeline = pipeline + end + + def name = self.class.stage_name + def description = self.class.description + def steps = self.class.steps + def config = context.config + def paths = context.paths + def ui = context.ui + def shell = context.shell + def state = @pipeline.state + + # -- freshness ------------------------------------------------------------ + + def key = "stage/#{name}" + def stamp = state.read(key) + def upstream = @pipeline.before(self) + + # What downstream stages record to notice that this one ran again. + def token = stamp&.fetch("token", nil) + + # Fingerprint of everything this stage reads, besides the stage before it. + def inputs = raise(NotImplementedError) + + # Paths that must exist for the stage to count as built. + def outputs = [] + + # [:fresh | :stale | :missing, reason] + def status + current = stamp + return [:missing, "never built"] unless current + return [:stale, "output missing"] unless outputs.all? { |p| File.exist?(p) } + return [:stale, "inputs changed"] if current["inputs"] != inputs + return [:stale, "#{upstream.name} changed"] if upstream && current["upstream"] != upstream.token + + [:fresh, nil] + end + + def fresh? = status.first == :fresh + + # -- running ---------------------------------------------------------------- + + # Raises unless +items+ name things this stage can do. + def validate(items) + unknown = items - steps.map(&:to_s) + return if unknown.empty? + + raise Error.new("#{name} has no step `#{unknown.first}`", hint: "Steps: #{steps.join(", ")}") + end + + # Short description of what +items+ will do, for --plan. + def plan(items) = (items || steps).join(", ") + + # Raises if what this stage consumes isn't there. + def check_prerequisites; end + + # Runs the selected steps (all by default) and returns a summary line. + def run(items, force: false) # rubocop:disable Lint/UnusedMethodArgument -- used by overrides + selected = (items || steps.map(&:to_s)).map(&:to_sym) + prior = stamp + started = ui.clock + around_steps(selected) do + selected.each { |step| context.task(name, step, label: step_label(step)) { |t| send("step_#{step}", t) } } + end + record(items.nil?, prior, ui.clock - started) + summary + end + + # Deletes what the stage produced, and its stamp. + def clean(_items = nil) + outputs.each { |path| Mounts.remove_tree(path) } + state.delete(key) + end + + protected + + def around_steps(_selected) = yield + def step_label(step) = step.to_s + def summary = nil + + # A whole run records the inputs it was built from. A partial run leaves + # the recorded inputs alone (the stage as a whole wasn't redone) but still + # takes a new token, so downstream stages see that something changed. + def record(whole, prior, duration) + if whole + state.write(key, "inputs" => inputs, "upstream" => upstream&.token, "duration" => duration) + elsif prior + state.write(key, prior.merge("token" => nil, "duration" => duration)) + end + end + + def relative(path) = path.to_s.delete_prefix("#{paths.root}/") + + def require_output(path, from) + return if File.exist?(path) + + raise Error.new("#{relative(path)} is missing", hint: "Build it first: `sigma build #{from}`.") + end + end +end diff --git a/lib/sigma/stages/iso.rb b/lib/sigma/stages/iso.rb new file mode 100644 index 0000000..85d28d7 --- /dev/null +++ b/lib/sigma/stages/iso.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "erb" +require "fileutils" + +module Sigma + module Stages + # Assembles the bootable ISO in out/: the kernels and initramfs collected + # in cache/boot, the squashfs, and a GRUB menu with one entry per kernel. + class Iso < Stage + stage "iso", "bootable ISO", steps: %i[tree grub image] + + TEMPLATE = "image/grub.cfg.erb" + + # The boot menu theme ships in sigma-conf, so the live ISO and installed + # systems (through /etc/default/grub) share one source of truth. + THEME_DIR = "packages/sigma-conf/rootfs/usr/share/sigma/grub" + + Kernel = Struct.new(:flavor, :vmlinuz, :initramfs, :title, keyword_init: true) + + def tree = paths.iso_tree + def theme_dir = File.join(paths.root, THEME_DIR) + + def inputs + Fingerprint.of(config.name, config.dig("iso"), File.read(File.join(paths.root, TEMPLATE)), + Fingerprint.tree(theme_dir)) + end + + def outputs = [paths.iso] + + def clean(_items = nil) + Mounts.remove_tree(tree) + super + end + + def check_prerequisites + require_output(paths.squashfs, "squashfs") + return unless kernels.empty? + + raise Error.new("no kernel in #{relative(paths.boot)}", + hint: "The profile must install a kernel (linux-sigma); rebuild with `sigma build rootfs..`.") + end + + # Boot entries: the distribution's own kernel first, then any others. + def kernels + Dir[File.join(paths.boot, "vmlinuz-*")].map { |path| File.basename(path).delete_prefix("vmlinuz-") } + .select { |flavor| File.exist?(File.join(paths.boot, "initramfs-#{flavor}")) } + .sort_by { |flavor| [flavor == config.id ? 0 : 1, flavor] } + .then { |flavors| entries(flavors) } + end + + # Theme values from sigma-conf's grub.conf: plain KEY="value" lines. + def theme + File.readlines(File.join(theme_dir, "grub.conf"), chomp: true).each_with_object({}) do |line, vars| + next unless (match = line.match(/\A([A-Z_][A-Z0-9_]*)=(?:"([^"]*)"|'([^']*)'|(\S*))\s*(?:#.*)?\z/)) + + vars[match[1]] = match[2] || match[3] || match[4] + end + end + + protected + + def step_label(step) = { tree: "stage files", grub: "boot menu", image: "write image" }.fetch(step) + def summary = "#{relative(paths.iso)} · #{ui.bytes(File.size(paths.iso))}" + + def step_tree(task) + Mounts.remove_tree(tree) + FileUtils.mkdir_p(File.join(tree, "boot", "grub")) + shell.run(task, "cp", "-a", "#{paths.boot}/.", File.join(tree, "boot/")) + squashfs = File.join(tree, "rootfs.squashfs") + begin + File.link(paths.squashfs, squashfs) # same filesystem: no 2 GB copy + rescue SystemCallError + FileUtils.cp(paths.squashfs, squashfs) + end + task.detail = ui.plural(kernels.size, "kernel") + end + + def step_grub(task) + grub = File.join(tree, "boot", "grub") + FileUtils.mkdir_p(grub) + FileUtils.cp(File.join(theme_dir, "wallpaper.png"), File.join(grub, "wallpaper.png")) + File.write(File.join(grub, "grub.cfg"), render) + task.detail = kernels.map(&:flavor).join(", ") + end + + def step_image(task) + FileUtils.mkdir_p(File.dirname(paths.iso)) + shell.run(task, "grub-mkrescue", tree, "-o", paths.iso, "-volid", config.fetch("iso.volume_id"), heavy: true) + task.detail = ui.bytes(File.size(paths.iso)) + end + + private + + def entries(flavors) + flavors.map do |flavor| + Kernel.new(flavor: flavor, vmlinuz: "vmlinuz-#{flavor}", initramfs: "initramfs-#{flavor}", + title: flavors.size == 1 ? config.name : "#{config.name} (#{flavor})") + end + end + + def render + values = { name: config.name, volume_id: config.fetch("iso.volume_id"), kernels: kernels, theme: theme, + cmdline: theme.fetch("GRUB_CMDLINE_LINUX_DEFAULT", "") } + ERB.new(File.read(File.join(paths.root, TEMPLATE)), trim_mode: "-").result_with_hash(values) + end + end + end +end diff --git a/lib/sigma/stages/packages.rb b/lib/sigma/stages/packages.rb new file mode 100644 index 0000000..ceec31b --- /dev/null +++ b/lib/sigma/stages/packages.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require "fileutils" + +module Sigma + module Stages + # Builds the local packages into a signed repository at cache/repo/. + # + # Each package is tracked on its own: it rebuilds when anything in its + # packages// directory changes, or when its .apk files are missing. + # Packages build in a disposable copy (cache/build/), so abuild never + # writes into the source tree. + class Packages < Stage + stage "packages", "local APK repository" + + TARBALL = /\.(tar\.(gz|xz|zst|bz2)|tgz)\z/ + + def local + @local ||= LocalPackages.new(File.join(paths.root, "packages"), shell: shell, carch: config.arch) + end + + # The packages a run covers: the named ones, or what the profile needs. + def selected(items = nil) + items ? local.for_names(items) : local.for_profile(@pipeline.profile.packages) + end + + def validate(items) = local.for_names(items) + + def plan(items) + list = selected(items) + pending = list.reject { |pkg| package_status(pkg).first == :fresh } + "#{ui.plural(list.size, "package")} (#{pending.empty? ? "all up to date" : "to build: #{pending.map(&:name).join(", ")}"})" + end + + def inputs = Fingerprint.of(selected.map { |pkg| [pkg.name, fingerprint(pkg)] }) + + # Changes whenever a package the profile uses is rebuilt. + def token = Fingerprint.of(selected.map { |pkg| state.read(package_key(pkg))&.fetch("token", nil) }) + + def status + pending = selected.map { |pkg| [pkg, package_status(pkg)] }.reject { |_, (s, _)| s == :fresh } + return [:fresh, nil] if pending.empty? + return [:missing, "never built"] if pending.all? { |_, (s, _)| s == :missing } && pending.size == selected.size + + [:stale, "#{ui.plural(pending.size, "package")} to build"] + end + + def package_status(pkg) + stamp = state.read(package_key(pkg)) + return [:missing, "never built"] unless stamp + return [:stale, "#{stamp["version"]} → #{pkg.version}"] if stamp["version"] != pkg.version + return [:stale, "recipe changed"] if stamp["fingerprint"] != fingerprint(pkg) + return [:stale, "APKs missing"] unless stamp["apks"].all? { |apk| File.exist?(File.join(arch_dir, apk)) } + + [:fresh, nil] + end + + def run(items, force: false) + list = selected(items) + signing = Signing.new(context) + prepare_directories + context.task(name, "signing-key", label: "signing key") do |t| + had_key = signing.private_key + signing.ensure(t) + signing.trust_here + t.detail = had_key ? File.basename(signing.private_key) : "generated #{File.basename(signing.private_key)}" + end + + built = list.count do |pkg| + state_now, reason = package_status(pkg) + if state_now == :fresh && !force + ui.line(:skip, label(pkg), "up to date") + next false + end + + build(pkg, signing.private_key, reason: force && state_now == :fresh ? "forced" : reason) + true + end + + @summary = [built.positive? ? "#{built} built" : nil, "#{list.size - built} up to date"].compact.join(" · ") + @summary = ui.plural(list.size, "package") if built == list.size + install_hint(list) if items + @summary + end + + def clean(items = nil) + if items + validate(items) + items.map { |item| local[item] }.uniq.each do |pkg| + (state.read(package_key(pkg))&.fetch("apks", []) || []).each { |apk| FileUtils.rm_f(File.join(arch_dir, apk)) } + state.delete(package_key(pkg)) + end + else + [paths.repo, File.dirname(paths.staging)].each { |dir| FileUtils.rm_rf(dir) } + state.keys("package").each { |key| state.delete(key) } + state.delete(key) + end + end + + protected + + def summary = @summary + + private + + def label(pkg) = "#{pkg.name} #{pkg.version}" + def package_key(pkg) = "package/#{pkg.name}" + def arch_dir = File.join(config.repo_dir, config.arch) + + def fingerprint(pkg) = Fingerprint.of(Fingerprint.tree(pkg.dir), config.arch) + + def build(pkg, key, reason:) + context.task(name, pkg.name, label: label(pkg)) do |task| + started = ui.clock + task.detail = reason + dir = stage_copy(pkg, task) + task.detail = "checksum" + abuild(task, dir, key, "checksum") + task.detail = reason + abuild(task, dir, key, "-r", "-f", *(config.dig("packages.keep_build_trees") ? ["-K"] : [])) + apks = prune(pkg, task, dir, key) + state.write(package_key(pkg), "version" => pkg.version, "fingerprint" => fingerprint(pkg), + "apks" => apks, "duration" => ui.clock - started) + task.detail = "built #{ui.bytes(apks.sum { |apk| File.size(File.join(arch_dir, apk)) })}" + end + end + + # A fresh working copy in cache/build/sigma/. A source named + # foo.tar.gz with a sibling foo/ directory is packed here, reproducibly, + # which is how sigma-conf ships rootfs/ without per-package code. + def stage_copy(pkg, task) + dest = File.join(paths.staging, pkg.name) + FileUtils.rm_rf(dest) + FileUtils.copy_entry(pkg.dir, dest, true) + pkg.local_sources.each do |source| + next if File.exist?(File.join(dest, source)) || !source.match?(TARBALL) + + stem = source.sub(TARBALL, "") + next unless File.directory?(File.join(dest, stem)) + + compress = source.end_with?("gz") ? ["-I", "gzip -n"] : ["-a"] + shell.run(task, "tar", "--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner", + *compress, "-cf", source, stem, chdir: dest) + end + context.give_to_builder(dest, recursive: true) + dest + end + + def abuild(task, dir, key, *args) + shell.run(task, "abuild", "-P", paths.repo, *args, chdir: dir, as: :builder, env: abuild_env(key), heavy: true) + end + + def abuild_env(key) + jobs = config.jobs + env = { + "SRCDEST" => paths.sources, + "REPODEST" => paths.repo, + "PACKAGER" => "#{config.name} builder", + "PACKAGER_PRIVKEY" => key, + "JOBS" => jobs, + "MAKEFLAGS" => "-j#{jobs}" + } + env.merge!("USE_CCACHE" => "1", "CCACHE_DIR" => paths.ccache) if config.dig("packages.ccache") + env + end + + # Keeps only this version's .apk files for the package's outputs and + # returns them; re-indexes when older versions were removed. + def prune(pkg, task, dir, key) + files = Dir.children(arch_dir).select { |f| f.end_with?(".apk") } + mine = files.select { |f| pkg.outputs.any? { |o| f.match?(/\A#{Regexp.escape(o)}-\d[^-]*-r\d+\.apk\z/) } } + current = mine.select { |f| pkg.outputs.any? { |o| f == "#{o}-#{pkg.version}.apk" } } + stale = mine - current + raise Error, "abuild finished but produced no APKs for #{pkg.name}" if current.empty? + return current if stale.empty? + + stale.each { |f| FileUtils.rm_f(File.join(arch_dir, f)) } + abuild(task, dir, key, "index") + current + end + + def prepare_directories + [paths.staging, paths.repo, paths.sources, paths.ccache].each do |dir| + FileUtils.mkdir_p(dir) + context.give_to_builder(dir) + end + context.give_to_builder(File.dirname(paths.staging)) + context.give_to_builder(File.dirname(paths.sources)) + share_apk_cache + end + + # In the container, point apk's cache at cache/downloads/apk: the + # makedepends `abuild -r` installs for each build then come from disk. + def share_apk_cache + return unless Docker.inside? && shell.root? + + FileUtils.mkdir_p(paths.apk_cache) + return if File.symlink?("/etc/apk/cache") && File.readlink("/etc/apk/cache") == paths.apk_cache + + FileUtils.rm_rf("/etc/apk/cache") + File.symlink(paths.apk_cache, "/etc/apk/cache") + end + + # After `sigma pkg`, say where the result is and how to use it here. + def install_hint(list) + names = list.map(&:name).join(" ") + ui.note "repository #{relative(config.repo_dir)}" + ui.note "install it on this machine: doas apk add --repository #{relative(config.repo_dir)} #{names}" + ui.note "(trust the key once first: doas cp #{relative(paths.keys)}/*.pub /etc/apk/keys/)" + end + end + end +end diff --git a/lib/sigma/stages/rootfs.rb b/lib/sigma/stages/rootfs.rb new file mode 100644 index 0000000..33df07d --- /dev/null +++ b/lib/sigma/stages/rootfs.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +require "fileutils" + +module Sigma + module Stages + # Builds the root filesystem in cache/rootfs/, and collects what packages + # install into /boot (kernel, initramfs) in cache/boot/. + # + # Every run starts from an empty root: the configure step isn't idempotent + # (users, services), and with the apk download cache a fresh install takes + # about a minute. + class Rootfs < Stage + stage "rootfs", "root filesystem", steps: %i[prepare install configure] + + # Alpine's grub trigger regenerates grub.cfg whenever a kernel is + # installed. Inside the image build that's wasted work at best, so it is + # disabled while we build, and the guard is removed before shipping so + # installed systems keep regenerating their (themed) config. + GRUB_GUARD = "etc/update-grub.conf" + + def root = paths.rootfs + def profile = @pipeline.profile + def overlay = File.expand_path(config.fetch("rootfs.overlay"), paths.root) + def repositories = File.expand_path(config.fetch("rootfs.repositories"), paths.root) + + def inputs + Fingerprint.of(profile.packages, profile.files.map { |f| File.read(f) }, Fingerprint.tree(overlay), + File.read(repositories), config.dig("rootfs"), config.arch, config.id) + end + + def outputs = [root, paths.boot] + + def plan(items) + selected = (items || steps).map(&:to_s) + detail = selected.include?("install") ? " (#{profile.packages.size} packages, profile #{profile.name})" : "" + "#{selected.join(", ")}#{detail}" + end + + def check_prerequisites + return if @pipeline.stage("packages").selected.empty? + + require_output(File.join(config.repo_dir, config.arch, "APKINDEX.tar.gz"), "packages") + end + + def run(items, force: false) + unless shell.root? + raise Error.new("building the root filesystem needs root (mounts, chroot)", + hint: "Run it in the container (drop --native), or as root.") + end + if items && !items.include?("prepare") && !File.directory?(File.join(root, "etc")) + raise Error.new("there is no root filesystem to #{items.join(" and ")}", hint: "Run the whole stage: `sigma build rootfs`.") + end + + super + end + + def clean(_items = nil) + unless Mounts.under(root).empty? + context.task(name, "unmount", label: "unmount leftovers") { |t| Mounts.release(shell, t, root) } + end + super + end + + protected + + def step_label(step) + { prepare: "prepare", install: "install #{profile.packages.size} packages", configure: "configure" }.fetch(step) + end + + def summary = "#{ui.bytes(disk_usage(root))} root" + + def step_prepare(task) + Mounts.release(shell, task, root) # leftovers of an interrupted run + [root, paths.boot].each { |dir| Mounts.remove_tree(dir) } + FileUtils.mkdir_p([File.join(root, "etc/apk/keys"), paths.boot]) + + # Trust Alpine's keys (from this machine) and our repository's key, so + # every package is verified: no --allow-untrusted. + keys = Dir["/etc/apk/keys/*.pub"] + [Signing.new(context).public_key].compact + FileUtils.cp(keys, File.join(root, "etc/apk/keys")) + File.write(File.join(root, GRUB_GUARD), "disable_trigger=1\n") + + # Overlay files go in before packages too, so install-time triggers + # see them (the initramfs generator reads /etc/modules). + copy_overlay + task.detail = "#{ui.plural(keys.size, "trusted key")}" + end + + def step_install(task) + FileUtils.mkdir_p(paths.apk_cache) + chroot_mounts(task) do + shell.run(task, "apk", "add", "--root", root, "--initdb", "--arch", config.arch, "--update-cache", + "--repositories-file", repositories, "--repository", config.repo_dir, + "--cache-dir", paths.apk_cache, *profile.packages) + end + task.detail = task.last_line.to_s[/\AOK: (.*)/, 1] + end + + def step_configure(task) + chroot_mounts(task) do + copy_overlay(drop_apk_new: true) + File.write(File.join(root, "etc/hostname"), "#{config.id}\n") + FileUtils.cp(repositories, File.join(root, "etc/apk/repositories")) + + config.fetch("rootfs.skel_dirs").each { |dir| FileUtils.mkdir_p(File.join(root, "etc/skel", dir)) } + # Root gets a desktop-ready home too (logging in as root on the live ISO). + shell.run(task, "cp", "-a", "#{root}/etc/skel/.", "#{root}/root/") + + enable_services(task) + create_user(task) + + # https://alpinelinux.org/posts/2025-10-01-usr-merge.html + shell.run(task, "chroot", root, "merge-usr") + FileUtils.rm_f(File.join(root, GRUB_GUARD)) + end + end + + private + + def enable_services(task) + config.fetch("rootfs.services").each do |level, services| + services.each { |service| shell.run(task, "chroot", root, "rc-update", "add", service, level) } + end + end + + def create_user(task) + user = config.fetch("rootfs.user") + shell.run(task, "useradd", "-R", root, "-s", user.fetch("shell"), "-m", + "-G", user.fetch("groups").join(","), user.fetch("name")) + shell.run(task, "chroot", root, "chpasswd", input: "#{user.fetch("name")}:#{user.fetch("password")}\n") + end + + # Packages write to /dev and /proc while installing (e.g. /dev/null), and + # the kernel and initramfs land in /boot, which is cache/boot here. + def chroot_mounts(task) + Mounts.bind(shell, task, "/dev", File.join(root, "dev")) + Mounts.bind(shell, task, "/proc", File.join(root, "proc")) + Mounts.bind(shell, task, paths.boot, File.join(root, "boot")) + yield + ensure + begin + Mounts.release(shell, task, root) + rescue Error => e + ui.warn(e.message) + end + end + + # Files are written by us (so owned by root in the image, whatever owns + # the checkout) with their modes kept. With drop_apk_new, the .apk-new + # copies apk left next to them while installing are removed: the + # overlay wins. + def copy_overlay(drop_apk_new: false) + Dir.glob("**/*", File::FNM_DOTMATCH, base: overlay).sort.each do |relative| + next if relative.end_with?("/.", "/..") || relative == "." + + source = File.join(overlay, relative) + target = File.join(root, relative) + if File.symlink?(source) + FileUtils.rm_f(target) + File.symlink(File.readlink(source), target) + elsif File.directory?(source) + FileUtils.mkdir_p(target) + else + FileUtils.mkdir_p(File.dirname(target)) + FileUtils.rm_f(target) + FileUtils.install(source, target, mode: File.stat(source).mode & 0o7777) + FileUtils.rm_f("#{target}.apk-new") if drop_apk_new + end + end + end + + def disk_usage(path) + shell.capture("du", "-sk", path).split.first.to_i * 1024 + rescue CommandFailed + 0 + end + end + end +end diff --git a/lib/sigma/stages/squashfs.rb b/lib/sigma/stages/squashfs.rb new file mode 100644 index 0000000..f768467 --- /dev/null +++ b/lib/sigma/stages/squashfs.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "fileutils" + +module Sigma + module Stages + # Compresses the root filesystem into cache/rootfs.squashfs, which the + # live ISO boots from. + class Squashfs < Stage + stage "squashfs", "compressed root image", steps: %i[compress] + + def inputs = Fingerprint.of(config.dig("squashfs")) + def outputs = [paths.squashfs] + + def check_prerequisites + require_output(File.join(paths.rootfs, "etc", "apk", "world"), "rootfs") + return if Mounts.under(paths.rootfs).empty? + + raise Error.new("#{relative(paths.rootfs)} still has things mounted in it", + hint: "An earlier run was interrupted; `sigma build rootfs` cleans it up.") + end + + protected + + def step_label(_step) = "compress (#{config.fetch("squashfs.compression")})" + def summary = ui.bytes(File.size(paths.squashfs)) + + def step_compress(task) + FileUtils.mkdir_p(File.dirname(paths.squashfs)) + shell.run(task, "mksquashfs", paths.rootfs, paths.squashfs, "-noappend", + "-comp", config.fetch("squashfs.compression"), "-processors", config.jobs, + *Array(config.dig("squashfs.args")), heavy: true) + task.detail = ui.bytes(File.size(paths.squashfs)) + end + end + end +end diff --git a/lib/sigma/state.rb b/lib/sigma/state.rb new file mode 100644 index 0000000..33e4f02 --- /dev/null +++ b/lib/sigma/state.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "json" +require "fileutils" +require "securerandom" + +module Sigma + # Stamps recording what was built from what, as small JSON files under + # cache/state/. Keys look like "stage/rootfs" or "package/linux-sigma". + # + # Every stamp carries a `token` that changes whenever its producer runs; + # downstream stages record the token they were built against, which is how + # "rootfs ran again" makes squashfs stale even if rootfs's inputs didn't change. + class State + def initialize(dir) + @dir = dir + end + + def read(key) + path = path_for(key) + File.exist?(path) ? JSON.parse(File.read(path)) : nil + rescue JSON::ParserError + nil + end + + def write(key, data) + path = path_for(key) + FileUtils.mkdir_p(File.dirname(path)) + stamp = data.merge("token" => data["token"] || SecureRandom.hex(8), "finished_at" => Time.now.to_i) + File.write("#{path}.tmp", JSON.pretty_generate(stamp)) + File.rename("#{path}.tmp", path) + stamp + end + + def delete(key) + FileUtils.rm_f(path_for(key)) + end + + def keys(prefix) + Dir[File.join(@dir, prefix, "*.json")].map { |f| "#{prefix}/#{File.basename(f, ".json")}" } + end + + private + + def path_for(key) = File.join(@dir, "#{key}.json") + end +end diff --git a/lib/sigma/ui.rb b/lib/sigma/ui.rb new file mode 100644 index 0000000..b2838cb --- /dev/null +++ b/lib/sigma/ui.rb @@ -0,0 +1,352 @@ +# frozen_string_literal: true + +require "io/console" +require "monitor" + +module Sigma + # Everything the user sees goes through here: colours, glyphs, stage frames, + # live task lines and boxes. It adapts to where it is writing: + # + # * an interactive terminal gets colour, a spinner and a live tail of the + # running command's output on a single line; + # * verbose mode streams every output line under its task instead; + # * pipes and CI logs get plain, append-only lines plus a heartbeat so long + # steps don't look hung. + class UI + SPINNER = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze + HEARTBEAT = 60 # seconds between "still running" lines when not on a TTY + LABEL_WIDTH = 30 + + # [truecolor RGB, 256-colour index] + PALETTE = { + accent: [[0, 255, 200], 49], # Sigma teal, colour 14 of the VT palette + ok: [[80, 250, 160], 85], + warn: [[255, 200, 87], 221], + error: [[255, 95, 95], 203], + muted: [[112, 122, 135], 244], + text: [[222, 226, 232], 254] + }.freeze + + GLYPHS = { + ok: "✓", fail: "✗", skip: "○", warn: "!", run: "▸", info: "·", + frame_top: "┌", frame_mid: "│", frame_end: "└" + }.freeze + + attr_reader :verbose + + def initialize(out: $stdout, verbose: false, color: nil) + @out = out + @tty = out.tty? + @verbose = verbose + @depth = detect_color_depth(color) + @lock = Monitor.new + @in_stage = false + end + + def tty? = @tty + def color? = @depth.positive? + + # -- primitives ----------------------------------------------------------- + + def paint(text, role = nil, bold: false, dim: false) + return text.to_s if @depth.zero? || (role.nil? && !bold && !dim) + + codes = [] + codes << "1" if bold + codes << "2" if dim + if role + rgb, index = PALETTE.fetch(role) + codes << (@depth == 24 ? "38;2;#{rgb.join(";")}" : "38;5;#{index}") + end + "\e[#{codes.join(";")}m#{text}\e[0m" + end + + def say(line = "") + @lock.synchronize { @out.puts(line) } + end + + def blank = say + + def width + return 100 unless @tty + + IO.console&.winsize&.last.then { |w| w.nil? || w.zero? ? 100 : w } + rescue StandardError + 100 + end + + # -- page furniture --------------------------------------------------------- + + def banner(title, details = []) + blank + say " #{paint("Σ", :accent, bold: true)} #{paint(title, :text, bold: true)}" + say " #{details.map { |d| paint(d, :muted) }.join(paint(" · ", :muted))}" unless details.empty? + blank + end + + # A framed group of tasks, e.g. one pipeline stage: + # + # ┌ packages · local APK repository + # │ ✓ initrdbo 0.5.3-r0 up to date + # └ 1 package · 3s + def stage(name, description = nil) + say " #{frame(:frame_top)} #{paint(name, :accent, bold: true)}" \ + "#{description ? paint(" · #{description}", :muted) : ""}" + @in_stage = true + started = clock + summary = yield + @in_stage = false + say " #{frame(:frame_end)} #{paint([summary, duration(clock - started)].compact.join(" · "), :muted)}" + blank + rescue Exception # rubocop:disable Lint/RescueException -- close the frame on Ctrl-C too + @in_stage = false + say " #{frame(:frame_end)} #{paint("failed", :error)}" + blank + raise + end + + def stage_skipped(name, reason) + say " #{paint(GLYPHS[:skip], :muted)} #{paint(name, :accent)}#{paint(" · #{reason}", :muted)}" + blank + end + + # A single line inside a stage frame, for things that are not tasks. + def line(glyph, label, note = nil, role: :muted) + say "#{prefix}#{paint(GLYPHS.fetch(glyph), role)} #{pad(label)}#{note ? paint(note, :muted) : ""}" + end + + def note(text) = say("#{prefix} #{paint(text, :muted)}") + def warn(text) = say("#{prefix}#{paint(GLYPHS[:warn], :warn, bold: true)} #{paint(text, :warn)}") + + # Key/value listing, used by `sigma config` and `sigma status`. + def table(rows, indent: 2) + key_width = rows.map { |k, _| k.to_s.length }.max || 0 + rows.each do |key, value| + say "#{" " * indent}#{paint(key.to_s.ljust(key_width), :muted)} #{value}" + end + end + + # A rounded box. Lines are truncated to the terminal width. + def box(lines, title: nil, role: :accent) + inner = [width - 6, 40].max + content = lines.flat_map { |l| l.to_s.empty? ? [""] : l.to_s.split("\n", -1) } + body_width = [content.map { |l| visible_length(l) }.max.to_i, visible_length(title.to_s) + 2].max + body_width = body_width.clamp(20, inner) + + top = title ? "╭─ #{title} #{"─" * [body_width - visible_length(title) - 1, 0].max}╮" : "╭#{"─" * (body_width + 2)}╮" + say " #{paint(top, role)}" + content.each do |l| + l = truncate(l, body_width) + say " #{paint("│", role)} #{l}#{" " * (body_width - visible_length(l))} #{paint("│", role)}" + end + say " #{paint("╰#{"─" * (body_width + 2)}╯", role)}" + end + + # -- tasks ------------------------------------------------------------------ + + # Runs the block as a named task with a live status line, and returns the + # block's value. The block receives the Task, which commands write their + # output through (see Shell). Exceptions mark the task failed and re-raise. + def task(label, log: nil) + task = Task.new(self, label, log) + task.start + result = yield task + task.finish + result + rescue Exception => e # rubocop:disable Lint/RescueException -- show the ✗ on Ctrl-C too + task&.fail(e) + raise + end + + # -- formatting helpers (public: stages use them for notes) ------------------ + + def duration(seconds) + seconds = seconds.to_f + return format("%.1fs", seconds) if seconds < 10 + return "#{seconds.round}s" if seconds < 60 + + minutes, secs = seconds.round.divmod(60) + return format("%dm %02ds", minutes, secs) if minutes < 60 + + hours, minutes = minutes.divmod(60) + format("%dh %02dm", hours, minutes) + end + + def bytes(count) + units = %w[B KB MB GB TB] + value = count.to_f + unit = units.shift + while value >= 1024 && !units.empty? + value /= 1024 + unit = units.shift + end + unit == "B" ? "#{count} B" : format("%.1f %s", value, unit) + end + + def plural(count, word, plural_word = "#{word}s") + "#{count} #{count == 1 ? word : plural_word}" + end + + # -- internals used by Task ------------------------------------------------- + + def prefix + @in_stage ? " #{frame(:frame_mid)} " : " " + end + + def pad(label, width = LABEL_WIDTH) + label = label.to_s + visible_length(label) >= width ? "#{label} " : label + " " * (width - visible_length(label)) + end + + def visible_length(text) + text.to_s.gsub(/\e\[[0-9;?]*[A-Za-z]/, "").length + end + + # Word-wraps plain text (no escape codes) to +max+ columns. + def wrap(text, max) + text.to_s.split("\n").flat_map do |paragraph| + paragraph.split(/(?<=\s)/).each_with_object([+""]) do |word, lines| + lines << +"" if !lines.last.empty? && (lines.last + word.rstrip).length > max + lines.last << word + end.map(&:rstrip) + end + end + + def truncate(text, max) + return text if visible_length(text) <= max + + plain = text.gsub(/\e\[[0-9;?]*[A-Za-z]/, "") + "#{plain[0, [max - 1, 0].max]}…" + end + + def synchronize(&) = @lock.synchronize(&) + def raw(text) = @out.print(text) + def flush = @out.flush + + def clock = Process.clock_gettime(Process::CLOCK_MONOTONIC) + + private + + def frame(part) = paint(GLYPHS.fetch(part), :accent) + + def detect_color_depth(requested) + return 0 if requested == false + return 0 if ENV.key?("NO_COLOR") && !ENV["NO_COLOR"].empty? + return 0 unless @tty || ENV["FORCE_COLOR"] || requested + return 24 if ENV["COLORTERM"].to_s.match?(/truecolor|24bit/i) + + 256 + end + + # One running task: owns its status line and, when given, its log file. + class Task + attr_reader :label, :log + attr_accessor :detail + + def initialize(ui, label, log) + @ui = ui + @label = label + @log = log + @tail = nil + @detail = nil + @skipped = nil + end + + def start + @started = @ui.clock + if live? + @ui.synchronize { @ui.raw("\e[?25l") } # hide cursor while spinning + @spinner = Thread.new { spin } + else + @ui.say "#{@ui.prefix}#{@ui.paint(GLYPHS[:run], :accent)} #{@label}" + @heartbeat = Thread.new { beat } unless @ui.verbose + end + end + + # Called by Shell for every line a command prints. + def output(line) + @log&.puts(line) + clean = line.gsub(/\e\[[0-9;?]*[A-Za-z]/, "").strip + return if clean.empty? + + @tail = clean + @ui.say("#{@ui.prefix} #{@ui.paint("│", :muted)} #{@ui.paint(clean, :muted)}") if @ui.verbose + end + + # Mark the task as intentionally not done, with a reason ("up to date"). + def skip(reason) + @skipped = reason + end + + def elapsed = @ui.clock - @started + + # The most recent non-empty line of output, e.g. a command's summary. + def last_line = @tail + + def finish + stop_threads + if @skipped + render_final(@ui.paint(GLYPHS[:skip], :muted), @ui.paint(@skipped, :muted)) + else + note = [@detail, @ui.duration(elapsed)].compact.join(" ") + render_final(@ui.paint(GLYPHS[:ok], :ok, bold: true), @ui.paint(note, :muted)) + end + end + + def fail(error) + stop_threads + what = error.is_a?(Interrupt) ? "interrupted" : "failed" + render_final(@ui.paint(GLYPHS[:fail], :error, bold: true), + @ui.paint("#{what} after #{@ui.duration(elapsed)}", :error)) + end + + private + + def live? = @ui.tty? && !@ui.verbose + + def render_final(glyph, note) + @ui.synchronize do + @ui.raw("\r\e[2K\e[?25h") if live? + @ui.raw("#{@ui.prefix}#{glyph} #{@ui.pad(@label)}#{note}\n") + @ui.flush + end + end + + def spin + frame = 0 + loop do + render_live(SPINNER[frame % SPINNER.size]) + frame += 1 + sleep 0.08 + end + end + + def render_live(glyph) + head = "#{@ui.prefix}#{@ui.paint(glyph, :accent)} #{@ui.pad(@label)}" + meta = [@detail, @ui.duration(elapsed)].compact.join(" ") + line = "#{head}#{@ui.paint(meta, :muted)}" + room = @ui.width - @ui.visible_length(line) - 4 + line += " #{@ui.paint(@ui.truncate(@tail, room), :muted, dim: true)}" if @tail && room > 8 + @ui.synchronize do + @ui.raw("\r\e[2K#{line}") + @ui.flush + end + end + + def beat + loop do + sleep HEARTBEAT + @ui.say "#{@ui.prefix} #{@ui.paint("… #{@label}: still running (#{@ui.duration(elapsed)})", :muted)}" + end + end + + def stop_threads + [@spinner, @heartbeat].compact.each do |thread| + thread.kill + thread.join + end + @spinner = @heartbeat = nil + end + end + end +end diff --git a/src/apk/biblegen/APKBUILD b/packages/biblegen/APKBUILD similarity index 98% rename from src/apk/biblegen/APKBUILD rename to packages/biblegen/APKBUILD index 36d43dc..cfc254e 100644 --- a/src/apk/biblegen/APKBUILD +++ b/packages/biblegen/APKBUILD @@ -10,6 +10,7 @@ makedepends=" ruby ruby-dev ruby-bundler + openssl-dev " source="$pkgname-$pkgver.tar.gz::https://github.com/rdbo/biblegen/archive/refs/tags/$pkgver.tar.gz" diff --git a/src/apk/initrdbo/APKBUILD b/packages/initrdbo/APKBUILD similarity index 100% rename from src/apk/initrdbo/APKBUILD rename to packages/initrdbo/APKBUILD diff --git a/src/apk/initrdbo/initrdbo.trigger b/packages/initrdbo/initrdbo.trigger similarity index 100% rename from src/apk/initrdbo/initrdbo.trigger rename to packages/initrdbo/initrdbo.trigger diff --git a/src/apk/linux-sigma/APKBUILD b/packages/linux-sigma/APKBUILD similarity index 100% rename from src/apk/linux-sigma/APKBUILD rename to packages/linux-sigma/APKBUILD diff --git a/src/apk/linux-sigma/sigma.x86_64.config b/packages/linux-sigma/sigma.x86_64.config similarity index 100% rename from src/apk/linux-sigma/sigma.x86_64.config rename to packages/linux-sigma/sigma.x86_64.config diff --git a/src/apk/rdbo-archives/APKBUILD b/packages/rdbo-archives/APKBUILD similarity index 100% rename from src/apk/rdbo-archives/APKBUILD rename to packages/rdbo-archives/APKBUILD diff --git a/src/apk/rdbo-archives/archiver.rb b/packages/rdbo-archives/archiver.rb similarity index 100% rename from src/apk/rdbo-archives/archiver.rb rename to packages/rdbo-archives/archiver.rb diff --git a/src/apk/rdbo-archives/src/archive.rb b/packages/rdbo-archives/src/archive.rb similarity index 100% rename from src/apk/rdbo-archives/src/archive.rb rename to packages/rdbo-archives/src/archive.rb diff --git a/src/apk/rdbo-archives/src/archiver.rb b/packages/rdbo-archives/src/archiver.rb similarity index 100% rename from src/apk/rdbo-archives/src/archiver.rb rename to packages/rdbo-archives/src/archiver.rb diff --git a/src/apk/sigma-conf/APKBUILD b/packages/sigma-conf/APKBUILD similarity index 100% rename from src/apk/sigma-conf/APKBUILD rename to packages/sigma-conf/APKBUILD diff --git a/src/apk/sigma-conf/rootfs/etc/modules-load.d/sigma.conf b/packages/sigma-conf/rootfs/etc/modules-load.d/sigma.conf similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/modules-load.d/sigma.conf rename to packages/sigma-conf/rootfs/etc/modules-load.d/sigma.conf diff --git a/src/apk/sigma-conf/rootfs/etc/polkit-1/rules.d/99-network.rules b/packages/sigma-conf/rootfs/etc/polkit-1/rules.d/99-network.rules similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/polkit-1/rules.d/99-network.rules rename to packages/sigma-conf/rootfs/etc/polkit-1/rules.d/99-network.rules diff --git a/src/apk/sigma-conf/rootfs/etc/profile.d/xdg.sh b/packages/sigma-conf/rootfs/etc/profile.d/xdg.sh similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/profile.d/xdg.sh rename to packages/sigma-conf/rootfs/etc/profile.d/xdg.sh diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.Xresources b/packages/sigma-conf/rootfs/etc/skel/.Xresources similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.Xresources rename to packages/sigma-conf/rootfs/etc/skel/.Xresources diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.bashrc b/packages/sigma-conf/rootfs/etc/skel/.bashrc similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.bashrc rename to packages/sigma-conf/rootfs/etc/skel/.bashrc diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/autostart/sigma.desktop b/packages/sigma-conf/rootfs/etc/skel/.config/autostart/sigma.desktop similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/autostart/sigma.desktop rename to packages/sigma-conf/rootfs/etc/skel/.config/autostart/sigma.desktop diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/first_day_of_week b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/first_day_of_week similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/first_day_of_week rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/first_day_of_week diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/military_time b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/military_time similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/military_time rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAppletTime/v1/military_time diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_sink b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_sink similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_sink rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_sink diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_source b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_source similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_source rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicAudio/v1/amplification_source diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/all b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/all similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/all rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/all diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/same-on-all b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/same-on-all similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/same-on-all rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicBackground/v1/same-on-all diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile_behavior b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile_behavior similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile_behavior rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/autotile_behavior diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor_delay b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor_delay similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor_delay rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/focus_follows_cursor_delay diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/xkb_config b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/xkb_config similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/xkb_config rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicComp/v1/xkb_config diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor_gap b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor_gap similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor_gap rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/anchor_gap diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohide b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohide similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohide rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohide diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohover_delay_ms b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohover_delay_ms similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohover_delay_ms rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/autohover_delay_ms diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/background b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/background similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/background rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/background diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/border_radius b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/border_radius similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/border_radius rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/border_radius diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/exclusive_zone b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/exclusive_zone similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/exclusive_zone rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/exclusive_zone diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/expand_to_edges b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/expand_to_edges similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/expand_to_edges rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/expand_to_edges diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/keyboard_interactivity b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/keyboard_interactivity similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/keyboard_interactivity rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/keyboard_interactivity diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/layer b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/layer similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/layer rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/layer diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/margin b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/margin similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/margin rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/margin diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/name b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/name similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/name rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/name diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/opacity b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/opacity similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/opacity rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/opacity diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/output b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/output similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/output rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/output diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding_overlap b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding_overlap similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding_overlap rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/padding_overlap diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_center b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_center similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_center rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_center diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_wings b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_wings similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_wings rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/plugins_wings diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_center b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_center similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_center rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_center diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_wings b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_wings similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_wings rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/size_wings diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/spacing b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/spacing similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/spacing rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Dock/v1/spacing diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor_gap b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor_gap similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor_gap rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/anchor_gap diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohide b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohide similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohide rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohide diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohover_delay_ms b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohover_delay_ms similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohover_delay_ms rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/autohover_delay_ms diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/background b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/background similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/background rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/background diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/border_radius b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/border_radius similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/border_radius rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/border_radius diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/exclusive_zone b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/exclusive_zone similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/exclusive_zone rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/exclusive_zone diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/expand_to_edges b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/expand_to_edges similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/expand_to_edges rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/expand_to_edges diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/keyboard_interactivity b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/keyboard_interactivity similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/keyboard_interactivity rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/keyboard_interactivity diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/layer b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/layer similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/layer rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/layer diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/margin b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/margin similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/margin rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/margin diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/name b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/name similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/name rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/name diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/opacity b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/opacity similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/opacity rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/opacity diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/output b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/output similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/output rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/output diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding_overlap b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding_overlap similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding_overlap rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/padding_overlap diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_center b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_center similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_center rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_center diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_wings b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_wings similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_wings rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/plugins_wings diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_center b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_center similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_center rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_center diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_wings b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_wings similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_wings rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/size_wings diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/spacing b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/spacing similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/spacing rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel.Panel/v1/spacing diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel/v1/entries b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel/v1/entries similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel/v1/entries rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPanel/v1/entries diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPortal/v1/screenshot b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPortal/v1/screenshot similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPortal/v1/screenshot rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicPortal/v1/screenshot diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/custom diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/system_actions b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/system_actions similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/system_actions rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Shortcuts/v1/system_actions diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Wallpaper/v1/custom-images b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Wallpaper/v1/custom-images similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Wallpaper/v1/custom-images rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicSettings.Wallpaper/v1/custom-images diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/accent b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/accent similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/accent rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/accent diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/bg_color b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/bg_color similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/bg_color rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/bg_color diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/text_tint b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/text_tint similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/text_tint rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark.Builder/v1/text_tint diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent_button b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent_button similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent_button rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/accent_button diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/background b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/background similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/background rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/background diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/button b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/button similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/button rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/button diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive_button b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive_button similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive_button rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/destructive_button diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/icon_button b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/icon_button similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/icon_button rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/icon_button diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/link_button b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/link_button similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/link_button rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/link_button diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/palette b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/palette similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/palette rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/palette diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/primary b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/primary similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/primary rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/primary diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/secondary b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/secondary similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/secondary rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/secondary diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/success b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/success similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/success rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/success diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/text_button b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/text_button similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/text_button rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/text_button diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning_button b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning_button similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning_button rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTheme.Dark/v1/warning_button diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/interface_font b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/interface_font similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/interface_font rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/interface_font diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/monospace_font b/packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/monospace_font similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/monospace_font rename to packages/sigma-conf/rootfs/etc/skel/.config/cosmic/com.system76.CosmicTk/v1/monospace_font diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/foot/foot.ini b/packages/sigma-conf/rootfs/etc/skel/.config/foot/foot.ini similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/foot/foot.ini rename to packages/sigma-conf/rootfs/etc/skel/.config/foot/foot.ini diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/fuzzel/fuzzel.ini b/packages/sigma-conf/rootfs/etc/skel/.config/fuzzel/fuzzel.ini similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/fuzzel/fuzzel.ini rename to packages/sigma-conf/rootfs/etc/skel/.config/fuzzel/fuzzel.ini diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/gtk-3.0/settings.ini b/packages/sigma-conf/rootfs/etc/skel/.config/gtk-3.0/settings.ini similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/gtk-3.0/settings.ini rename to packages/sigma-conf/rootfs/etc/skel/.config/gtk-3.0/settings.ini diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/helix/config.toml b/packages/sigma-conf/rootfs/etc/skel/.config/helix/config.toml similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/helix/config.toml rename to packages/sigma-conf/rootfs/etc/skel/.config/helix/config.toml diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/helix/languages.toml b/packages/sigma-conf/rootfs/etc/skel/.config/helix/languages.toml similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/helix/languages.toml rename to packages/sigma-conf/rootfs/etc/skel/.config/helix/languages.toml diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/helix/runtime b/packages/sigma-conf/rootfs/etc/skel/.config/helix/runtime similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/helix/runtime rename to packages/sigma-conf/rootfs/etc/skel/.config/helix/runtime diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/helix/themes/sigma.toml b/packages/sigma-conf/rootfs/etc/skel/.config/helix/themes/sigma.toml similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/helix/themes/sigma.toml rename to packages/sigma-conf/rootfs/etc/skel/.config/helix/themes/sigma.toml diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/hypr/hyprland.conf b/packages/sigma-conf/rootfs/etc/skel/.config/hypr/hyprland.conf similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/hypr/hyprland.conf rename to packages/sigma-conf/rootfs/etc/skel/.config/hypr/hyprland.conf diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/keepassxc/keepassxc.ini b/packages/sigma-conf/rootfs/etc/skel/.config/keepassxc/keepassxc.ini similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/keepassxc/keepassxc.ini rename to packages/sigma-conf/rootfs/etc/skel/.config/keepassxc/keepassxc.ini diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/kitty/kitty.conf b/packages/sigma-conf/rootfs/etc/skel/.config/kitty/kitty.conf similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/kitty/kitty.conf rename to packages/sigma-conf/rootfs/etc/skel/.config/kitty/kitty.conf diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/luakit/userconf.lua b/packages/sigma-conf/rootfs/etc/skel/.config/luakit/userconf.lua similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/luakit/userconf.lua rename to packages/sigma-conf/rootfs/etc/skel/.config/luakit/userconf.lua diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/mako/config b/packages/sigma-conf/rootfs/etc/skel/.config/mako/config similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/mako/config rename to packages/sigma-conf/rootfs/etc/skel/.config/mako/config diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/mango/autostart.sh b/packages/sigma-conf/rootfs/etc/skel/.config/mango/autostart.sh similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/mango/autostart.sh rename to packages/sigma-conf/rootfs/etc/skel/.config/mango/autostart.sh diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/mango/config.conf b/packages/sigma-conf/rootfs/etc/skel/.config/mango/config.conf similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/mango/config.conf rename to packages/sigma-conf/rootfs/etc/skel/.config/mango/config.conf diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/neofetch/config.conf b/packages/sigma-conf/rootfs/etc/skel/.config/neofetch/config.conf similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/neofetch/config.conf rename to packages/sigma-conf/rootfs/etc/skel/.config/neofetch/config.conf diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/niri/config.kdl b/packages/sigma-conf/rootfs/etc/skel/.config/niri/config.kdl similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/niri/config.kdl rename to packages/sigma-conf/rootfs/etc/skel/.config/niri/config.kdl diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/rio/config.toml b/packages/sigma-conf/rootfs/etc/skel/.config/rio/config.toml similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/rio/config.toml rename to packages/sigma-conf/rootfs/etc/skel/.config/rio/config.toml diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/river/init b/packages/sigma-conf/rootfs/etc/skel/.config/river/init similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/river/init rename to packages/sigma-conf/rootfs/etc/skel/.config/river/init diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/sc-im/scimrc b/packages/sigma-conf/rootfs/etc/skel/.config/sc-im/scimrc similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/sc-im/scimrc rename to packages/sigma-conf/rootfs/etc/skel/.config/sc-im/scimrc diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/sigma/background b/packages/sigma-conf/rootfs/etc/skel/.config/sigma/background similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/sigma/background rename to packages/sigma-conf/rootfs/etc/skel/.config/sigma/background diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/sigma/startup.sh b/packages/sigma-conf/rootfs/etc/skel/.config/sigma/startup.sh similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/sigma/startup.sh rename to packages/sigma-conf/rootfs/etc/skel/.config/sigma/startup.sh diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/starship.toml b/packages/sigma-conf/rootfs/etc/skel/.config/starship.toml similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/starship.toml rename to packages/sigma-conf/rootfs/etc/skel/.config/starship.toml diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/swaylock/config b/packages/sigma-conf/rootfs/etc/skel/.config/swaylock/config similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/swaylock/config rename to packages/sigma-conf/rootfs/etc/skel/.config/swaylock/config diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/waybar/config.jsonc b/packages/sigma-conf/rootfs/etc/skel/.config/waybar/config.jsonc similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/waybar/config.jsonc rename to packages/sigma-conf/rootfs/etc/skel/.config/waybar/config.jsonc diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/waybar/style.css b/packages/sigma-conf/rootfs/etc/skel/.config/waybar/style.css similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/waybar/style.css rename to packages/sigma-conf/rootfs/etc/skel/.config/waybar/style.css diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/xfe/xferc b/packages/sigma-conf/rootfs/etc/skel/.config/xfe/xferc similarity index 99% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/xfe/xferc rename to packages/sigma-conf/rootfs/etc/skel/.config/xfe/xferc index faca848..4e16b80 100644 --- a/src/apk/sigma-conf/rootfs/etc/skel/.config/xfe/xferc +++ b/packages/sigma-conf/rootfs/etc/skel/.config/xfe/xferc @@ -127,7 +127,6 @@ ignore_case=1 sort_func=ascendingCase showthumbnails=0 name_size=200 -lastdir=/home/user hiddenfiles=0 [PROGS] @@ -155,7 +154,6 @@ ignore_case=1 sort_func=ascendingCase showthumbnails=0 name_size=19163 -lastdir=/home/user/Repos/typemaster hiddenfiles=0 [SETTINGS] diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.config/yambar/config.yml b/packages/sigma-conf/rootfs/etc/skel/.config/yambar/config.yml similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.config/yambar/config.yml rename to packages/sigma-conf/rootfs/etc/skel/.config/yambar/config.yml diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.gtkrc-2.0 b/packages/sigma-conf/rootfs/etc/skel/.gtkrc-2.0 similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.gtkrc-2.0 rename to packages/sigma-conf/rootfs/etc/skel/.gtkrc-2.0 diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.inputrc b/packages/sigma-conf/rootfs/etc/skel/.inputrc similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.inputrc rename to packages/sigma-conf/rootfs/etc/skel/.inputrc diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.mozilla/firefox/installs.ini b/packages/sigma-conf/rootfs/etc/skel/.mozilla/firefox/installs.ini similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.mozilla/firefox/installs.ini rename to packages/sigma-conf/rootfs/etc/skel/.mozilla/firefox/installs.ini diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.mozilla/firefox/profiles.ini b/packages/sigma-conf/rootfs/etc/skel/.mozilla/firefox/profiles.ini similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.mozilla/firefox/profiles.ini rename to packages/sigma-conf/rootfs/etc/skel/.mozilla/firefox/profiles.ini diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.mozilla/firefox/sigma.default-release/user.js b/packages/sigma-conf/rootfs/etc/skel/.mozilla/firefox/sigma.default-release/user.js similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.mozilla/firefox/sigma.default-release/user.js rename to packages/sigma-conf/rootfs/etc/skel/.mozilla/firefox/sigma.default-release/user.js diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.newsboat/config b/packages/sigma-conf/rootfs/etc/skel/.newsboat/config similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.newsboat/config rename to packages/sigma-conf/rootfs/etc/skel/.newsboat/config diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.profile b/packages/sigma-conf/rootfs/etc/skel/.profile similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.profile rename to packages/sigma-conf/rootfs/etc/skel/.profile diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.tmux.conf b/packages/sigma-conf/rootfs/etc/skel/.tmux.conf similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.tmux.conf rename to packages/sigma-conf/rootfs/etc/skel/.tmux.conf diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.vim/after/syntax/c.vim b/packages/sigma-conf/rootfs/etc/skel/.vim/after/syntax/c.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.vim/after/syntax/c.vim rename to packages/sigma-conf/rootfs/etc/skel/.vim/after/syntax/c.vim diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.vim/after/syntax/cpp.vim b/packages/sigma-conf/rootfs/etc/skel/.vim/after/syntax/cpp.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.vim/after/syntax/cpp.vim rename to packages/sigma-conf/rootfs/etc/skel/.vim/after/syntax/cpp.vim diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.vimrc b/packages/sigma-conf/rootfs/etc/skel/.vimrc similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.vimrc rename to packages/sigma-conf/rootfs/etc/skel/.vimrc diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.zlogin b/packages/sigma-conf/rootfs/etc/skel/.zlogin similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.zlogin rename to packages/sigma-conf/rootfs/etc/skel/.zlogin diff --git a/src/apk/sigma-conf/rootfs/etc/skel/.zshrc b/packages/sigma-conf/rootfs/etc/skel/.zshrc similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/skel/.zshrc rename to packages/sigma-conf/rootfs/etc/skel/.zshrc diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/LICENSE b/packages/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/LICENSE similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/LICENSE rename to packages/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/LICENSE diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/README.md b/packages/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/README.md similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/README.md rename to packages/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/README.md diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/colors/sigma.vim b/packages/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/colors/sigma.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/colors/sigma.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/themes/start/sigma/colors/sigma.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.gitignore b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.gitignore similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.gitignore rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.gitignore diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.vintrc.yaml b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.vintrc.yaml similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.vintrc.yaml rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/.vintrc.yaml diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/CHANGELOG.md b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/CHANGELOG.md similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/CHANGELOG.md rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/CHANGELOG.md diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/LICENCE b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/LICENCE similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/LICENCE rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/LICENCE diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/README.markdown b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/README.markdown similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/README.markdown rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/README.markdown diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/_config.yml b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/_config.yml similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/_config.yml rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/_config.yml diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree/ui_glue.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree/ui_glue.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree/ui_glue.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/autoload/nerdtree/ui_glue.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/doc/NERDTree.txt b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/doc/NERDTree.txt similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/doc/NERDTree.txt rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/doc/NERDTree.txt diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/bookmark.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/bookmark.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/bookmark.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/bookmark.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/creator.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/creator.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/creator.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/creator.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/event.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/event.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/event.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/event.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/flag_set.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/flag_set.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/flag_set.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/flag_set.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/key_map.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/key_map.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/key_map.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/key_map.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_controller.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_controller.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_controller.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_controller.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_item.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_item.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_item.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/menu_item.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/nerdtree.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/nerdtree.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/nerdtree.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/nerdtree.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/notifier.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/notifier.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/notifier.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/notifier.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/opener.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/opener.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/opener.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/opener.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/path.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/path.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/path.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/path.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_dir_node.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_dir_node.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_dir_node.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_dir_node.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_file_node.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_file_node.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_file_node.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/tree_file_node.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/ui.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/ui.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/ui.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/lib/nerdtree/ui.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/exec_menuitem.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/exec_menuitem.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/exec_menuitem.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/exec_menuitem.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/fs_menu.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/fs_menu.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/fs_menu.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/fs_menu.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/vcs.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/vcs.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/vcs.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/nerdtree_plugin/vcs.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/plugin/NERD_tree.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/plugin/NERD_tree.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/plugin/NERD_tree.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/plugin/NERD_tree.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/screenshot.png b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/screenshot.png similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/screenshot.png rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/screenshot.png diff --git a/src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/syntax/nerdtree.vim b/packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/syntax/nerdtree.vim similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/syntax/nerdtree.vim rename to packages/sigma-conf/rootfs/etc/vim/pack/vendor/start/nerdtree/syntax/nerdtree.vim diff --git a/src/apk/sigma-conf/rootfs/etc/vim/vimrc.local b/packages/sigma-conf/rootfs/etc/vim/vimrc.local similarity index 100% rename from src/apk/sigma-conf/rootfs/etc/vim/vimrc.local rename to packages/sigma-conf/rootfs/etc/vim/vimrc.local diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-audio b/packages/sigma-conf/rootfs/usr/bin/sigma-audio similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-audio rename to packages/sigma-conf/rootfs/usr/bin/sigma-audio diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-battery b/packages/sigma-conf/rootfs/usr/bin/sigma-battery similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-battery rename to packages/sigma-conf/rootfs/usr/bin/sigma-battery diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-change-kbdlayout b/packages/sigma-conf/rootfs/usr/bin/sigma-change-kbdlayout similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-change-kbdlayout rename to packages/sigma-conf/rootfs/usr/bin/sigma-change-kbdlayout diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-change-resolution b/packages/sigma-conf/rootfs/usr/bin/sigma-change-resolution similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-change-resolution rename to packages/sigma-conf/rootfs/usr/bin/sigma-change-resolution diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-cipher b/packages/sigma-conf/rootfs/usr/bin/sigma-cipher similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-cipher rename to packages/sigma-conf/rootfs/usr/bin/sigma-cipher diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-common b/packages/sigma-conf/rootfs/usr/bin/sigma-common similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-common rename to packages/sigma-conf/rootfs/usr/bin/sigma-common diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-cpu b/packages/sigma-conf/rootfs/usr/bin/sigma-cpu similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-cpu rename to packages/sigma-conf/rootfs/usr/bin/sigma-cpu diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-disk b/packages/sigma-conf/rootfs/usr/bin/sigma-disk similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-disk rename to packages/sigma-conf/rootfs/usr/bin/sigma-disk diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-dmenu b/packages/sigma-conf/rootfs/usr/bin/sigma-dmenu similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-dmenu rename to packages/sigma-conf/rootfs/usr/bin/sigma-dmenu diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-filemgr b/packages/sigma-conf/rootfs/usr/bin/sigma-filemgr similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-filemgr rename to packages/sigma-conf/rootfs/usr/bin/sigma-filemgr diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-launcher b/packages/sigma-conf/rootfs/usr/bin/sigma-launcher similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-launcher rename to packages/sigma-conf/rootfs/usr/bin/sigma-launcher diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-lock-screen b/packages/sigma-conf/rootfs/usr/bin/sigma-lock-screen similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-lock-screen rename to packages/sigma-conf/rootfs/usr/bin/sigma-lock-screen diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-memory b/packages/sigma-conf/rootfs/usr/bin/sigma-memory similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-memory rename to packages/sigma-conf/rootfs/usr/bin/sigma-memory diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-nwif b/packages/sigma-conf/rootfs/usr/bin/sigma-nwif similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-nwif rename to packages/sigma-conf/rootfs/usr/bin/sigma-nwif diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-nwusage b/packages/sigma-conf/rootfs/usr/bin/sigma-nwusage similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-nwusage rename to packages/sigma-conf/rootfs/usr/bin/sigma-nwusage diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-power-menu b/packages/sigma-conf/rootfs/usr/bin/sigma-power-menu similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-power-menu rename to packages/sigma-conf/rootfs/usr/bin/sigma-power-menu diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-screenshot b/packages/sigma-conf/rootfs/usr/bin/sigma-screenshot similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-screenshot rename to packages/sigma-conf/rootfs/usr/bin/sigma-screenshot diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-startup b/packages/sigma-conf/rootfs/usr/bin/sigma-startup similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-startup rename to packages/sigma-conf/rootfs/usr/bin/sigma-startup diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-term b/packages/sigma-conf/rootfs/usr/bin/sigma-term similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-term rename to packages/sigma-conf/rootfs/usr/bin/sigma-term diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-toggle-vkbd b/packages/sigma-conf/rootfs/usr/bin/sigma-toggle-vkbd similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-toggle-vkbd rename to packages/sigma-conf/rootfs/usr/bin/sigma-toggle-vkbd diff --git a/src/apk/sigma-conf/rootfs/usr/bin/sigma-vkbd b/packages/sigma-conf/rootfs/usr/bin/sigma-vkbd similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/bin/sigma-vkbd rename to packages/sigma-conf/rootfs/usr/bin/sigma-vkbd diff --git a/src/apk/sigma-conf/rootfs/usr/sbin/setup-sigma b/packages/sigma-conf/rootfs/usr/sbin/setup-sigma similarity index 86% rename from src/apk/sigma-conf/rootfs/usr/sbin/setup-sigma rename to packages/sigma-conf/rootfs/usr/sbin/setup-sigma index 89d9ae1..c3e9d8f 100755 --- a/src/apk/sigma-conf/rootfs/usr/sbin/setup-sigma +++ b/packages/sigma-conf/rootfs/usr/sbin/setup-sigma @@ -132,21 +132,19 @@ echo "Copying files..." cp -r -p /live/. "$newroot/" echo "Mounting pseudo filesystems on new root..." -mkdir -p "$newroot/dev" "$newroot/proc" -mount -t devtmpfs none "$newroot/dev" +mkdir -p "$newroot/dev" "$newroot/proc" "$newroot/sys" +# The live /dev itself, not a fresh devtmpfs: grub-mkconfig resolves the root +# device, which on encrypted installs is the /dev/mapper node cryptsetup made here. +mount --rbind /dev "$newroot/dev" mount -t proc proc "$newroot/proc" +mount -t sysfs sys "$newroot/sys" # grub-probe looks through device-mapper (LUKS) here echo "Setting up boot..." -arch="$(uname -m)" -chroot "$newroot" grub-install --target=x86_64-efi --boot-directory=/boot --efi-directory=/boot/efi --removable "$disk" -cp /cdrom/boot/grub/grub.cfg /cdrom/boot/grub/wallpaper.png "$newroot/boot/grub/" -# Patch GRUB config -sed -i -E 's|/boot||g' "$newroot/boot/grub/grub.cfg" -root_findfs="$(get_dev_id "$root_partition")" -sed -i -E "s|root=[^[:space:]]+|root=$root_findfs|" "$newroot/boot/grub/grub.cfg" -sed -i -E "s|rootfstype=[^[:space:]]+|rootfstype=ext4|" "$newroot/boot/grub/grub.cfg" -sed -i -E 's|[[:space:]]*rd\.live=[^[:space:]]+||' "$newroot/boot/grub/grub.cfg" cp /cdrom/boot/vmlinuz* /cdrom/boot/config* /cdrom/boot/initramfs* /cdrom/boot/System.map* "$newroot/boot/" +chroot "$newroot" grub-install --target=x86_64-efi --boot-directory=/boot --efi-directory=/boot/efi --removable "$disk" +# The menu is generated by grub-mkconfig and themed through /etc/default/grub, +# so the regeneration apk triggers on every kernel update keeps it intact. +chroot "$newroot" sigma-grub-setup --root "$root_partition" --fstype ext4 echo "Setting up /etc/fstab..." boot_id="$(get_dev_id "$boot_partition")" @@ -159,8 +157,9 @@ $efi_id /boot/efi vfat defaults 0 1 EOF echo "Unmounting partitions..." +umount "$newroot/sys" umount "$newroot/proc" -umount "$newroot/dev" +umount -R "$newroot/dev" umount "$newroot/boot/efi" umount "$newroot/boot" umount "$newroot" diff --git a/packages/sigma-conf/rootfs/usr/sbin/sigma-grub-setup b/packages/sigma-conf/rootfs/usr/sbin/sigma-grub-setup new file mode 100755 index 0000000..5824427 --- /dev/null +++ b/packages/sigma-conf/rootfs/usr/sbin/sigma-grub-setup @@ -0,0 +1,134 @@ +#!/bin/sh + +# sigma-grub-setup: make grub-mkconfig produce the Sigma Linux boot menu. +# +# Writes /etc/default/grub so that every grub-mkconfig run, including the one +# apk triggers after each kernel update, keeps the Sigma theme and the kernel +# parameters initrdbo needs. Safe to re-run. It also migrates older installs, +# whose grub.cfg was copied from the ISO and got overwritten on kernel updates. +# +# usage: sigma-grub-setup [--root DEVICE --fstype TYPE] [--no-update] +# +# --root DEVICE partition holding the root filesystem (the LUKS container +# on encrypted installs); detected from / when omitted +# --fstype TYPE filesystem of the root (default: detected from /) +# --no-update write the configuration but don't run update-grub + +set -e + +THEME=/usr/share/sigma/grub +DEFAULTS=/etc/default/grub +MARKER="# Managed by sigma-grub-setup." +OVERRIDES="# Local overrides (kept when sigma-grub-setup runs again):" + +die() { + echo "sigma-grub-setup: $*" >&2 + exit 1 +} + +usage() { + sed -n '/^# usage:/,/^$/s/^# \{0,1\}//p' "$0" +} + +# The device and filesystem type mounted at /. The last entry wins: the +# initramfs's rootfs can be listed before the real root. +mounted_root() { + awk '$2 == "/" { dev = $1; fs = $3 } END { print dev, fs }' /proc/mounts +} + +# initrdbo opens LUKS containers itself, so root= must name the partition +# under a device-mapper device, not the mapped filesystem. +backing_device() { + dev="$(readlink -f "$1")" + for slave in "/sys/block/${dev##*/}/slaves/"*; do + if [ -e "$slave" ]; then + echo "/dev/${slave##*/}" + return + fi + done + echo "$dev" +} + +# Busybox blkid has no -s/-o, so pick UUID="..." out of its output. +device_uuid() { + blkid "$1" | sed -n 's/.* UUID="\([^"]*\)".*/\1/p' +} + +root_dev="" +fstype="" +update=yes +while [ $# -gt 0 ]; do + case "$1" in + --root) root_dev="$2"; shift 2 ;; + --fstype) fstype="$2"; shift 2 ;; + --no-update) update=""; shift ;; + -h|--help) usage; exit 0 ;; + *) die "unknown option: $1 (see --help)" ;; + esac +done + +[ "$(id -u)" = 0 ] || die "run as root" +[ -r "$THEME/grub.conf" ] || die "$THEME/grub.conf is missing; is sigma-conf installed?" + +if [ -z "$root_dev" ]; then + set -- $(mounted_root) + root_dev="$1" + [ -n "$fstype" ] || fstype="$2" +fi +[ -n "$fstype" ] || die "--fstype is required with --root" +root_dev="$(backing_device "$root_dev")" +[ -b "$root_dev" ] || die "$root_dev is not a block device" +uuid="$(device_uuid "$root_dev")" +[ -n "$uuid" ] || die "could not read the UUID of $root_dev" + +# A hand-written /etc/default/grub is kept aside, once. +if [ -f "$DEFAULTS" ] && ! grep -qF "$MARKER" "$DEFAULTS"; then + cp "$DEFAULTS" "$DEFAULTS.pre-sigma" + echo "Saved the previous $DEFAULTS as $DEFAULTS.pre-sigma" +fi + +# Whatever was added below the overrides line survives a rewrite. +overrides="" +if [ -f "$DEFAULTS" ] && grep -qF "$OVERRIDES" "$DEFAULTS"; then + overrides="$(sed "1,/^$OVERRIDES\$/d" "$DEFAULTS")" +fi + +mkdir -p "${DEFAULTS%/*}" +cat > "$DEFAULTS.new" <<- EOF + $MARKER Running it again rewrites everything + # above the "Local overrides" line and keeps everything below it. + + # The Sigma Linux theme and kernel parameters, shared with the live ISO. + [ -r $THEME/grub.conf ] && . $THEME/grub.conf + + # initrdbo mounts the root filesystem itself: root= must name the partition + # (on encrypted installs, the LUKS container it unlocks), and rootfstype= + # must say how to mount it. + GRUB_DEVICE_UUID="$uuid" + GRUB_CMDLINE_LINUX="rootfstype=$fstype" + + $OVERRIDES +EOF +[ -z "$overrides" ] || printf '%s\n' "$overrides" >> "$DEFAULTS.new" +mv "$DEFAULTS.new" "$DEFAULTS" + +# GRUB reads the wallpaper before any kernel runs, so it has to live on /boot +# (which may be its own unencrypted partition). +mkdir -p /boot/grub +cp "$THEME/wallpaper.png" /boot/grub/wallpaper.png + +# Older installs carry files meant to stop apk's grub trigger: one under a +# name the trigger never reads (upgrade-grub.conf), so it never worked. The +# trigger is wanted now; it is what keeps the menu current. +if [ -f /etc/upgrade-grub.conf ] && ! grep -qv '^disable_trigger=' /etc/upgrade-grub.conf; then + rm -f /etc/upgrade-grub.conf +fi +if [ -f /etc/update-grub.conf ]; then + sed -i '/^disable_trigger=/d' /etc/update-grub.conf + [ -s /etc/update-grub.conf ] || rm -f /etc/update-grub.conf +fi + +echo "Configured GRUB: root=UUID=$uuid ($root_dev), rootfstype=$fstype" +if [ -n "$update" ]; then + update-grub +fi diff --git a/src/apk/sigma-conf/rootfs/usr/share/icons/default/index.theme b/packages/sigma-conf/rootfs/usr/share/icons/default/index.theme similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/icons/default/index.theme rename to packages/sigma-conf/rootfs/usr/share/icons/default/index.theme diff --git a/src/apk/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/image.png b/packages/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/image.png similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/image.png rename to packages/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/image.png diff --git a/src/apk/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.plymouth b/packages/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.plymouth similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.plymouth rename to packages/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.plymouth diff --git a/src/apk/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.script b/packages/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.script similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.script rename to packages/sigma-conf/rootfs/usr/share/plymouth/themes/sigma/sigma.script diff --git a/src/apk/sigma-conf/rootfs/usr/share/sigma/ascii/sigma b/packages/sigma-conf/rootfs/usr/share/sigma/ascii/sigma similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/sigma/ascii/sigma rename to packages/sigma-conf/rootfs/usr/share/sigma/ascii/sigma diff --git a/src/apk/sigma-conf/rootfs/usr/share/sigma/ascii/sigma-linux-logo b/packages/sigma-conf/rootfs/usr/share/sigma/ascii/sigma-linux-logo similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/sigma/ascii/sigma-linux-logo rename to packages/sigma-conf/rootfs/usr/share/sigma/ascii/sigma-linux-logo diff --git a/src/apk/sigma-conf/rootfs/usr/share/sigma/backgrounds/wallpaper.png b/packages/sigma-conf/rootfs/usr/share/sigma/backgrounds/wallpaper.png similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/sigma/backgrounds/wallpaper.png rename to packages/sigma-conf/rootfs/usr/share/sigma/backgrounds/wallpaper.png diff --git a/packages/sigma-conf/rootfs/usr/share/sigma/grub/grub.conf b/packages/sigma-conf/rootfs/usr/share/sigma/grub/grub.conf new file mode 100644 index 0000000..61456d8 --- /dev/null +++ b/packages/sigma-conf/rootfs/usr/share/sigma/grub/grub.conf @@ -0,0 +1,34 @@ +# Sigma Linux boot menu: theme and kernel parameters. +# +# One source of truth for both boot menus: +# * the live ISO's grub.cfg is rendered from it at build time; +# * installed systems source it from /etc/default/grub (written by +# sigma-grub-setup), so every grub-mkconfig run, including the one apk +# triggers on kernel updates, keeps the Sigma look. +# +# Keep to plain KEY="value" lines: the ISO build reads this file without a +# shell. Per-machine overrides belong in /etc/default/grub, after the line +# that sources this file. + +GRUB_DISTRIBUTOR="Sigma Linux" +GRUB_TIMEOUT=5 +GRUB_GFXMODE=640x480 +GRUB_TERMINAL_OUTPUT=gfxterm +GRUB_FONT=/boot/grub/fonts/unicode.pf2 +GRUB_BACKGROUND=/boot/grub/wallpaper.png +GRUB_DISABLE_RECOVERY=true + +# The Sigma kernel is the plain "Sigma Linux" entry; any other installed +# kernels go under "Advanced options for Sigma Linux". +GRUB_TOP_LEVEL=/boot/vmlinuz-sigma + +# vt.*: the console colour palette, one value per colour 0-15. +# Linux's defaults, for reference: +# red: 0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff +# grn: 0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff +# blu: 0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff +# The table is not in ANSI order. From drivers/tty/vt/vt.c: +# const unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7, +# 8,12,10,14, 9,13,11,15 }; +# so colour "3" takes the RGB values at index 6, for example. +GRUB_CMDLINE_LINUX_DEFAULT="vt.color=0x0B vt.default_red=0x10,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,0x55,0xff,0x55,0xff,0x55,0xff,0x00,0xff vt.default_grn=0x1a,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff vt.default_blu=0x20,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,0x55,0x55,0x55,0x55,0xff,0xff,0xc8,0xff splash" diff --git a/src/grub_wallpaper.png b/packages/sigma-conf/rootfs/usr/share/sigma/grub/wallpaper.png similarity index 100% rename from src/grub_wallpaper.png rename to packages/sigma-conf/rootfs/usr/share/sigma/grub/wallpaper.png diff --git a/src/apk/sigma-conf/rootfs/usr/share/sigma/lore.cipher b/packages/sigma-conf/rootfs/usr/share/sigma/lore.cipher similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/sigma/lore.cipher rename to packages/sigma-conf/rootfs/usr/share/sigma/lore.cipher diff --git a/src/apk/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/index.html b/packages/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/index.html similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/index.html rename to packages/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/index.html diff --git a/src/apk/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/sigma-logo-min.svg b/packages/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/sigma-logo-min.svg similarity index 100% rename from src/apk/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/sigma-logo-min.svg rename to packages/sigma-conf/rootfs/usr/share/sigma/luakit-welcome/sigma-logo-min.svg diff --git a/src/apk/sigma-dwl/APKBUILD b/packages/sigma-dwl/APKBUILD similarity index 100% rename from src/apk/sigma-dwl/APKBUILD rename to packages/sigma-dwl/APKBUILD diff --git a/src/apk/sigma-dwl/config.h b/packages/sigma-dwl/config.h similarity index 100% rename from src/apk/sigma-dwl/config.h rename to packages/sigma-dwl/config.h diff --git a/src/apk/sigma-firacode-nerd/APKBUILD b/packages/sigma-firacode-nerd/APKBUILD similarity index 100% rename from src/apk/sigma-firacode-nerd/APKBUILD rename to packages/sigma-firacode-nerd/APKBUILD diff --git a/src/apk/sigma-firacode-nerd/firacode.tar.xz b/packages/sigma-firacode-nerd/firacode.tar.xz similarity index 100% rename from src/apk/sigma-firacode-nerd/firacode.tar.xz rename to packages/sigma-firacode-nerd/firacode.tar.xz diff --git a/src/apk/sigma-flat-remix-gtk/APKBUILD b/packages/sigma-flat-remix-gtk/APKBUILD similarity index 100% rename from src/apk/sigma-flat-remix-gtk/APKBUILD rename to packages/sigma-flat-remix-gtk/APKBUILD diff --git a/src/apk/sigma-flat-remix-gtk/flat-remix-gtk-green-dark.tar.xz b/packages/sigma-flat-remix-gtk/flat-remix-gtk-green-dark.tar.xz similarity index 100% rename from src/apk/sigma-flat-remix-gtk/flat-remix-gtk-green-dark.tar.xz rename to packages/sigma-flat-remix-gtk/flat-remix-gtk-green-dark.tar.xz diff --git a/src/apk/sigma-material-symbols/APKBUILD b/packages/sigma-material-symbols/APKBUILD similarity index 100% rename from src/apk/sigma-material-symbols/APKBUILD rename to packages/sigma-material-symbols/APKBUILD diff --git a/src/apk/sigma-material-symbols/material-symbols.tar.xz b/packages/sigma-material-symbols/material-symbols.tar.xz similarity index 100% rename from src/apk/sigma-material-symbols/material-symbols.tar.xz rename to packages/sigma-material-symbols/material-symbols.tar.xz diff --git a/src/apk/sigma-sent/APKBUILD b/packages/sigma-sent/APKBUILD similarity index 100% rename from src/apk/sigma-sent/APKBUILD rename to packages/sigma-sent/APKBUILD diff --git a/src/apk/sigma-sent/sent/LICENSE b/packages/sigma-sent/sent/LICENSE similarity index 100% rename from src/apk/sigma-sent/sent/LICENSE rename to packages/sigma-sent/sent/LICENSE diff --git a/src/apk/sigma-sent/sent/Makefile b/packages/sigma-sent/sent/Makefile similarity index 100% rename from src/apk/sigma-sent/sent/Makefile rename to packages/sigma-sent/sent/Makefile diff --git a/src/apk/sigma-sent/sent/README.md b/packages/sigma-sent/sent/README.md similarity index 100% rename from src/apk/sigma-sent/sent/README.md rename to packages/sigma-sent/sent/README.md diff --git a/src/apk/sigma-sent/sent/arg.h b/packages/sigma-sent/sent/arg.h similarity index 100% rename from src/apk/sigma-sent/sent/arg.h rename to packages/sigma-sent/sent/arg.h diff --git a/src/apk/sigma-sent/sent/config.def.h b/packages/sigma-sent/sent/config.def.h similarity index 100% rename from src/apk/sigma-sent/sent/config.def.h rename to packages/sigma-sent/sent/config.def.h diff --git a/src/apk/sigma-sent/sent/config.h b/packages/sigma-sent/sent/config.h similarity index 100% rename from src/apk/sigma-sent/sent/config.h rename to packages/sigma-sent/sent/config.h diff --git a/src/apk/sigma-sent/sent/config.mk b/packages/sigma-sent/sent/config.mk similarity index 100% rename from src/apk/sigma-sent/sent/config.mk rename to packages/sigma-sent/sent/config.mk diff --git a/src/apk/sigma-sent/sent/drw.c b/packages/sigma-sent/sent/drw.c similarity index 100% rename from src/apk/sigma-sent/sent/drw.c rename to packages/sigma-sent/sent/drw.c diff --git a/src/apk/sigma-sent/sent/drw.h b/packages/sigma-sent/sent/drw.h similarity index 100% rename from src/apk/sigma-sent/sent/drw.h rename to packages/sigma-sent/sent/drw.h diff --git a/src/apk/sigma-sent/sent/example b/packages/sigma-sent/sent/example similarity index 100% rename from src/apk/sigma-sent/sent/example rename to packages/sigma-sent/sent/example diff --git a/src/apk/sigma-sent/sent/nyan.png b/packages/sigma-sent/sent/nyan.png similarity index 100% rename from src/apk/sigma-sent/sent/nyan.png rename to packages/sigma-sent/sent/nyan.png diff --git a/src/apk/sigma-sent/sent/sent.1 b/packages/sigma-sent/sent/sent.1 similarity index 100% rename from src/apk/sigma-sent/sent/sent.1 rename to packages/sigma-sent/sent/sent.1 diff --git a/src/apk/sigma-sent/sent/sent.c b/packages/sigma-sent/sent/sent.c similarity index 100% rename from src/apk/sigma-sent/sent/sent.c rename to packages/sigma-sent/sent/sent.c diff --git a/src/apk/sigma-sent/sent/transparent_test.ff b/packages/sigma-sent/sent/transparent_test.ff similarity index 100% rename from src/apk/sigma-sent/sent/transparent_test.ff rename to packages/sigma-sent/sent/transparent_test.ff diff --git a/src/apk/sigma-sent/sent/util.c b/packages/sigma-sent/sent/util.c similarity index 100% rename from src/apk/sigma-sent/sent/util.c rename to packages/sigma-sent/sent/util.c diff --git a/src/apk/sigma-sent/sent/util.h b/packages/sigma-sent/sent/util.h similarity index 100% rename from src/apk/sigma-sent/sent/util.h rename to packages/sigma-sent/sent/util.h diff --git a/src/apk/sigma-st/APKBUILD b/packages/sigma-st/APKBUILD similarity index 100% rename from src/apk/sigma-st/APKBUILD rename to packages/sigma-st/APKBUILD diff --git a/src/apk/sigma-vt323/APKBUILD b/packages/sigma-vt323/APKBUILD similarity index 100% rename from src/apk/sigma-vt323/APKBUILD rename to packages/sigma-vt323/APKBUILD diff --git a/src/apk/sigma-vt323/VT323.tar.xz b/packages/sigma-vt323/VT323.tar.xz similarity index 100% rename from src/apk/sigma-vt323/VT323.tar.xz rename to packages/sigma-vt323/VT323.tar.xz diff --git a/src/apk/sigma-wvkbd/APKBUILD b/packages/sigma-wvkbd/APKBUILD similarity index 100% rename from src/apk/sigma-wvkbd/APKBUILD rename to packages/sigma-wvkbd/APKBUILD diff --git a/src/apk/simplex-chat/APKBUILD b/packages/simplex-chat/APKBUILD similarity index 100% rename from src/apk/simplex-chat/APKBUILD rename to packages/simplex-chat/APKBUILD diff --git a/profiles/full.list b/profiles/full.list new file mode 100644 index 0000000..8b5a222 --- /dev/null +++ b/profiles/full.list @@ -0,0 +1,73 @@ +# full: standard plus office, creative, virtualization and security tools +# +# Syntax: one or more packages per line, # starts a comment, +# `@include ` pulls in another profile, `-pattern` removes +# packages (globs allowed) added before it. + +@include standard + +# All firmware instead of the curated subset +-linux-firmware-* +linux-firmware + +# Every tree-sitter grammar instead of helix's vendored set +-helix-tree-sitter-vendor + +# System + +## Administration Tools +yt-dlp +lighttpd + +#--------------------------------# + +# Desktop Environment + +## Text Editing +tree-sitter-grammars + +## Office +zathura zathura-pdf-mupdf +libreoffice libreoffice-gtk + +## Mail +neomutt +thunderbird + +## RSS +newsboat + +## Multimedia +gimp +inkscape +shotcut +lmms + +## Chat +simplex-chat + +## Monitoring +wireshark tshark + +## Misc +electrum +qemu-ui-gtk qemu-system-x86_64 qemu-system-aarch64 ovmf + +#--------------------------------# + +# 1337 h@xx0r + +aircrack-ng +nmap nmap-ncat +rustscan +ghidra +zaproxy +mitmproxy +hashcat +john +hydra +radare2 +sqlmap +recon-ng +bettercap +jadx apktool diff --git a/src/pkglist.standard b/profiles/minimal.list similarity index 69% rename from src/pkglist.standard rename to profiles/minimal.list index 7273d15..6446bab 100644 --- a/src/pkglist.standard +++ b/profiles/minimal.list @@ -1,3 +1,9 @@ +# minimal: the base system and desktop, nothing extra +# +# Syntax: one or more packages per line, # starts a comment, +# `@include ` pulls in another profile, `-pattern` removes +# packages (globs allowed) added before it. + # Core Packages ## Core @@ -71,32 +77,11 @@ linux-firmware-mwlwifi ## Administration Tools bash starship doas -openssh -curl wget -gpg -patch entr -7zip xfsprogs e2fsprogs btrfs-progs dosfstools ntfs-3g -# yt-dlp tar xz zstd zip unzip gzip -# lighttpd -rsync -diffutils -tmux tmux-doc -jq -ripgrep wireless-tools -btop -bandwhich -timeshift -flatpak -bubblewrap -bc cpupower polkit -argon2 -delta -just ## Suspend/Hibernate acpid zzz @@ -108,7 +93,6 @@ earlyoom zram-init wpa_supplicant dhcpcd iwd networkmanager networkmanager-wifi networkmanager-wwan networkmanager-dnsmasq networkmanager-cli networkmanager-tui -openvpn wireguard-tools ## Audio pipewire pipewire-pulse pipewire-alsa pipewire-jack @@ -118,9 +102,6 @@ easyeffects calf-lv2 lsp-plugins-lv2 mda-lv2 zam-plugins-lv2 ## Bluetooth bluez -## Docs -mandoc man-pages - ## Configuration sigma-conf biblegen @@ -137,11 +118,10 @@ xwayland xwayland-satellite seatd eudev xrdb ## Fonts sigma-firacode-nerd sigma-vt323 sigma-material-symbols font-ubuntu-nerd -font-noto-all ## Desktop # river river-doc # compositor -# hyprland hyprland-doc +# hyprland # hyprland-doc mangowc # niri waybar waybar-doc # status bar @@ -156,12 +136,11 @@ swaybg # wallpaper daemon foot ## Text Editing -vim helix helix-tree-sitter-vendor +vim ## Browser # luakit luakit-doc # firefox -librewolf ## Notifications mako @@ -171,48 +150,11 @@ libnotify grim slurp -## File Managent -yazi dragon-drop - ## Office -sc-im sigma-sent -groff -poppler poppler-utils -# zathura zathura-pdf-mupdf -# libreoffice libreoffice-gtk - -## Mail -# neomutt -# thunderbird - -## RSS -# newsboat - -## Multimedia -mpv -ffmpeg -imagemagick -nsxiv -# gimp -# inkscape -wf-recorder -# shotcut -# lmms - -## Password Management -keepassxc pass - -## Chat -weechat -# simplex-chat - -## Monitoring -# wireshark tshark ## System Resources lm-sensors -hardinfo2 pciutils lscpu @@ -222,13 +164,7 @@ wlr-randr dbus-x11 sigma-wvkbd neofetch -lxappearance -zenity setxkbmap -# electrum -cava -wev xev -# qemu-ui-gtk qemu-system-x86_64 qemu-system-aarch64 ovmf ## Themes sigma-flat-remix-gtk @@ -251,54 +187,12 @@ mesa-utils # Development -## Compilers/Interpreters -ruby ruby-doc ruby-rdoc ruby-full ruby-dev -python3 py3-pip py3-virtualenv -gcc g++ valgrind -perl -openjdk25 -rust cargo -go - -## Linkers -mold - ## LSP # clang-extra-tools # py3-lsp-server -## Build Systems -make cmake meson ninja-build -maven - -## Versioning -git git-doc - -## Debuggers -gdb - #--------------------------------# # 1337 h@xx0r -hexedit -# aircrack-ng -# nmap nmap-ncat -# rustscan -# ghidra -# zaproxy -# mitmproxy -# hashcat -# john -# hydra -# radare2 -exiftool -# sqlmap -# recon-ng -# bettercap -socat -arp-scan -tor -# jadx apktool -android-tools scrcpy rdbo-archives diff --git a/profiles/standard.list b/profiles/standard.list new file mode 100644 index 0000000..90ea86f --- /dev/null +++ b/profiles/standard.list @@ -0,0 +1,116 @@ +# standard: minimal plus everyday tools, media and development +# +# Syntax: one or more packages per line, # starts a comment, +# `@include ` pulls in another profile, `-pattern` removes +# packages (globs allowed) added before it. + +@include minimal + +# System + +## Administration Tools +openssh +curl wget +gpg +patch entr +7zip +rsync +diffutils +tmux tmux-doc +jq +ripgrep +btop +bandwhich +timeshift +flatpak +bubblewrap +bc +argon2 +delta +just + +## Network +openvpn wireguard-tools + +## Docs +mandoc man-pages + +#--------------------------------# + +# Desktop Environment + +## Fonts +font-noto-all + +## Text Editing +helix helix-tree-sitter-vendor + +## Browser +librewolf + +## File Managent +yazi dragon-drop + +## Office +sc-im +groff +poppler poppler-utils + +## Multimedia +mpv +ffmpeg +imagemagick +nsxiv +wf-recorder + +## Password Management +keepassxc pass + +## Chat +weechat + +## System Resources +hardinfo2 + +## Misc +lxappearance +zenity +cava +wev xev + +#--------------------------------# + +# Development + +## Compilers/Interpreters +ruby ruby-doc ruby-rdoc ruby-full ruby-dev +python3 py3-pip py3-virtualenv +gcc g++ valgrind +perl +openjdk25 +rust cargo +go + +## Linkers +mold + +## Build Systems +make cmake meson ninja-build +maven + +## Versioning +git git-doc + +## Debuggers +gdb + +#--------------------------------# + +# 1337 h@xx0r + +hexedit +exiftool +socat +arp-scan +tor +android-tools scrcpy diff --git a/sensible_clean.sh b/sensible_clean.sh deleted file mode 100755 index c79df25..0000000 --- a/sensible_clean.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -# Clean everything, except built packages - -rm -rf cache/boot cache/firmware cache/iso cache/rootfs.squashfs cache/sigma-linux.iso cache/squashfs/ diff --git a/sigma b/sigma new file mode 100755 index 0000000..02a7420 --- /dev/null +++ b/sigma @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Sigma Linux builder. Run `./sigma --help`. +require_relative "lib/sigma" + +exit Sigma::CLI.start(ARGV) || 0 diff --git a/src/apk/sigma-vt323/src/VT323.tar.xz b/src/apk/sigma-vt323/src/VT323.tar.xz deleted file mode 120000 index 98f29ea..0000000 --- a/src/apk/sigma-vt323/src/VT323.tar.xz +++ /dev/null @@ -1 +0,0 @@ -/app/src/apk/sigma-vt323/VT323.tar.xz \ No newline at end of file diff --git a/src/apk/sigma-vt323/src/vt323.tar.xz b/src/apk/sigma-vt323/src/vt323.tar.xz deleted file mode 120000 index 2c6dbe6..0000000 --- a/src/apk/sigma-vt323/src/vt323.tar.xz +++ /dev/null @@ -1 +0,0 @@ -/app/src/apk/sigma-vt323/vt323.tar.xz \ No newline at end of file diff --git a/src/build_repo.sh b/src/build_repo.sh deleted file mode 100755 index 1ae78a6..0000000 --- a/src/build_repo.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh - -# NOTE: Run as the build user! - -set -e - -mkdir -p "$REPO_DIR" "$APKTEMP_DIR" - -# Get existing APKs in repo (if any) -apklist="$(find cache/repo/ -name "*.apk" | sed 's|.*/||')" -is_apk_indexed() { - printf "%s" "$apklist" | tr ' ' '\n' | grep "^$1-" > /dev/null - return $? -} - -# Build APKs -for apk in $(ls "$APKTEMP_DIR"); do - echo "[*] Building ${apk}..." - if is_apk_indexed "$apk"; then - echo "[*] Skipped building APK '${apk}', already indexed" - continue - fi - - cd "$APKTEMP_DIR/$apk" - abuild -kK -rf -P "$REPO_DIR" -done - -# Return to root dir -cd "$ROOT_DIR" diff --git a/src/config.sh b/src/config.sh deleted file mode 100755 index 5326a31..0000000 --- a/src/config.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh - -set -e - -export PROFILENAME="sigma" -export SYSNAME="Sigma Linux" -export ROOT_DIR="$(dirname -- "$(readlink -f -- "$0")")" -export CACHE_DIR="$ROOT_DIR/cache" -export SRC_DIR="$ROOT_DIR/src" -export REPO_DIR="$CACHE_DIR/repo" -export APK_DIR="$SRC_DIR/apk" -export APKTEMP_DIR="$CACHE_DIR/apk" -export BUILD_USER="build" # User with abuild setup -export MAX_THREADS="$(nproc)" -export DISTRO_TARGET_ARCH="$(uname -m)" -export REPOS_FILE="$SRC_DIR/repositories" -export PKG_PROFILE="standard" -export BOOT_DIR="$CACHE_DIR/boot" -export SQUASHFS_DIR="$CACHE_DIR/squashfs" -export SQUASHFS_PATH="$CACHE_DIR/rootfs.squashfs" -export SQUASHFS_COMP="xz" -export SQUASHFS_EXTRA_ARGS="" -export FIRMWARE_DIR="$CACHE_DIR/firmware" -export ISO_DIR="$CACHE_DIR/iso" -export ISO_PATH="$CACHE_DIR/$PROFILENAME-linux.iso" -export ISO_VOLID="sigma-linux-cdrom" - -# Allow overriding the default variables through a -# separate script, which is added to gitignore -if [ -f "$SRC_DIR/config.override.sh" ]; then - . "$SRC_DIR/config.override.sh" - echo "[*] Loaded config overrides" -fi - -echo "Config:" -echo " - PROFILENAME: $PROFILENAME" -echo " - SYSNAME: $SYSNAME" -echo " - ROOT_DIR: $ROOT_DIR" -echo " - CACHE_DIR: $CACHE_DIR" -echo " - SRC_DIR: $SRC_DIR" -echo " - REPO_DIR: $REPO_DIR" -echo " - APK_DIR: $APK_DIR" -echo " - APKTEMP_DIR: $APKTEMP_DIR" -echo " - BUILD_USER: $BUILD_USER" -echo " - MAX_THREADS: $MAX_THREADS" -echo " - DISTRO_TARGET_ARCH: $DISTRO_TARGET_ARCH" -echo " - REPOS_FILE: $REPOS_FILE" -echo " - PKG_PROFILE: $PKG_PROFILE" -echo " - SQUASHFS_DIR: $SQUASHFS_DIR" -echo " - SQUASHFS_PATH: $SQUASHFS_PATH" -echo " - SQUASHFS_COMP: $SQUASHFS_COMP" -echo " - SQUASHFS_EXTRA_ARGS: $SQUASHFS_EXTRA_ARGS" -echo " - FIRMWARE_DIR: $FIRMWARE_DIR" -echo " - ISO_DIR: $ISO_DIR" -echo " - ISO_PATH: $ISO_PATH" -echo " - ISO_VOLID: $ISO_VOLID" diff --git a/src/make_iso.sh b/src/make_iso.sh deleted file mode 100755 index 797f533..0000000 --- a/src/make_iso.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -set -e - -# The "$BOOT_DIR" will contain kernel, initramfs, kernel config, modules, etc -# so we don't have to do any aditional setup here -mkdir -p "$ISO_DIR" "$ISO_DIR/boot" -cp -r "$BOOT_DIR/." "$ISO_DIR/boot/." - -# Copy squashfs to ISO dir -cp "$SQUASHFS_PATH" "$ISO_DIR/rootfs.squashfs" - -# Copy GRUB wallpaper to /boot/grub -cp "$SRC_DIR/grub_wallpaper.png" "$ISO_DIR/boot/grub/wallpaper.png" - -# Copy GRUB config to ISO dir -# NOTE: The vt.* kernel parameters are used for changing the virtual terminal (VT) theme -# Sane defaults: -# default_red: 0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff -# default_grn: 0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff -# default_blu: 0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff -# NOTE: The VT color table is not in order! -# From 'drivers/tty/vt/vt.c': -# ``` -# const unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7, -# 8,12,10,14, 9,13,11,15 }; -# ``` -# This means that color "3" will actually be mapped to the RGB indices of color "6", for -# example. -vt_default_red=0x10,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,0x55,0xff,0x55,0xff,0x55,0xff,0x00,0xff -vt_default_grn=0x1a,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff -vt_default_blu=0x20,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,0x55,0x55,0x55,0x55,0xff,0xff,0xc8,0xff - -# Get kernel and initramfs names -vmlinuz="$(ls "$BOOT_DIR/" | grep "^vmlinuz" | tail -1)" -initramfs="$(ls "$BOOT_DIR" | grep "^initramfs" | tail -1)" - -cat <<- EOF > "$ISO_DIR/boot/grub/grub.cfg" -set timeout=5 - -insmod all_video -loadfont /boot/grub/fonts/unicode.pf2 - -insmod gfxterm -set gfxmode=640x480 -terminal_output gfxterm - -insmod png -background_image /boot/grub/wallpaper.png - -menuentry "$SYSNAME" { - echo "Loading vmlinuz..." - linux /boot/$vmlinuz root=LABEL=$ISO_VOLID rootfstype=iso9660 rd.live=/rootfs.squashfs vt.color=0x0B vt.default_red=$vt_default_red vt.default_grn=$vt_default_grn vt.default_blu=$vt_default_blu splash - echo "Loading initrd..." - initrd /boot/$initramfs -} -EOF - -# Build ISO -grub-mkrescue "$ISO_DIR" -o "$ISO_PATH" -volid "$ISO_VOLID" diff --git a/src/make_squashfs.sh b/src/make_squashfs.sh deleted file mode 100755 index 6a3484f..0000000 --- a/src/make_squashfs.sh +++ /dev/null @@ -1,213 +0,0 @@ -#!/bin/sh - -set -e - -mkdir -p "$SQUASHFS_DIR" - -# Mount filesystems because some packages write to them -# (e.g /dev/null) -umount -R "$SQUASHFS_DIR/dev" > /dev/null 2>&1 || true -rm -rf "$SQUASHFS_DIR/dev" > /dev/null 2>&1 || true -mkdir -p "$SQUASHFS_DIR/dev" -mount --rbind /dev "$SQUASHFS_DIR/dev" - -umount -R "$SQUASHFS_DIR/proc" > /dev/null 2>&1 || true -rm -rf "$SQUASHFS_DIR/proc" > /dev/null 2>&1 || true -mkdir -p "$SQUASHFS_DIR/proc" -mount --rbind /proc "$SQUASHFS_DIR/proc" - -# Mount temporary boot dir, which will be populated by the -# kernel and initramfs packages -umount -R "$SQUASHFS_DIR/boot" > /dev/null 2>&1 || true -rm -rf "$SQUASHFS_DIR/boot" > /dev/null 2>&1 || true -mkdir -p "$BOOT_DIR" "$SQUASHFS_DIR/boot" -mount --rbind "$BOOT_DIR" "$SQUASHFS_DIR/boot" - -# Disable GRUB triggers since they -# interfere with our GRUB config -mkdir -p "$SQUASHFS_DIR/etc" -echo 'disable_trigger=1' > "$SQUASHFS_DIR/etc/upgrade-grub.conf" - -pkgs="$(cat "$SRC_DIR/pkglist.$PKG_PROFILE" | sed 's/#.*//g' | tr '\n' ' ')" -echo "Packages: $pkgs" - -# Create /etc/modules for use in initrdbo -cat <<- EOF > "$SQUASHFS_DIR/etc/modules" -ahci libahci -ata_generic libata -ata_piix -nvme_core nvme -usbcore -xhci_pci ehci_pci uhci_hcd -usb_common usb_storage uas - -scsi_mod sd_mod sr_mod - -hid hid_generic usbhid - -af_packet -ipv6 -EOF - - -# Skip installing APKs if APK database is already set up -if [ ! -d "$SQUASHFS_DIR/etc/apk" ]; then - # Initialize APK database - apk add --arch "$DISTRO_TARGET_ARCH" --initdb -p "$SQUASHFS_DIR" - - # Install packages - apk add \ - -p "$SQUASHFS_DIR" \ - --arch "$DISTRO_TARGET_ARCH" \ - --allow-untrusted \ - --no-cache \ - --repositories-file="$REPOS_FILE" \ - -X "$REPO_DIR/apk" \ - $pkgs -else - echo "[*] Skipped installing APKs in SquashFS, '/etc/apk' exists" -fi - -# Add repositories file to squashfs -mkdir -p "$SQUASHFS_DIR/etc/apk" -cp "$REPOS_FILE" "$SQUASHFS_DIR/etc/apk/repositories" - -# Overwrite package owned files -cat <<- EOF > "$SQUASHFS_DIR/etc/issue" -Welcome to Sigma Linux (made by rdbo) -Kernel \r on an \m (\l) -EOF - -cat <<- EOF > "$SQUASHFS_DIR/etc/motd" -Welcome to Sigma Linux! - -To install the system, run the following command: setup-sigma - -For more information about the distribution, see: - - https://github.com/rdbo/sigma-linux - - https://wiki.alpinelinux.org - -EOF - -echo "$PROFILENAME" > "$SQUASHFS_DIR/etc/hostname" - -> "$SQUASHFS_DIR/etc/fstab" - -# Add default network interfaces configuration -mkdir -p "$SQUASHFS_DIR/etc/network" -cat <<- EOF > "$SQUASHFS_DIR/etc/network/interfaces" -auto lo -iface lo inet loopback -EOF - -# Create default doas config -cat <<- EOF > "$SQUASHFS_DIR/etc/doas.conf" -permit persist :wheel -permit nopass root -EOF - -# Modify zram-init config -cat <<- EOF > "$SQUASHFS_DIR/etc/conf.d/zram-init" -load_on_start="yes" -unload_on_stop="yes" -num_devices="1" - -type0="swap" -flag0= -size0=\`LC_ALL=C free -m | awk '/^Mem:/{print int(\$2/2)}'\` # 50% of memory reserved for zram -maxs0=1 -algo0=zstd -labl0=zram_swap -EOF - -# Create common user directories in /etc/skel -dirs="Downloads Documents Pictures Videos Music" -for dir in $dirs; do - mkdir -p "$SQUASHFS_DIR/etc/skel/$dir" -done - -# Copy /etc/skel to /root (allows for logging in to the desktop environment as root on live boot) -cp -r "$SQUASHFS_DIR/etc/skel/." "$SQUASHFS_DIR/root/." - -# Enable OpenRC services -rc_add() { - # $1: service name - # $2: run level - chroot "$SQUASHFS_DIR" rc-update add "$1" "$2" -} - -rc_add devfs sysinit -rc_add dmesg sysinit -rc_add mdev sysinit -rc_add hwdrivers sysinit -# rc_add modloop sysinit # NOTE: Not necessary on Sigma Linux - -rc_add hwclock boot -rc_add modules boot -rc_add sysctl boot -rc_add bootmisc boot -rc_add syslog boot - -# NOTE: The 'udev' services are used for setting up /dev and doing things -# such as changing the ownership of certain devices (e.g /dev/dri/cardN). -# That behavior allows us to access devices (such as the GPU and the input -# devices) without root access. -rc_add udev sysinit -rc_add udev-trigger sysinit -rc_add udev-settle sysinit -rc_add udev-postmount default -rc_add hostname boot -rc_add zram-init boot -rc_add networking default # Sets up interfaces based on /etc/network/interfaces -rc_add earlyoom default -rc_add polkit default -rc_add networkmanager default -rc_add dbus default -rc_add seatd default -rc_add bluetooth default - -rc_add local default # used for start scripts - -# Setup regular user -if [ ! -e "$SQUASHFS_DIR/home/user" ]; then - useradd -R "$SQUASHFS_DIR" -s /bin/bash -m -G wheel,audio,input,video,seat,dialout,netdev user -fi -# passwd -R "$SQUASHFS_DIR" -d user -chroot "$SQUASHFS_DIR" sh -c 'printf "user:pass" | chpasswd' - -# Merge user patches (https://alpinelinux.org/posts/2025-10-01-usr-merge.html) -chroot "$SQUASHFS_DIR" merge-usr - -# Unmount filesystems -umount -R "$SQUASHFS_DIR/proc" -rm -rf "$SQUASHFS_DIR/proc" - -umount -R "$SQUASHFS_DIR/dev" -rm -rf "$SQUASHFS_DIR/dev" - -umount -R "$SQUASHFS_DIR/boot" -rm -rf "$SQUASHFS_DIR/boot" - -# Cleanup firmware files that are not used by any module -# (they can be reinstalled through the `linux-firmware` pkg) -# if [ -e "$FIRMWARE_DIR" ]; then -# echo "[*] Skipped firmware cleanup, '$FIRMWARE_DIR' exists" -# else -# echo "[*] Cleaning up unused firmware for squashfs..." -# mv "$SQUASHFS_DIR/lib/firmware" "$FIRMWARE_DIR" -# mkdir -p "$SQUASHFS_DIR/lib/firmware" -# # TODO: Make sure that `modinfo` cannot fail. It mail fail due to -# # the host kernel not being the same as the installer kernel -# chroot "$FILESYSTEM_DIR" find /lib/modules -type f -name "*.ko*" | chroot "$FILESYSTEM_DIR" xargs -r -- modinfo -F firmware | sort -u | while read fw; do -# for fname in "$fw" "$fw.zst" "$fw.xz"; do -# if [ -e "${FIRMWARE_DIR}/$fname" ]; then -# install -pD "${FIRMWARE_DIR}/$fname" "$SQUASHFS_DIR/lib/firmware/$fname" -# break -# fi -# done -# done -# fi - -# Create squashfs -rm -f "$SQUASHFS_PATH" # Avoid appending to existing squashfs file -mksquashfs "$SQUASHFS_DIR" "$SQUASHFS_PATH" -comp "$SQUASHFS_COMP" $SQUASHFS_EXTRA_ARGS diff --git a/src/pkglist.full b/src/pkglist.full deleted file mode 100644 index ef4fe05..0000000 --- a/src/pkglist.full +++ /dev/null @@ -1,273 +0,0 @@ -# Core Packages - -## Core -alpine-base -alpine-baselayout -alpine-baselayout-data -alpine-keys -apk-tools -busybox busybox-doc -ca-certificates-bundle -musl -openrc -openssl -cryptsetup -sfdisk -lsblk -grub -grub-bios -grub-efi -xorriso -mtools -efibootmgr -initrdbo -linux-sigma linux-sigma-dev -# linux-stable linux-stable-dev -alpine-sdk -kmod -shadow -umount -merge-usr -losetup - -## Firmware -linux-firmware - -#--------------------------------# - -# System - -## Administration Tools -bash starship -doas -openssh -curl wget -gpg -patch entr -7zip -xfsprogs e2fsprogs btrfs-progs dosfstools ntfs-3g -yt-dlp -tar xz zstd zip unzip gzip -lighttpd -rsync -diffutils -tmux tmux-doc -jq -ripgrep -wireless-tools -btop -bandwhich -timeshift -flatpak -bubblewrap -bc -cpupower -polkit -argon2 -delta -just - -## Suspend/Hibernate -acpid zzz - -## Memory -earlyoom zram-init - -## Network -wpa_supplicant dhcpcd -iwd -networkmanager networkmanager-wifi networkmanager-wwan networkmanager-dnsmasq networkmanager-cli networkmanager-tui -openvpn wireguard-tools - -## Audio -pipewire pipewire-pulse pipewire-alsa pipewire-jack -pulsemixer pulseaudio-ctl pulseaudio-utils -easyeffects calf-lv2 lsp-plugins-lv2 mda-lv2 zam-plugins-lv2 - -## Bluetooth -bluez - -## Docs -mandoc man-pages - -## Configuration -sigma-conf -biblegen -ncurses-terminfo - -#--------------------------------# - -# Desktop Environment - -## Core -wayland wayland-libs-client wayland-libs-server -xwayland xwayland-satellite seatd eudev xrdb - -## Fonts -sigma-firacode-nerd sigma-vt323 sigma-material-symbols -font-ubuntu-nerd -font-noto-all - -## Desktop -# river river-doc # compositor -# hyprland hyprland-doc -mangowc -# niri -waybar waybar-doc # status bar -# yambar yambar-doc # status bar -fuzzel # dmenu-like -swaylock # screen locker -swaybg # wallpaper daemon -# cosmic-session cosmic-launcher cosmic-randr cosmic-panel cosmic-files cosmic-icons cosmic-comp cosmic-bg - -## Terminal -# rio -foot - -## Text Editing -vim helix tree-sitter-grammars - -## Browser -# luakit luakit-doc -# firefox -librewolf - -## Notifications -mako -libnotify - -## Screenshot -grim -slurp - -## File Managent -yazi dragon-drop - -## Office -sc-im -sigma-sent -groff -poppler poppler-utils -zathura zathura-pdf-mupdf -libreoffice libreoffice-gtk - -## Mail -neomutt -thunderbird - -## RSS -newsboat - -## Multimedia -mpv -ffmpeg -imagemagick -nsxiv -gimp -inkscape -wf-recorder -shotcut -lmms - -## Password Management -keepassxc pass - -## Chat -weechat -simplex-chat - -## Monitoring -wireshark tshark - -## System Resources -lm-sensors -hardinfo2 -pciutils -lscpu - -## Misc -wl-clipboard -wlr-randr -dbus-x11 -sigma-wvkbd -neofetch -lxappearance -zenity -setxkbmap -electrum -cava -wev xev -qemu-ui-gtk qemu-system-x86_64 qemu-system-aarch64 ovmf - -## Themes -sigma-flat-remix-gtk -adwaita-icon-theme -papirus-icon-theme - -## GL/Rendering -mesa -mesa-gbm -mesa-dri-gallium -mesa-egl -mesa-gl -mesa-gles -mesa-vulkan-ati -mesa-vulkan-intel -mesa-vulkan-swrast -mesa-utils - -#--------------------------------# - -# Development - -## Compilers/Interpreters -ruby ruby-doc ruby-rdoc ruby-full ruby-dev -python3 py3-pip py3-virtualenv -gcc g++ valgrind -perl -openjdk25 -rust cargo -go - -## Linkers -mold - -## LSP -# clang-extra-tools -# py3-lsp-server - -## Build Systems -make cmake meson ninja-build -maven - -## Versioning -git git-doc - -## Debuggers -gdb - -#--------------------------------# - -# 1337 h@xx0r - -hexedit -aircrack-ng -nmap nmap-ncat -rustscan -ghidra -zaproxy -mitmproxy -hashcat -john -hydra -radare2 -exiftool -sqlmap -recon-ng -bettercap -socat -arp-scan -tor -jadx apktool -android-tools scrcpy -rdbo-archives diff --git a/src/pkglist.minimal b/src/pkglist.minimal deleted file mode 100644 index 2593475..0000000 --- a/src/pkglist.minimal +++ /dev/null @@ -1,304 +0,0 @@ -# Core Packages - -## Core -alpine-base -alpine-baselayout -alpine-baselayout-data -alpine-keys -apk-tools -busybox busybox-doc -ca-certificates-bundle -musl -openrc -openssl -cryptsetup -sfdisk -lsblk -grub -grub-bios -grub-efi -xorriso -mtools -efibootmgr -initrdbo -linux-sigma linux-sigma-dev -# linux-stable linux-stable-dev -alpine-sdk -kmod -shadow -umount -merge-usr -losetup - -## Firmware -linux-firmware-amd -linux-firmware-amd-ucode -linux-firmware-amdgpu -linux-firmware-intel -linux-firmware-i915 -linux-firmware-nvidia -linux-firmware-brcm -linux-firmware-realtek -linux-firmware-rtl_bt -linux-firmware-rtw88 -linux-firmware-rtw89 -linux-firmware-rtlwifi -linux-firmware-qca -linux-firmware-ath10k -linux-firmware-ath11k -linux-firmware-ath12k -linux-firmware-bnx2 -linux-firmware-bnx2x -linux-firmware-mellanox -linux-firmware-microchip -linux-firmware-synaptics -linux-firmware-hp -linux-firmware-lenovo -linux-firmware-dell -linux-firmware-powervr -linux-firmware-amlogic -linux-firmware-meson -linux-firmware-rockchip -linux-firmware-mrvl -linux-firmware-qcom -linux-firmware-mediatek -linux-firmware-mwlwifi - -#--------------------------------# - -# System - -## Administration Tools -bash starship -doas -# openssh -# curl wget -# gpg -# patch entr -# 7zip -xfsprogs e2fsprogs btrfs-progs dosfstools ntfs-3g -# yt-dlp -tar xz zstd zip unzip gzip -# lighttpd -# rsync -# diffutils -# tmux tmux-doc -# jq -# ripgrep -wireless-tools -# btop -# bandwhich -# timeshift -# flatpak -# bubblewrap -# bc -cpupower -polkit -# argon2 -# delta -# just - -## Suspend/Hibernate -acpid zzz - -## Memory -earlyoom zram-init - -## Network -wpa_supplicant dhcpcd -iwd -networkmanager networkmanager-wifi networkmanager-wwan networkmanager-dnsmasq networkmanager-cli networkmanager-tui -# openvpn wireguard-tools - -## Audio -pipewire pipewire-pulse pipewire-alsa pipewire-jack -pulsemixer pulseaudio-ctl pulseaudio-utils -easyeffects calf-lv2 lsp-plugins-lv2 mda-lv2 zam-plugins-lv2 - -## Bluetooth -bluez - -## Docs -# mandoc man-pages - -## Configuration -sigma-conf -biblegen -ncurses-terminfo - -#--------------------------------# - -# Desktop Environment - -## Core -wayland wayland-libs-client wayland-libs-server -xwayland xwayland-satellite seatd eudev xrdb - -## Fonts -sigma-firacode-nerd sigma-vt323 sigma-material-symbols -font-ubuntu-nerd -# font-noto-all - -## Desktop -# river river-doc # compositor -# hyprland # hyprland-doc -mangowc -# niri -waybar waybar-doc # status bar -# yambar yambar-doc # status bar -fuzzel # dmenu-like -swaylock # screen locker -swaybg # wallpaper daemon -# cosmic-session cosmic-launcher cosmic-randr cosmic-panel cosmic-files cosmic-icons cosmic-comp cosmic-bg - -## Terminal -# rio -foot - -## Text Editing -vim # helix tree-sitter-grammars - -## Browser -# luakit luakit-doc -# firefox -# librewolf - -## Notifications -mako -libnotify - -## Screenshot -grim -slurp - -## File Managent -# yazi dragon-drop - -## Office -# sc-im -sigma-sent -# groff -# poppler poppler-utils -# zathura zathura-pdf-mupdf -# libreoffice libreoffice-gtk - -## Mail -# neomutt -# thunderbird - -## RSS -# newsboat - -## Multimedia -# mpv -# ffmpeg -# imagemagick -# nsxiv -# gimp -# inkscape -# wf-recorder -# shotcut -# lmms - -## Password Management -# keepassxc pass - -## Chat -# weechat -# simplex-chat - -## Monitoring -# wireshark tshark - -## System Resources -lm-sensors -# hardinfo2 -pciutils -lscpu - -## Misc -wl-clipboard -wlr-randr -dbus-x11 -sigma-wvkbd -neofetch -# lxappearance -# zenity -setxkbmap -# electrum -# cava -# wev xev -# qemu-ui-gtk qemu-system-x86_64 qemu-system-aarch64 ovmf - -## Themes -sigma-flat-remix-gtk -adwaita-icon-theme -papirus-icon-theme - -## GL/Rendering -mesa -mesa-gbm -mesa-dri-gallium -mesa-egl -mesa-gl -mesa-gles -mesa-vulkan-ati -mesa-vulkan-intel -mesa-vulkan-swrast -mesa-utils - -#--------------------------------# - -# Development - -## Compilers/Interpreters -# ruby ruby-doc ruby-rdoc ruby-full ruby-dev -# python3 py3-pip py3-virtualenv -# gcc g++ valgrind -# perl -# openjdk25 -# rust cargo -# go - -## Linkers -# mold - -## LSP -# clang-extra-tools -# py3-lsp-server - -## Build Systems -# make cmake meson ninja-build -# maven - -## Versioning -# git git-doc - -## Debuggers -# gdb - -#--------------------------------# - -# 1337 h@xx0r - -# hexedit -# aircrack-ng -# nmap nmap-ncat -# rustscan -# ghidra -# zaproxy -# mitmproxy -# hashcat -# john -# hydra -# radare2 -# exiftool -# sqlmap -# recon-ng -# bettercap -# socat -# arp-scan -# tor -# jadx apktool -# android-tools scrcpy -rdbo-archives diff --git a/src/setup_apks.sh b/src/setup_apks.sh deleted file mode 100755 index dee491f..0000000 --- a/src/setup_apks.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/bin/sh - -# linux-sigma -if [ ! -d "$APKTEMP_DIR/linux-sigma" ]; then - mkdir -p "$APKTEMP_DIR/linux-sigma" - cp -r "$APK_DIR/linux-sigma/." "$APKTEMP_DIR/linux-sigma/." - cd "$APKTEMP_DIR/linux-sigma" - abuild checksum -fi - -# initrdbo -if [ ! -d "$APKTEMP_DIR/initrdbo" ]; then - mkdir -p "$APKTEMP_DIR/initrdbo" - cp -r "$APK_DIR/initrdbo/." "$APKTEMP_DIR/initrdbo/." - cd "$APKTEMP_DIR/initrdbo" - abuild checksum -fi - -# sigma-conf -if [ ! -d "$APKTEMP_DIR/sigma-conf" ]; then - mkdir -p "$APKTEMP_DIR/sigma-conf/" - cp "$APK_DIR/sigma-conf/APKBUILD" "$APKTEMP_DIR/sigma-conf/" - cd "$APK_DIR/sigma-conf" - tar -czf "$APKTEMP_DIR/sigma-conf/rootfs.tar.gz" rootfs - cd "$APKTEMP_DIR/sigma-conf" - abuild checksum -fi - -# rdbo-archives -if [ ! -d "$APKTEMP_DIR/rdbo-archives" ]; then - mkdir -p "$APKTEMP_DIR/rdbo-archives" - cp -r "$APK_DIR/rdbo-archives/." "$APKTEMP_DIR/rdbo-archives/." - cd "$APKTEMP_DIR/rdbo-archives" - abuild checksum -fi - -# sigma-firacode-nerd -if [ ! -d "$APKTEMP_DIR/sigma-firacode-nerd" ]; then - cp -r "$APK_DIR/sigma-firacode-nerd/" "$APKTEMP_DIR/" -fi - -# sigma-material-symbols -if [ ! -d "$APKTEMP_DIR/sigma-material-symbols" ]; then - cp -r "$APK_DIR/sigma-material-symbols/" "$APKTEMP_DIR/" -fi - -# sigma-sent -if [ ! -d "$APKTEMP_DIR/sigma-sent" ]; then - mkdir -p "$APKTEMP_DIR/sigma-sent/" - cp "$APK_DIR/sigma-sent/APKBUILD" "$APKTEMP_DIR/sigma-sent/" - cd "$APK_DIR/sigma-sent" - tar -czf "$APKTEMP_DIR/sigma-sent/sent.tar.gz" sent - cd "$APKTEMP_DIR/sigma-sent" - abuild checksum -fi - -# sigma-st -if [ ! -d "$APKTEMP_DIR/sigma-st" ]; then - cp -r "$APK_DIR/sigma-st/" "$APKTEMP_DIR/" - cd "$APKTEMP_DIR/sigma-st" - abuild checksum -fi - -# sigma-vt323 -if [ ! -d "$APKTEMP_DIR/sigma-vt323" ]; then - cp -r "$APK_DIR/sigma-vt323/" "$APKTEMP_DIR/" -fi - -# sigma-wvkbd -if [ ! -d "$APKTEMP_DIR/sigma-wvkbd" ]; then - cp -r "$APK_DIR/sigma-wvkbd/" "$APKTEMP_DIR/" - cd "$APKTEMP_DIR/sigma-wvkbd" - abuild checksum -fi - -# sigma-flat-remix-gtk -if [ ! -d "$APKTEMP_DIR/sigma-flat-remix-gtk" ]; then - cp -r "$APK_DIR/sigma-flat-remix-gtk/" "$APKTEMP_DIR/" -fi - -# simplex-chat -# if [ ! -d "$APKTEMP_DIR/simplex-chat" ]; then -# cp -r "$APK_DIR/simplex-chat/" "$APKTEMP_DIR/" -# fi - -# biblegen -if [ ! -d "$APKTEMP_DIR/biblegen" ]; then - mkdir -p "$APKTEMP_DIR/biblegen" - cp -r "$APK_DIR/biblegen/." "$APKTEMP_DIR/biblegen/." - cd "$APKTEMP_DIR/biblegen" - abuild checksum -fi diff --git a/start.sh b/start.sh deleted file mode 100755 index 7e540a4..0000000 --- a/start.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -qemu-system-x86_64 \ - -enable-kvm \ - -smp 4 \ - -m 2048 \ - -cdrom cache/sigma-linux.iso \ - $@ diff --git a/start_uefi.sh b/start_uefi.sh deleted file mode 100755 index 57092e5..0000000 --- a/start_uefi.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -qemu-system-x86_64 \ - -bios /usr/share/OVMF/OVMF.fd \ - -enable-kvm \ - -smp 4 \ - -m 2048 \ - -cdrom cache/sigma-linux.iso \ - $@ diff --git a/test/iso_test.rb b/test/iso_test.rb new file mode 100644 index 0000000..e4951e7 --- /dev/null +++ b/test/iso_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class IsoTest < Minitest::Test + def setup + @dir = Dir.mktmpdir + @context = Sigma::Context.new(config("paths.cache" => @dir), quiet_ui) + pipeline = Sigma::Pipeline.new(@context) + @iso = pipeline.stage("iso") + FileUtils.mkdir_p(@context.paths.boot) + end + + def teardown = FileUtils.rm_rf(@dir) + + def boot(*files) = files.each { |f| FileUtils.touch(File.join(@context.paths.boot, f)) } + + # The menu the old make_iso.sh heredoc produced, verbatim. + OLD_MENU = <<~'CFG' + set timeout=5 + + insmod all_video + loadfont /boot/grub/fonts/unicode.pf2 + + insmod gfxterm + set gfxmode=640x480 + terminal_output gfxterm + + insmod png + background_image /boot/grub/wallpaper.png + + menuentry "Sigma Linux" { + echo "Loading vmlinuz..." + linux /boot/vmlinuz-sigma root=LABEL=sigma-linux-cdrom rootfstype=iso9660 rd.live=/rootfs.squashfs vt.color=0x0B vt.default_red=0x10,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,0x55,0xff,0x55,0xff,0x55,0xff,0x00,0xff vt.default_grn=0x1a,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff vt.default_blu=0x20,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,0x55,0x55,0x55,0x55,0xff,0xff,0xc8,0xff splash + echo "Loading initrd..." + initrd /boot/initramfs-sigma + } + CFG + + def test_single_kernel_menu_matches_the_old_one + boot("vmlinuz-sigma", "initramfs-sigma", "System.map-7.3.1-0-sigma") + assert_equal OLD_MENU, @iso.send(:render) + end + + def test_one_entry_per_kernel_own_kernel_first + boot("vmlinuz-stable", "initramfs-stable", "vmlinuz-sigma", "initramfs-sigma", "vmlinuz-noinitrd") + kernels = @iso.kernels + assert_equal %w[sigma stable], kernels.map(&:flavor) + assert_equal ["Sigma Linux (sigma)", "Sigma Linux (stable)"], kernels.map(&:title) + assert_equal 2, @iso.send(:render).scan("menuentry").size + end + + def test_theme_parses_the_shared_file + theme = @iso.theme + assert_equal "640x480", theme["GRUB_GFXMODE"] + assert_equal "Sigma Linux", theme["GRUB_DISTRIBUTOR"] + assert theme["GRUB_CMDLINE_LINUX_DEFAULT"].start_with?("vt.color=0x0B ") + end +end diff --git a/test/packages_test.rb b/test/packages_test.rb new file mode 100644 index 0000000..c7028e7 --- /dev/null +++ b/test/packages_test.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class PackagesTest < Minitest::Test + def local + @local ||= Sigma::LocalPackages.new(File.join(Sigma::ROOT, "packages"), + shell: Sigma::Shell.new(builder: "build"), carch: "x86_64") + end + + def test_reads_real_apkbuilds + kernel = local["linux-sigma"] + assert_equal %w[linux-sigma linux-sigma-dev linux-sigma-doc], kernel.outputs + assert_includes kernel.local_sources, "sigma.x86_64.config" + assert_equal ["initramfs-generator"], kernel.depends + end + + def test_resolves_subpackages_and_provides + assert_equal "linux-sigma", local["linux-sigma-dev"].name + assert_equal "initrdbo", local["initramfs-generator"].name + assert_nil local["busybox"] + end + + def test_profile_needs_only_what_it_names + needed = local.for_profile(Sigma::Profile.new(File.join(Sigma::ROOT, "profiles"), "standard").packages).map(&:name) + assert_equal 11, needed.size + %w[sigma-dwl sigma-st simplex-chat].each { |unused| refute_includes needed, unused } + end + + def test_build_order_puts_dependencies_first + order = local.for_profile(%w[linux-sigma]).map(&:name) + assert_equal %w[initrdbo linux-sigma], order, "linux-sigma depends on initramfs-generator (initrdbo)" + end + + def test_unknown_package + assert_raises(Sigma::Error) { local.for_names(%w[nope]) } + end +end diff --git a/test/profile_test.rb b/test/profile_test.rb new file mode 100644 index 0000000..dbc580e --- /dev/null +++ b/test/profile_test.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class ProfileTest < Minitest::Test + def with_profiles(files) + Dir.mktmpdir do |dir| + files.each { |name, body| File.write(File.join(dir, "#{name}.list"), body) } + yield dir + end + end + + def test_includes_removals_and_globs_apply_in_order + with_profiles("base" => "a b # trailing comment\nfw-x fw-y\n", + "top" => "@include base\n-fw-*\nfw\nc\n-a\na\n") do |dir| + assert_equal %w[b fw c a], Sigma::Profile.new(dir, "top").packages + end + end + + def test_cycles_and_unknown_directives_are_errors + with_profiles("a" => "@include b\n", "b" => "@include a\n", "c" => "@require x\n") do |dir| + assert_match(/cycle/, assert_raises(Sigma::Error) { Sigma::Profile.new(dir, "a") }.message) + assert_match(/@require/, assert_raises(Sigma::Error) { Sigma::Profile.new(dir, "c") }.message) + assert_raises(Sigma::Error) { Sigma::Profile.new(dir, "missing") } + end + end + + def test_shipped_profiles_layer + dir = File.join(Sigma::ROOT, "profiles") + minimal, standard, full = %w[minimal standard full].map { |n| Sigma::Profile.new(dir, n).packages } + assert_empty minimal - standard, "minimal ⊂ standard" + assert_includes full, "linux-firmware" + refute full.any? { |p| p.start_with?("linux-firmware-") }, "full swaps the firmware subset for linux-firmware" + %w[linux-sigma sigma-conf initrdbo].each { |p| assert_includes minimal, p } + end +end diff --git a/test/run.rb b/test/run.rb new file mode 100644 index 0000000..2f5fd3b --- /dev/null +++ b/test/run.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Runs every test: ruby test/run.rb +Dir[File.join(__dir__, "*_test.rb")].each { |file| require file } diff --git a/test/selection_test.rb b/test/selection_test.rb new file mode 100644 index 0000000..87107c7 --- /dev/null +++ b/test/selection_test.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class SelectionTest < Minitest::Test + STAGES = %w[packages rootfs squashfs iso].freeze + + def parse(*args) = Sigma::Selection.parse(args, STAGES) + def pairs(*args) = parse(*args).entries.map { |e| [e.stage, e.items] } + + def test_nothing_selects_everything_incrementally + selection = parse + assert selection.incremental? + assert_equal STAGES, selection.stages + end + + def test_named_stage_is_explicit + selection = parse("rootfs") + refute selection.incremental? + assert_equal [["rootfs", nil]], pairs("rootfs") + end + + def test_ranges + assert_equal %w[rootfs squashfs iso], parse("rootfs..").stages + assert_equal %w[packages rootfs squashfs], parse("..squashfs").stages + assert_equal %w[rootfs squashfs], parse("rootfs..squashfs").stages + assert_equal STAGES, parse("..").stages + end + + def test_items_merge_and_whole_wins + assert_equal [["packages", %w[linux-sigma initrdbo]]], pairs("packages:linux-sigma", "packages:initrdbo,linux-sigma") + assert_equal [["packages", nil]], pairs("packages:linux-sigma", "packages") + assert_equal [["packages", nil]], pairs("packages", "packages:linux-sigma") + end + + def test_result_is_in_pipeline_order + assert_equal [["rootfs", %w[configure]], ["iso", nil]], pairs("iso", "rootfs:configure") + end + + def test_errors + error = assert_raises(Sigma::Error) { parse("rotfs") } + assert_match(/did you mean `rootfs`/, error.message) + assert_raises(Sigma::Error) { parse("iso..rootfs") } + assert_raises(Sigma::Error) { parse("packages:") } + assert_raises(Sigma::Error) { parse("rootfs:configure..iso") } + end +end diff --git a/test/stage_test.rb b/test/stage_test.rb new file mode 100644 index 0000000..26c5dee --- /dev/null +++ b/test/stage_test.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +# Freshness: inputs, outputs and the upstream token. +class StageTest < Minitest::Test + class Upstream < Sigma::Stage + stage "up", "upstream", steps: %i[make] + attr_accessor :input + + def inputs = input + def step_make(_task) = nil + end + + class Downstream < Sigma::Stage + stage "down", "downstream", steps: %i[one two] + attr_accessor :input, :output + + def inputs = input + def outputs = [output] + def step_one(_task) = File.write(output, "x") + def step_two(_task) = nil + end + + FakePipeline = Struct.new(:state, :stages) do + def before(stage) = stage.is_a?(Downstream) ? stages.first : nil + end + + def setup + @dir = Dir.mktmpdir + cfg = config("paths.cache" => File.join(@dir, "cache")) + @context = Sigma::Context.new(cfg, quiet_ui) + pipeline = FakePipeline.new(Sigma::State.new(File.join(@dir, "state")), []) + @up = Upstream.new(@context, pipeline).tap { |s| s.input = "u1" } + @down = Downstream.new(@context, pipeline).tap { |s| s.input = "d1"; s.output = File.join(@dir, "out") } + pipeline.stages.replace([@up, @down]) + end + + def teardown = FileUtils.rm_rf(@dir) + + def test_lifecycle + assert_equal :missing, @down.status.first + @up.run(nil) + @down.run(nil) + assert @down.fresh? + + @down.input = "d2" + assert_equal [:stale, "inputs changed"], @down.status + @down.run(nil) + assert @down.fresh? + + @up.run(nil) # upstream ran again: same inputs, new token + assert_equal [:stale, "up changed"], @down.status + @down.run(nil) + + File.delete(@down.output) + assert_equal [:stale, "output missing"], @down.status + end + + def test_partial_run_keeps_inputs_but_changes_token + @up.run(nil) + @down.run(nil) + token = @down.token + @down.run(["two"]) + refute_equal token, @down.token, "downstream of a partial run must notice" + assert @down.fresh?, "a partial run doesn't invalidate the stage itself" + end + + def test_partial_run_without_prior_whole_run_records_nothing + @down.run(["two"]) + assert_nil @down.stamp + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..1ec8d9e --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require "tmpdir" +require_relative "../lib/sigma" + +module TestHelpers + def config(**overrides) + sets = overrides.map { |k, v| "#{k}=#{v}" } + Sigma::Config.load(Sigma::ROOT, overrides: sets) + end + + def quiet_ui = Sigma::UI.new(out: StringIO.new, color: false) +end + +Minitest::Test.include(TestHelpers) diff --git a/test/ui_test.rb b/test/ui_test.rb new file mode 100644 index 0000000..0bbc8a7 --- /dev/null +++ b/test/ui_test.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class UITest < Minitest::Test + def test_durations + ui = quiet_ui + assert_equal "0.4s", ui.duration(0.4) + assert_equal "42s", ui.duration(42) + assert_equal "4m 05s", ui.duration(245) + assert_equal "1h 03m", ui.duration(3780) + end + + def test_wrap_and_truncate + ui = quiet_ui + assert_equal ["aaa bbb", "ccc"], ui.wrap("aaa bbb ccc", 8) + assert_equal "abcd…", ui.truncate("abcdefgh", 5) + end + + def test_no_color_when_not_a_tty + out = StringIO.new + ui = Sigma::UI.new(out: out) + ui.say ui.paint("x", :accent, bold: true) + assert_equal "x\n", out.string + end +end From a7d2f1fe3e36717414738c386fa803eb5128bdf6 Mon Sep 17 00:00:00 2001 From: Rdbo Date: Sat, 31 Oct 2026 01:08:20 +0000 Subject: [PATCH 2/6] minor fix --- lib/sigma/stages/packages.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/sigma/stages/packages.rb b/lib/sigma/stages/packages.rb index ceec31b..5aded57 100644 --- a/lib/sigma/stages/packages.rb +++ b/lib/sigma/stages/packages.rb @@ -202,11 +202,15 @@ def share_apk_cache end # After `sigma pkg`, say where the result is and how to use it here. + # The hint runs on the host, from the checkout: "$PWD" is expanded there + # (inside the container the checkout is /app). -u upgrades a package + # that is already installed instead of leaving the old version. def install_hint(list) names = list.map(&:name).join(" ") - ui.note "repository #{relative(config.repo_dir)}" - ui.note "install it on this machine: doas apk add --repository #{relative(config.repo_dir)} #{names}" - ui.note "(trust the key once first: doas cp #{relative(paths.keys)}/*.pub /etc/apk/keys/)" + repo = "\"$PWD/#{relative(config.repo_dir)}\"" + ui.note "repository #{relative(config.repo_dir)}/#{config.arch}/" + ui.note "install here, from the checkout: doas apk add -u --repository #{repo} #{names}" + ui.note "(trust the key once first: doas cp #{relative(paths.keys)}/*.rsa.pub /etc/apk/keys/)" end end end From 1a4517d7c7d2da8229e79eeded69291d1db44a3f Mon Sep 17 00:00:00 2001 From: Rdbo Date: Sat, 31 Oct 2026 10:43:10 +0000 Subject: [PATCH 3/6] disable selftest --- packages/linux-sigma/sigma.x86_64.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/linux-sigma/sigma.x86_64.config b/packages/linux-sigma/sigma.x86_64.config index 65c852b..95809ab 100644 --- a/packages/linux-sigma/sigma.x86_64.config +++ b/packages/linux-sigma/sigma.x86_64.config @@ -3204,5 +3204,5 @@ CONFIG_FTRACE_SYSCALLS=y CONFIG_IO_STRICT_DEVMEM=y # CONFIG_X86_VERBOSE_BOOTUP is not set # CONFIG_EARLY_PRINTK is not set -CONFIG_X86_DECODER_SELFTEST=y +# CONFIG_X86_DECODER_SELFTEST is not set # CONFIG_RUNTIME_TESTING_MENU is not set From 5bf3f213982e6c4de2dcc79eea1ec571e34f69ba Mon Sep 17 00:00:00 2001 From: Rdbo Date: Sat, 31 Oct 2026 11:30:05 +0000 Subject: [PATCH 4/6] ccache check by content --- config/sigma.yml | 1 + lib/sigma/cli.rb | 12 +++++++- lib/sigma/errors.rb | 15 ++++++---- lib/sigma/shell.rb | 17 ++++++++++- lib/sigma/stages/packages.rb | 52 +++++++++++++++++++++++++++++++-- lib/sigma/ui.rb | 8 ++++++ test/packages_test.rb | 8 ++++++ test/shell_test.rb | 56 ++++++++++++++++++++++++++++++++++++ test/ui_test.rb | 7 +++++ 9 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 test/shell_test.rb diff --git a/config/sigma.yml b/config/sigma.yml index 20c4fdc..e57a3ae 100644 --- a/config/sigma.yml +++ b/config/sigma.yml @@ -21,6 +21,7 @@ paths: packages: builder: build # unprivileged user abuild runs as when we are root ccache: true # speeds up rebuilds, the kernel's especially + ccache_size: 10G # one kernel build fills ~3.5 GB; older entries are evicted beyond this keep_build_trees: false # keep abuild's src/ and pkg/ dirs for debugging rootfs: diff --git a/lib/sigma/cli.rb b/lib/sigma/cli.rb index e0cda1f..7907d09 100644 --- a/lib/sigma/cli.rb +++ b/lib/sigma/cli.rb @@ -187,7 +187,8 @@ def render_error(error) if error.is_a?(CommandFailed) unless error.tail.empty? lines << "" - lines.concat(error.excerpt.map { |l| ui.paint(l, :muted) }) + lines << ui.paint("first error in the output:", :text) if error.first_error + lines.concat(error.excerpt.map { |l| ui.paint(without_build_tree(l), :muted) }) end lines << "" << "#{ui.paint("log", :muted)} #{relative(error.log_path)}" if error.log_path end @@ -202,6 +203,15 @@ def render_error(error) 1 end + # Compiler messages name files by their full path in abuild's build tree + # (cache/build/sigma//src/...); without that prefix the message + # itself fits in the box. + def without_build_tree(line) + return line unless @config + + line.gsub(%r{#{Regexp.escape(@config.paths.staging)}/[^/\s]+/src/}, "") + end + def relative(path) path.to_s.delete_prefix("#{ROOT}/") end diff --git a/lib/sigma/errors.rb b/lib/sigma/errors.rb index 49de4cf..01ac44c 100644 --- a/lib/sigma/errors.rb +++ b/lib/sigma/errors.rb @@ -13,22 +13,27 @@ def initialize(message, hint: nil) end # A command exited non-zero. Carries what is needed to explain it: the last - # lines it printed and where the full log is. + # lines it printed, the first error among them (see Shell::ERROR_LINE) and + # where the full log is. class CommandFailed < Error - attr_reader :command, :status, :log_path, :tail + attr_reader :command, :status, :log_path, :tail, :first_error - # The lines worth showing: abuild prints cleanup after its ">>> ERROR" - # line, so when there is one, the window ends there instead. + # The lines worth showing: the first error when there was one. Otherwise + # the end of the output, which for abuild means up to its ">>> ERROR" + # line, since the cleanup it prints afterwards explains nothing. def excerpt(count = 15) + return first_error.first(count) if first_error + marker = tail.rindex { |line| line.start_with?(">>> ERROR") } marker ? tail[[marker - count + 1, 0].max..marker] : tail.last(count) end - def initialize(command, status, log_path: nil, tail: [], hint: nil) + def initialize(command, status, log_path: nil, tail: [], first_error: nil, hint: nil) @command = command @status = status @log_path = log_path @tail = tail + @first_error = first_error super("`#{command}` exited with status #{status}", hint: hint) end end diff --git a/lib/sigma/shell.rb b/lib/sigma/shell.rb index 372e64f..b0d2855 100644 --- a/lib/sigma/shell.rb +++ b/lib/sigma/shell.rb @@ -9,6 +9,14 @@ module Sigma class Shell TAIL_LINES = 80 + # A compiler-style diagnostic ("file.c:48:15: error: …", "error[E0425]: …") + # or a tool's "ERROR: …". Parallel make keeps building after one, so the + # real error can be thousands of lines above the end of the output. + ERROR_LINE = /\berror(?:\[\w+\])?: |\AERROR: / + # Lines GCC prints above a diagnostic, naming where it came from. + ERROR_CONTEXT = /\AIn file included from |\A\s+from |: In (?:member )?function |: At top level:/ + FIRST_ERROR_LINES = 15 + def initialize(builder:) @builder = builder end @@ -26,12 +34,19 @@ def run(task, *cmd, env: {}, chdir: nil, as: nil, input: nil, heavy: false) argv, spawn_env = wrap(heavy ? ["nice", "-n", "10", *cmd] : cmd, env, as) task.log&.puts("$ #{display(cmd, env)}") tail = [] + first_error = nil status = Open3.popen2e(spawn_env, *argv, **spawn_options(chdir)) do |stdin, out, wait| stdin.write(input) if input stdin.close each_line(out) do |line| task.output(line) + if first_error + first_error << line if first_error.size < FIRST_ERROR_LINES + elsif line.match?(ERROR_LINE) + context = tail.reverse.take_while { |l| l.match?(ERROR_CONTEXT) }.reverse + first_error = context.last(FIRST_ERROR_LINES - 1) << line + end tail << line tail.shift if tail.size > TAIL_LINES end @@ -43,7 +58,7 @@ def run(task, *cmd, env: {}, chdir: nil, as: nil, input: nil, heavy: false) # The log has the full command with its environment; the error keeps # to the command itself. raise CommandFailed.new(display(cmd, {}), status.exitstatus || status.termsig, - log_path: task.log&.path, tail: tail) + log_path: task.log&.path, tail: tail, first_error: first_error) rescue Errno::ENOENT raise Error.new("command not found: #{cmd.first}", hint: "Is it installed? Building inside the container (the default) provides every tool.") diff --git a/lib/sigma/stages/packages.rb b/lib/sigma/stages/packages.rb index 5aded57..9f3b25e 100644 --- a/lib/sigma/stages/packages.rb +++ b/lib/sigma/stages/packages.rb @@ -117,14 +117,47 @@ def build(pkg, key, reason:) task.detail = "checksum" abuild(task, dir, key, "checksum") task.detail = reason + before = ccache_counters abuild(task, dir, key, "-r", "-f", *(config.dig("packages.keep_build_trees") ? ["-K"] : [])) + hits, calls = ccache_delta(before, ccache_counters) apks = prune(pkg, task, dir, key) state.write(package_key(pkg), "version" => pkg.version, "fingerprint" => fingerprint(pkg), - "apks" => apks, "duration" => ui.clock - started) - task.detail = "built #{ui.bytes(apks.sum { |apk| File.size(File.join(arch_dir, apk)) })}" + "apks" => apks, "duration" => ui.clock - started, + "ccache" => calls && { "hits" => hits, "calls" => calls }) + parts = ["built #{ui.bytes(apks.sum { |apk| File.size(File.join(arch_dir, apk)) })}"] + parts << ccache_rate(hits, calls) if calls&.positive? + task.detail = parts.join(" · ") end end + # ccache's lifetime counters (`ccache --print-stats`), or nil when ccache + # is off or missing. Builds run one at a time, so the difference across a + # build is that build's. + def ccache_counters + return unless config.dig("packages.ccache") && shell.available?("ccache") + + out = shell.capture("ccache", "--print-stats", env: { "CCACHE_DIR" => paths.ccache }, as: :builder) + out.lines.to_h do |line| + name, value = line.chomp.split("\t", 2) + [name, value.to_i] + end + rescue CommandFailed + nil + end + + # [hits, cacheable compiles] between two snapshots; nil when unknown. + def ccache_delta(before, after) + return unless before && after + + change = ->(name) { after.fetch(name, 0) - before.fetch(name, 0) } + hits = change["direct_cache_hit"] + change["preprocessed_cache_hit"] + [hits, hits + change["cache_miss"]] + end + + def ccache_rate(hits, calls) + "ccache hit #{(100.0 * hits / calls).floor}% of #{ui.count(calls)}" + end + # A fresh working copy in cache/build/sigma/. A source named # foo.tar.gz with a sibling foo/ directory is packed here, reproducibly, # which is how sigma-conf ships rootfs/ without per-package code. @@ -160,10 +193,23 @@ def abuild_env(key) "JOBS" => jobs, "MAKEFLAGS" => "-j#{jobs}" } - env.merge!("USE_CCACHE" => "1", "CCACHE_DIR" => paths.ccache) if config.dig("packages.ccache") + env.merge!(ccache_env) if config.dig("packages.ccache") env end + # compiler_check=content: ccache tells compilers and GCC plugins apart by + # their contents, not their timestamps. The kernel compiles its plugins + # afresh in every build tree and loads them into nearly every compile, + # so with the default (mtime) every one of those compiles missed. + def ccache_env + { + "USE_CCACHE" => "1", + "CCACHE_DIR" => paths.ccache, + "CCACHE_COMPILERCHECK" => "content", + "CCACHE_MAXSIZE" => config.dig("packages.ccache_size") || "10G" + } + end + # Keeps only this version's .apk files for the package's outputs and # returns them; re-indexes when older versions were removed. def prune(pkg, task, dir, key) diff --git a/lib/sigma/ui.rb b/lib/sigma/ui.rb index b2838cb..84c5d9d 100644 --- a/lib/sigma/ui.rb +++ b/lib/sigma/ui.rb @@ -183,6 +183,14 @@ def bytes(count) unit == "B" ? "#{count} B" : format("%.1f %s", value, unit) end + # 950, 18.2k, 1.3M + def count(number) + return number.to_s if number < 1000 + return format("%.1fk", number / 1000.0) if number < 999_950 + + format("%.1fM", number / 1_000_000.0) + end + def plural(count, word, plural_word = "#{word}s") "#{count} #{count == 1 ? word : plural_word}" end diff --git a/test/packages_test.rb b/test/packages_test.rb index c7028e7..968d474 100644 --- a/test/packages_test.rb +++ b/test/packages_test.rb @@ -32,6 +32,14 @@ def test_build_order_puts_dependencies_first assert_equal %w[initrdbo linux-sigma], order, "linux-sigma depends on initramfs-generator (initrdbo)" end + def test_ccache_delta_counts_one_builds_hits + stage = Sigma::Stages::Packages.allocate + before = { "direct_cache_hit" => 614, "preprocessed_cache_hit" => 3, "cache_miss" => 22_172 } + after = { "direct_cache_hit" => 1085, "preprocessed_cache_hit" => 139, "cache_miss" => 40_147 } + assert_equal [607, 18_582], stage.send(:ccache_delta, before, after) + assert_nil stage.send(:ccache_delta, nil, after) + end + def test_unknown_package assert_raises(Sigma::Error) { local.for_names(%w[nope]) } end diff --git a/test/shell_test.rb b/test/shell_test.rb new file mode 100644 index 0000000..b42001c --- /dev/null +++ b/test/shell_test.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class ShellTest < Minitest::Test + def shell = Sigma::Shell.new(builder: "build") + def task = Sigma::UI::Task.new(quiet_ui, "test", nil) + + def failure(script) + shell.run(task, "sh", "-c", script) + flunk "expected the command to fail" + rescue Sigma::CommandFailed => e + e + end + + # Parallel make: the real error, then a hundred lines of other jobs. + def test_excerpt_is_the_first_error_with_its_include_chain + error = failure(<<~SH) + echo " CC a.o" + echo "In file included from x.h:1," + echo " from y.c:2:" + echo "x.h:3:4: error: unknown type name 'foo'" + echo " 3 | foo bar;" + i=0; while [ $i -lt 100 ]; do echo " CC noise$i.o"; i=$((i+1)); done + echo "y.c:9:1: error: second" + echo ">>> ERROR: pkg: build failed" + exit 1 + SH + excerpt = error.excerpt + assert_equal "In file included from x.h:1,", excerpt.first + assert_equal "x.h:3:4: error: unknown type name 'foo'", excerpt[2] + assert_equal Sigma::Shell::FIRST_ERROR_LINES, excerpt.size + refute_includes excerpt, " CC a.o" + end + + def test_without_an_error_line_the_excerpt_ends_at_abuilds_marker + error = failure(%(echo one; echo ">>> ERROR: pkg: build failed"; echo cleanup; exit 1)) + assert_nil error.first_error + assert_equal ["one", ">>> ERROR: pkg: build failed"], error.excerpt + end + + def test_error_lines_in_a_successful_command_are_ignored + assert_nil shell.run(task, "sh", "-c", "echo 'x.c:1:1: error: harmless'") + end + + def test_error_line_pattern + ["swab.h:48:15: error: unknown type name", "error[E0425]: cannot find value", + "ERROR: modpost: missing symbol", "configure: error: no compiler"].each do |line| + assert_match Sigma::Shell::ERROR_LINE, line + end + [" CC drivers/base/error.o", "cc1: all warnings being treated as errors", + ">>> ERROR: pkg: build failed"].each do |line| + refute_match Sigma::Shell::ERROR_LINE, line + end + end +end diff --git a/test/ui_test.rb b/test/ui_test.rb index 0bbc8a7..22aca12 100644 --- a/test/ui_test.rb +++ b/test/ui_test.rb @@ -11,6 +11,13 @@ def test_durations assert_equal "1h 03m", ui.duration(3780) end + def test_counts + ui = quiet_ui + assert_equal "950", ui.count(950) + assert_equal "18.2k", ui.count(18_193) + assert_equal "1.0M", ui.count(999_999) + end + def test_wrap_and_truncate ui = quiet_ui assert_equal ["aaa bbb", "ccc"], ui.wrap("aaa bbb ccc", 8) From 3da3f71b873e38f61246a0e567990c532c476994 Mon Sep 17 00:00:00 2001 From: Rdbo Date: Sat, 31 Oct 2026 12:03:30 +0000 Subject: [PATCH 5/6] add more pkgs --- .github/boot-test.sh | 62 +++++++++++++ .github/workflows/build-iso.yml | 149 +++++++++++++++++++++++++++++--- PLAN.md | 116 ++++++++++++++++--------- README.md | 4 + profiles/minimal.list | 4 + 5 files changed, 285 insertions(+), 50 deletions(-) create mode 100644 .github/boot-test.sh diff --git a/.github/boot-test.sh b/.github/boot-test.sh new file mode 100644 index 0000000..12616eb --- /dev/null +++ b/.github/boot-test.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Boots the freshly built ISO in QEMU and waits until OpenRC reaches the last +# service of the default runlevel ("Starting local"): the kernel, initrdbo's +# live boot from the squashfs and the whole init sequence all worked. +# +# The ISO's own menu has no serial console, so its kernel and initramfs are +# booted directly, with the ISO's command line plus console=ttyS0 and the ISO +# attached as a CD-ROM, which is where initrdbo finds the squashfs. +# +# usage: sh .github/boot-test.sh (from the checkout, after `./sigma build`) + +set -eu + +iso=out/sigma-linux.iso +boot=cache/boot +menu=cache/iso/boot/grub/grub.cfg +log=cache/logs/boot-test.log +marker="Starting local" +limit=${BOOT_TIMEOUT:-600} + +for file in "$iso" "$boot/vmlinuz-sigma" "$boot/initramfs-sigma" "$menu"; do + [ -r "$file" ] || { echo "boot-test: $file is missing; build the ISO first" >&2; exit 1; } +done + +# The first entry's parameters, exactly as the ISO boots them. +cmdline="$(sed -n 's|^[[:space:]]*linux /boot/vmlinuz-sigma ||p' "$menu" | head -n 1)" +[ -n "$cmdline" ] || { echo "boot-test: no vmlinuz-sigma entry in $menu" >&2; exit 1; } + +accel="" +[ -w /dev/kvm ] && accel="-enable-kvm -cpu host" + +mkdir -p "${log%/*}" +: > "$log" +# shellcheck disable=SC2086 # $accel is a list of options +qemu-system-x86_64 $accel -m 4096 -smp 2 -display none -no-reboot \ + -cdrom "$iso" -kernel "$boot/vmlinuz-sigma" -initrd "$boot/initramfs-sigma" \ + -append "$cmdline console=ttyS0" -serial "file:$log" & +qemu=$! + +fail() { + kill "$qemu" 2>/dev/null || true + echo "boot-test: $*" >&2 + echo "--- last lines of the serial console ($log):" >&2 + tail -n 60 "$log" >&2 + exit 1 +} + +echo "Booting ${iso} ($([ -n "$accel" ] && echo KVM || echo 'no KVM, slow')); waiting up to ${limit}s for \"$marker\"" +waited=0 +until grep -q "$marker" "$log"; do + grep -q "Kernel panic" "$log" && fail "kernel panic" + kill -0 "$qemu" 2>/dev/null || fail "QEMU exited before the system finished booting" + [ "$waited" -lt "$limit" ] || fail "no \"$marker\" after ${limit}s" + sleep 5 + waited=$((waited + 5)) +done + +kill "$qemu" 2>/dev/null || true +echo "Booted in about ${waited}s:" +grep -m 1 "Linux version" "$log" || true +grep -m 1 "OpenRC" "$log" || true diff --git a/.github/workflows/build-iso.yml b/.github/workflows/build-iso.yml index f96952b..333edba 100644 --- a/.github/workflows/build-iso.yml +++ b/.github/workflows/build-iso.yml @@ -1,28 +1,155 @@ -name: Build ISO +name: CI -on: [workflow_dispatch] +# Every push and pull request runs the tests. The full ISO build (hours: the +# kernel alone is most of it) runs for master, for version tags, and on +# demand: Actions → CI → Run workflow, or `gh workflow run build-iso.yml --ref `. +# A version tag (git tag v1.2 && git push --tags) also publishes the ISO as a +# GitHub release, once it has booted in the boot test. +on: + push: + branches: ["**"] + tags: ["v*"] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +# A newer push to the same branch supersedes a build in progress. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }} jobs: + test: + name: Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + # In Alpine, like the build: the APKBUILD reader runs them through its sh. + - name: Unit tests and build plan + run: > + docker run --rm -v "$PWD:/app" -w /app alpine:edge + sh -c 'apk add --no-cache ruby ruby-minitest >/dev/null && ruby test/run.rb && ./sigma build --plan' + iso: + name: ISO + needs: test + if: > + github.event_name == 'workflow_dispatch' || + github.ref == 'refs/heads/master' || + startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest + timeout-minutes: 355 # the hosted runner's hard limit is 360 steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v7 + with: + persist-credentials: false - # A kernel build needs ~20 GB; the runner image ships with less free. + # The kernel's build tree peaks at ~20 GB and the image adds ~10 GB; the + # runner starts with less free than that. - name: Free disk space run: | - sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL + df -h / + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /usr/local/.ghcup \ + /opt/hostedtoolcache/CodeQL /usr/local/share/boost /usr/share/swift \ + /usr/local/share/powershell /usr/local/share/chromium /usr/lib/jvm + sudo docker image prune --all --force > /dev/null df -h / - # The same command as locally: sigma builds the container image and - # runs the whole pipeline inside it (privileged, for mounts and chroot). + - name: Ruby + run: command -v ruby || { sudo apt-get update -q && sudo apt-get install -y -q ruby; } + + # Compiled objects from earlier runs, the kernel's especially. Saved + # under a new key every run; the restore takes the most recent one. + - name: Restore ccache + uses: actions/cache/restore@v6 + with: + path: cache/ccache + key: ccache-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: ccache- + + # Same command as on a workstation: sigma builds its container image and + # runs the pipeline in it. All cores are ours here, and the ccache cap + # keeps the saved cache within the repository's 10 GB of Actions cache. + # The signing key is made fresh for this run and never leaves it. - name: Build - run: ./sigma build + id: build + timeout-minutes: 320 + run: ./sigma build --set jobs=$(nproc) --set packages.ccache_size=5G + + # On a timeout the step's process dies but its container may not. + - name: Stop the build container + if: always() + run: docker ps -q | xargs -r docker kill + + # Also after a failure or a timeout: the next run picks up where this one stopped. + - name: Save ccache + if: always() && steps.build.outcome != 'skipped' + uses: actions/cache/save@v6 + with: + path: cache/ccache + key: ccache-${{ github.run_id }}-${{ github.run_attempt }} + + - name: Checksum + run: cd out && sha256sum sigma-linux.iso | tee sigma-linux.iso.sha256 - name: Upload ISO - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: sigma-linux - path: out/sigma-linux.iso + path: | + out/sigma-linux.iso + out/sigma-linux.iso.sha256 compression-level: 0 + retention-days: 14 + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + sudo apt-get update -q && sudo apt-get install -y -q --no-install-recommends qemu-system-x86 + + - name: Boot test + timeout-minutes: 15 + run: sh .github/boot-test.sh + + - name: Upload logs + if: failure() || cancelled() + uses: actions/upload-artifact@v7 + with: + name: logs + path: | + cache/logs/ + !cache/logs/latest + if-no-files-found: ignore + retention-days: 7 + + release: + name: Release + needs: iso + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v8 + with: + name: sigma-linux + path: out + + - name: Publish + env: + GH_TOKEN: ${{ github.token }} + run: | + size=$(stat -c %s out/sigma-linux.iso) + if [ "$size" -ge 2147483648 ]; then + echo "::error::The ISO is $((size / 1048576)) MiB; GitHub release assets must be under 2 GiB." + exit 1 + fi + gh release create "$GITHUB_REF_NAME" out/sigma-linux.iso out/sigma-linux.iso.sha256 \ + --repo "$GITHUB_REPOSITORY" --title "Sigma Linux $GITHUB_REF_NAME" --generate-notes diff --git a/PLAN.md b/PLAN.md index bfeeb40..44822bd 100644 --- a/PLAN.md +++ b/PLAN.md @@ -193,8 +193,8 @@ Port the four stages onto the toolkit, with the selection grammar and freshness abuild signing keys (`keys/`) - [x] Docker dispatch from the host (`build` / `shell` / everything), cross-arch binfmt setup kept - [x] Host-owned outputs (no more root-owned `cache/` / `out/` on the host) -- [~] CI workflow rewritten to run the same `./sigma build` in a privileged container (can't be run from - here; mirrors the local invocation exactly, plus freeing runner disk space) +- [x] CI workflow rewritten to run the same `./sigma build` in a privileged container; grown into the full + CI/CD of Phase 7 - [x] **Check:** image builds; `sigma shell` works; `sigma pkg initrdbo` builds a package end to end in the container; a second run is a no-op; editing the APKBUILD triggers a rebuild. Also verified: a `pkgrel` bump builds `-r1` and prunes `-r0` from the signed index (and back); @@ -229,45 +229,43 @@ Port the four stages onto the toolkit, with the selection grammar and freshness them anyway) - [x] README: build documentation - [x] `sigma run --disk FILE [--no-iso]` to try the installer in QEMU (replaces `start*.sh`) -- [ ] **Check:** the runbook below, run by hand, one step at a time +- [~] **Check:** the runbook below. Steps 0-5 done on the workstation; the full build and a boot moved to + CI (Phase 7), so the workstation only runs the light steps and, when it can be spared, the VM test. #### Runbook You run these; I don't. Nothing heavy runs in parallel. **Stop at the first step that doesn't look as described** and send me the error box (plus `cache/logs/latest//.log` if it names one). -Steps 1-4 are light; 5 is the long one. -0. **Protect the work.** Commit and push (see the chat), and ideally check the SSD's health: - `doas smartctl -a /dev/nvme0` (look at *Media and Data Integrity Errors*, *Percentage Used*, *Available Spare*). +0. ✅ **Protect the work.** Committed and pushed (`claude-rework`); SSD health checked: no media errors, + 5% used. TRIM enabled through dm-crypt (Phase 7). -1. **No Docker yet.** `ruby test/run.rb` → `23 runs, … 0 failures`. Then `./sigma build --plan` → four - stages; `packages` says 11 packages, 8 to build (initrdbo, biblegen and sigma-sent were built before the - crash). `./sigma status` → a table, nothing broken. +1. ✅ **No Docker yet.** Tests pass; `--plan` and `status` work. -2. **Container, and leftovers from the crash.** `doas rc-service docker start`, then - `docker run --rm -v "$PWD/cache:/c" alpine:edge rm -rf /c/grubtest /c/build/sigma/linux-sigma` - (~7 GB of root-owned files). `./sigma shell` should drop you into `/app` as root; `exit`. +2. ✅ **Leftovers from the crash** removed (`cache/grubtest`, the interrupted kernel tree). -3. **Small packages.** `./sigma pkg initrdbo sigma-conf biblegen` → `sigma-conf` builds (✓, seconds; it - has the new GRUB files); `initrdbo` and `biblegen` show ○ up to date. That proves the per-package - freshness survived the crash. +3. ✅ **Small packages.** `initrdbo`, `sigma-conf`, `biblegen`, `sigma-sent` built. -4. **The rest except the kernel.** `./sigma pkg rdbo-archives sigma-firacode-nerd sigma-flat-remix-gtk - sigma-material-symbols sigma-sent sigma-vt323 sigma-wvkbd` → seven ✓. +4. **The rest except the kernel.** Covered by the CI build (step 6); locally only needed before a local + `sigma build`: `./sigma pkg rdbo-archives sigma-firacode-nerd sigma-flat-remix-gtk + sigma-material-symbols sigma-vt323 sigma-wvkbd` → six ✓. -5. **The kernel, alone.** Nothing else running on the machine. For the first time, go easier than the - default: `./sigma pkg linux-sigma --set jobs=6`. Expect 30-90 minutes; it peaks around 15-20 GB of disk - (`df -h /` now and then is wise) and cleans its build tree up when it succeeds. If the desktop gets - sluggish, Ctrl-C is safe: nothing is mounted during package builds, and the next run starts the package over. +5. ✅ **The kernel, alone.** `linux-sigma` 7.3.1 built (two fixes on the way, Phase 7), installed on the + workstation and booted. -6. **Root filesystem.** `./sigma build rootfs` → `prepare`, `install 237 packages` (downloads ~1-2 GB the - first time; cached after), `configure`. Then: `./sigma status` → packages and rootfs up to date. +6. **Full build, in CI.** Push, then run the workflow on the branch: + `gh workflow run build-iso.yml --ref claude-rework` (or Actions → CI → Run workflow). Expect a few hours, + mostly the kernel on 4 cores. Green means: every package, rootfs, squashfs and ISO built, and the ISO + booted in QEMU to the end of the default runlevel (`.github/boot-test.sh`). The ISO is the run's + `sigma-linux` artifact; on failure the `logs` artifact has every step's log. -7. **Image.** `./sigma build squashfs..` → squashfs (several minutes with xz) and the ISO in `out/`. +7. **Second CI run** (re-run the workflow): the `linux-sigma` line in the Build step should show a high + `ccache hit …%`, and the run should be much shorter. That verifies the ccache fix. -8. **Boot it** (needs QEMU on the host: `doas apk add qemu-system-x86_64 qemu-img qemu-ui-gtk ovmf`). - `./sigma run` (BIOS) and `./sigma run --uefi`: the Sigma menu (wallpaper, 640x480), boots to the live - desktop. Log in as `user` / `pass`. +8. **Boot it with a screen** (when the workstation can spare a VM; needs QEMU: + `doas apk add qemu-system-x86_64 qemu-img qemu-ui-gtk ovmf`). Put the CI artifact's ISO at + `out/sigma-linux.iso`, then `./sigma run` (BIOS) and `./sigma run --uefi`: the Sigma menu (wallpaper, + 640x480), boots to the live desktop. Log in as `user` / `pass`. 9. **Install test, in the VM only.** `./sigma run --uefi --disk cache/vm.qcow2` → in the live system: `doas setup-sigma`, disk `/dev/nvme0n1` (the VM disk is NVMe, like your machine), full-disk encryption @@ -281,17 +279,53 @@ Steps 1-4 are light; 5 is the long one. the default entry still boots. - Optional: repeat with encryption **n** on a fresh disk (`rm cache/vm.qcow2`). -10. **Incremental behaviour.** `./sigma build` → "Everything is up to date". `./sigma build iso` → only the - iso stage runs. - -11. **Optional, last: migrate your own machine's GRUB.** This touches your real bootloader, so dry-run first: - 1. `./sigma pkg sigma-conf`, then trust the key once: `doas cp cache/keys/*.pub /etc/apk/keys/`. - 2. `doas apk add --repository "$PWD/cache/repo/sigma" sigma-conf` - 3. `doas sigma-grub-setup --no-update`, then read `/etc/default/grub`: `GRUB_DEVICE_UUID` must equal the +10. **Incremental behaviour** (after a local full build only). `./sigma build` → "Everything is up to + date". `./sigma build iso` → only the iso stage runs. + +11. **Migrate your own machine's GRUB** (light: no build beyond `sigma-conf`, seconds). The installed + `sigma-conf` predates the theme files, so the new one goes in first. This touches your real + bootloader, so dry-run first: + 1. `./sigma pkg sigma-conf` (Docker running). The key is already trusted from the kernel install. + 2. `doas apk add -u --repository "$PWD/cache/repo/sigma" sigma-conf` + 3. `doas rm /boot/vmlinuz-sigma.old_*`: grub-mkconfig lists every `vmlinuz-*`, and that one has no + matching initramfs (or modules), so its entry couldn't boot. + 4. `doas sigma-grub-setup --no-update`, then read `/etc/default/grub`: `GRUB_DEVICE_UUID` must equal the `root=UUID=` in your `/proc/cmdline`. - 4. `doas grub-mkconfig -o /tmp/grub.cfg.new` and compare with `/boot/grub/grub.cfg`: same root UUID, + 5. `doas grub-mkconfig -o /tmp/grub.cfg.new` and compare with `/boot/grub/grub.cfg`: same root UUID, `rootfstype=ext4`, the colours, the wallpaper. - 5. Only then `doas update-grub` (it keeps the old file as `grub.cfg.backup`). + 6. Only then `doas update-grub` (it keeps the old file as `grub.cfg.backup`). From here on, kernel + updates regenerate the themed menu by themselves; `sigma-grub-setup` removed the + `disable_trigger` guard. + 7. The generated menu is "Sigma Linux" plus "Advanced options for Sigma Linux" (the other kernels). For + a flat list of every kernel instead, like the hand-made menu, add `GRUB_DISABLE_SUBMENU=true` below + the "Local overrides" line of `/etc/default/grub` and run `doas update-grub`. + +### Phase 7: After the first real builds +What building and installing the 7.3 kernel for real turned up, and the CI/CD that takes the heavy +builds off the workstation. + +- [x] Kernel 7.3 failed on musl in a build-time self-test (`insn_sanity`: a uapi header uses + `__attribute_const__`, which only glibc defines). `CONFIG_X86_DECODER_SELFTEST` off; it's a + decoder test for kernel developers, nothing in the shipped kernel depends on it +- [x] The error box shows the **first** error in the output, with GCC's include chain (parallel make + keeps going for thousands of lines after it), and drops the build-tree prefix from paths +- [~] ccache across kernel builds: the rebuild got ~3% hits. The kernel compiles its GCC plugin + (latent entropy) afresh in each build tree and loads it into nearly every compile; ccache keyed + the plugin by mtime. Now `compiler_check=content`, cap 10 GiB (`packages.ccache_size`), and each + package's line shows its hit rate. **Verify:** runbook step 7 +- [x] `smartmontools` and `nvme-cli` in the `minimal` profile +- [x] Workstation: TRIM enabled through dm-crypt (`cryptsetup refresh --allow-discards --persistent`), + first `fstrim` done; the GRUB guard moved to the file the trigger actually reads +- [~] CI/CD (`.github/workflows/build-iso.yml`): + - tests (in Alpine, plus `sigma build --plan`) on every push and pull request + - the full ISO build for master, version tags and on demand, with ccache carried between runs + (saved even when a build fails or times out, so a re-run continues) + - a boot test: the ISO's kernel and initramfs in QEMU with KVM and a serial console, the ISO as the + CD-ROM; passes when OpenRC reaches `Starting local` + - on a `v*` tag, a GitHub release with the ISO and its SHA-256, after the boot test passes + - the signing key is generated per run and never stored, cached or uploaded + + **Verify:** runbook steps 6-7 (nothing of it can run on the workstation) --- @@ -305,8 +339,12 @@ Steps 1-4 are light; 5 is the long one. - anything that partitions, formats or opens LUKS runs inside a QEMU VM, never against the host kernel; - one heavy job at a time. -- **Disk space.** A kernel build peaks around 15-20 GB; the rootfs, squashfs and ISO add ~10 GB. `cache/grubtest/` and the interrupted - `cache/build/sigma/linux-sigma/` (~7 GB, root-owned) still need removing through a container. -- **CI can't be exercised locally**; the workflow is written to mirror the local container invocation. +- **Disk space.** A kernel build peaks around 23 GB; the rootfs, squashfs and ISO add ~10 GB. +- **CI can't be exercised locally**; the workflow mirrors the local container invocation, and the first + runs (runbook steps 6-7) are its test. Hosted runners have 4 cores and a 6-hour limit: a cold kernel + build should fit, and if it ever doesn't, the saved ccache lets a re-run finish. +- **Release assets must be under 2 GiB**; the release job checks and fails clearly if the ISO grows past it. +- **initrdbo keeps every old initramfs** (`/boot/initramfs-.old_`, ~20 MB per kernel + update) and never deletes them; `/boot` (1 GB) fills up after a few dozen updates unless they're pruned. - Kernel updates on an installed system: the initramfs name is per-flavor (`initramfs-sigma`), so the order in which the initrdbo and grub triggers run doesn't matter for an existing kernel. diff --git a/README.md b/README.md index 11bf0a2..b61b9f8 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,10 @@ Packages rebuild when anything in their `packages//` directory changes. To The boot menu's look (resolution, wallpaper, console colours) lives in `packages/sigma-conf/rootfs/usr/share/sigma/grub/grub.conf`, shared by the ISO and installed systems. On an installed system, `sigma-grub-setup` hooks it into `/etc/default/grub`, so the menu keeps its theme when kernel updates regenerate it. Installs made before this fix can run it once to migrate: `doas sigma-grub-setup`. +## CI + +GitHub Actions (`.github/workflows/build-iso.yml`) runs the tests on every push and pull request. The full ISO build runs for `master`, for version tags, and on demand (`gh workflow run build-iso.yml --ref `). After building, it boots the ISO in QEMU (`.github/boot-test.sh`) and uploads it as the run's `sigma-linux` artifact. Pushing a `v*` tag also publishes the ISO as a GitHub release. Builds carry ccache between runs, so a rebuild after a kernel bump only recompiles what changed. + ## Resources A full build needs about 35 GB of free disk space (the kernel alone takes 15-20 GB while it builds). `jobs: auto` uses one core less than the machine has, and at most one job per 1.5 GiB of RAM, so the system stays usable. Run the tests with `ruby test/run.rb`. diff --git a/profiles/minimal.list b/profiles/minimal.list index 6446bab..4effd13 100644 --- a/profiles/minimal.list +++ b/profiles/minimal.list @@ -158,6 +158,10 @@ lm-sensors pciutils lscpu +## Disk Health +# smartctl -a /dev/nvme0, nvme smart-log /dev/nvme0 +smartmontools nvme-cli + ## Misc wl-clipboard wlr-randr From 3e22eaf3d1972bae2c574eb0d5d8a177ac65fc35 Mon Sep 17 00:00:00 2001 From: Rdbo Date: Sat, 31 Oct 2026 13:23:03 +0000 Subject: [PATCH 6/6] grub fixes --- PLAN.md | 16 ++++++++--- packages/sigma-conf/APKBUILD | 2 +- .../rootfs/usr/sbin/sigma-grub-setup | 27 ++++++++++++++++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/PLAN.md b/PLAN.md index 44822bd..189878e 100644 --- a/PLAN.md +++ b/PLAN.md @@ -286,9 +286,14 @@ described** and send me the error box (plus `cache/logs/latest//.lo `sigma-conf` predates the theme files, so the new one goes in first. This touches your real bootloader, so dry-run first: 1. `./sigma pkg sigma-conf` (Docker running). The key is already trusted from the kernel install. - 2. `doas apk add -u --repository "$PWD/cache/repo/sigma" sigma-conf` - 3. `doas rm /boot/vmlinuz-sigma.old_*`: grub-mkconfig lists every `vmlinuz-*`, and that one has no - matching initramfs (or modules), so its entry couldn't boot. + 2. `doas apk add -u --repository "$PWD/cache/repo/sigma" sigma-conf`, then `apk info -v sigma-conf` → + `sigma-conf-1.0-r2` and `command -v sigma-grub-setup` finds it. (The installed one was `1.0-r0`, the + same version as the first rework build, which apk wouldn't have replaced; hence the `pkgrel` bump.) + 3. Move kernel backups out of `/boot`'s top level: + `doas mkdir -p /boot/backup && doas mv /boot/vmlinuz-sigma.old_* /boot/backup/`. grub-mkconfig + counts every `/boot/vmlinuz-*` as a kernel (it skips only `.dpkg-*`, `.rpmsave`/`.rpmnew` and `.sig` + names), and that one has no matching initramfs (or modules), so its entry couldn't boot. initrdbo's + `initramfs-*.old_*` backups can stay; GRUB never lists them. 4. `doas sigma-grub-setup --no-update`, then read `/etc/default/grub`: `GRUB_DEVICE_UUID` must equal the `root=UUID=` in your `/proc/cmdline`. 5. `doas grub-mkconfig -o /tmp/grub.cfg.new` and compare with `/boot/grub/grub.cfg`: same root UUID, @@ -313,6 +318,11 @@ builds off the workstation. (latent entropy) afresh in each build tree and loads it into nearly every compile; ccache keyed the plugin by mtime. Now `compiler_check=content`, cap 10 GiB (`packages.ccache_size`), and each package's line shows its hit rate. **Verify:** runbook step 7 +- [x] `sigma-grub-setup` on the workstation picked the UUID of the ext4 *inside* LUKS: there + `/dev/mapper/cryptroot` is a device node, not a link to `/dev/dm-N`, so the lookup of the partition + under it found nothing. It now finds `dm-N` by the mapping's name, refuses to leave a device-mapper + device as `root=`, and on a running system refuses a UUID other than the one it booted with + (nothing is written in that case). `sigma-conf` 1.0-r2 (r1 was the first with the theme files) - [x] `smartmontools` and `nvme-cli` in the `minimal` profile - [x] Workstation: TRIM enabled through dm-crypt (`cryptsetup refresh --allow-discards --persistent`), first `fstrim` done; the GRUB guard moved to the file the trigger actually reads diff --git a/packages/sigma-conf/APKBUILD b/packages/sigma-conf/APKBUILD index 46e1fb0..49c0663 100644 --- a/packages/sigma-conf/APKBUILD +++ b/packages/sigma-conf/APKBUILD @@ -3,7 +3,7 @@ pkgname="sigma-conf" pkgver="1.0" -pkgrel=0 +pkgrel=2 pkgdesc="Sigma Linux configuration files" url="https://github.com/rdbo/sigma-linux" arch="noarch" diff --git a/packages/sigma-conf/rootfs/usr/sbin/sigma-grub-setup b/packages/sigma-conf/rootfs/usr/sbin/sigma-grub-setup index 5824427..7966c70 100755 --- a/packages/sigma-conf/rootfs/usr/sbin/sigma-grub-setup +++ b/packages/sigma-conf/rootfs/usr/sbin/sigma-grub-setup @@ -40,7 +40,18 @@ mounted_root() { # under a device-mapper device, not the mapped filesystem. backing_device() { dev="$(readlink -f "$1")" - for slave in "/sys/block/${dev##*/}/slaves/"*; do + name="${dev##*/}" + # With udev, /dev/mapper/ links to /dev/dm-N; without it, it's a + # device node of its own. Either way, find dm-N by the mapping's name. + if [ ! -e "/sys/class/block/$name" ]; then + for dm in /sys/class/block/dm-*; do + if [ "$(cat "$dm/dm/name" 2>/dev/null)" = "$name" ]; then + name="${dm##*/}" + break + fi + done + fi + for slave in "/sys/class/block/$name/slaves/"*; do if [ -e "$slave" ]; then echo "/dev/${slave##*/}" return @@ -70,17 +81,31 @@ done [ "$(id -u)" = 0 ] || die "run as root" [ -r "$THEME/grub.conf" ] || die "$THEME/grub.conf is missing; is sigma-conf installed?" +detected="" if [ -z "$root_dev" ]; then set -- $(mounted_root) root_dev="$1" [ -n "$fstype" ] || fstype="$2" + detected=yes fi [ -n "$fstype" ] || die "--fstype is required with --root" +mapped="$root_dev" root_dev="$(backing_device "$root_dev")" [ -b "$root_dev" ] || die "$root_dev is not a block device" +case "$root_dev" in + /dev/mapper/*|/dev/dm-*) + die "could not find the partition under $root_dev; pass it with --root" ;; +esac uuid="$(device_uuid "$root_dev")" [ -n "$uuid" ] || die "could not read the UUID of $root_dev" +# On a running system, the root= it booted with is known to work: refuse to +# write a different one unless told which device to use. +booted="$(sed -n 's/.*root=UUID=\([^ ]*\).*/\1/p' /proc/cmdline)" +if [ -n "$detected" ] && [ -n "$booted" ] && [ "$booted" != "$uuid" ]; then + die "this system booted with root=UUID=$booted, but $root_dev (under $mapped) has UUID $uuid; nothing was changed. Pass --root DEVICE to choose." +fi + # A hand-written /etc/default/grub is kept aside, once. if [ -f "$DEFAULTS" ] && ! grep -qF "$MARKER" "$DEFAULTS"; then cp "$DEFAULTS" "$DEFAULTS.pre-sigma"