Compare commits

68 Commits

Author SHA1 Message Date
Storm Dragon 5ec1d7727d Steam now offered in x86_64 installer. Fix up the rhvoice dictionary copy. A few minor cleanups. Rename pi images to more clearly show they support both Raspberry Pi 4 and 5. 2026-06-23 19:40:06 -04:00
Storm Dragon 426069f36a Xlibre build script updated. 2026-06-21 16:21:37 -04:00
Storm Dragon 4aeff109f7 Latest fixes for x86_64 installer. Updated README for Pi images. 2026-06-21 12:56:38 -04:00
Storm Dragon 46e3f0d084 Make sure GUI DISPLAY is set. 2026-06-06 14:22:07 -04:00
Storm Dragon 13e3ce64fd Fix bug in select loop so multiple options do not appear on the same line. 2026-06-05 13:16:48 -04:00
Storm Dragon f6e2e2f4c8 added sas to both images. A few minor updates to image generation for pi images. 2026-06-04 23:52:44 -04:00
Storm Dragon c94a71e0d6 Missed some deamon to daemon updates. This shoulf make Fenrir work with the installer automatically again, switching modes, etc. 2026-06-03 00:34:19 -04:00
Storm Dragon f1dfe1737b qemu-boot.sh now checks for required packages. 2026-06-01 19:37:53 -04:00
Storm Dragon eb36d6d976 Fixed typo, or loss of current thought or something lol. 2026-05-24 22:19:14 -04:00
Storm Dragon 040eecca09 Build script compresses the image and generates the sha1sum now. 2026-05-24 21:27:07 -04:00
Storm Dragon 973b2573c8 Couple improvements to the install-stormux script. make sure hostname doesn't contain spaces. Try to make sure current information for mirrors even if current iso is a bit out of date. 2026-05-23 06:44:20 -04:00
Storm Dragon 9a0a6fef00 Update install-stormux script. 2026-05-11 21:34:43 -04:00
Storm Dragon 9496559875 A couple of management scripts added. 2026-04-03 07:27:38 -04:00
Storm Dragon ddab0d827f Update xlibre dummy with vt srcinfo 2026-04-02 19:28:40 -04:00
Storm Dragon aacdf8eb4a Rebase xlibre dummy with vt packaging 2026-04-02 19:28:36 -04:00
Storm Dragon a6c65ca973 Fix XLibre updater package order 2026-04-02 19:20:12 -04:00
Storm Dragon 19fd4b1ed4 Add XLibre updater design spec 2026-04-02 18:21:21 -04:00
Storm Dragon 185d098bdd First pass at custom mount options. Everything should be mounted on /mnt. 2026-02-26 14:35:13 -05:00
Storm Dragon 140a1c8f88 x86_64 iso file name more closely match the format of other Stormux images. Updated rhvoice fixes. 2025-12-21 15:09:57 -05:00
Storm Dragon 695b9e2f75 Added brltty udev.rules so it hopefully works more reliably. A few other minor tweaks. 2025-12-21 14:43:40 -05:00
Storm Dragon acf8327949 Implement sas for x86_64. 2025-12-20 23:52:16 -05:00
Storm Dragon 08123ea6e9 Initial implementation of Stormux Assistance Service. 2025-12-20 19:59:37 -05:00
Storm Dragon db6a4880b3 Added vi as alternate editor. 2025-12-20 06:25:23 -05:00
Storm Dragon 1a36316cfd Removed openssh for debuggin, finally remembered to re-add it. 2025-12-20 05:26:26 -05:00
Storm Dragon ccec75c4c5 Structural changes to the build script. Cleaner generation without having to edit an existing image. 2025-12-20 05:00:29 -05:00
Storm Dragon d8056278d2 Minor improvements to install-stormux. Added xdotool dependency for I38. 2025-12-19 02:56:48 -05:00
Storm Dragon 3d62262849 RHVoice fixes added. 2025-12-19 02:15:05 -05:00
Storm Dragon fbd1c3f671 A few stability improvements. Added Fenrir ssh announcement script. 2025-12-18 14:02:45 -05:00
Storm Dragon f4e91f7ee2 Updates to the x86_64 image creation. 2025-12-17 16:09:22 -05:00
Dane Stange 3f2dc0b492 README.md for the pi4 build-stormux.sh script 2025-12-01 12:41:56 -05:00
Storm Dragon aceda3f1f8 Add missing packages and a few other tweaks to installer. 2025-11-29 19:40:46 -05:00
Storm Dragon 013b3d11ac Moving toward working gui installation. 2025-11-29 03:54:28 -05:00
Storm Dragon dfd2609461 Basic installation now working, system comes up talking. 2025-11-25 18:07:51 -05:00
Storm Dragon b4704295ec Added missing package. 2025-11-25 01:34:11 -05:00
Storm Dragon 855be1afcd More work on the installer, not quite ready yet, but at a good save point. 2025-11-24 21:29:19 -05:00
Storm Dragon 3df5017766 Lots of improvements to the installer for x86_64. 2025-11-24 01:48:18 -05:00
Storm Dragon 34f1e24e10 Updated motd for both x86_64 and aarch64. 2025-11-23 13:21:57 -05:00
Storm Dragon 3176d951b6 Removed some unnecessary instructions from the first boot script. 2025-11-23 01:28:05 -05:00
Storm Dragon 370d351887 Trim down the configure-stormux stub a bit more. It's actually tiny now for what is needed. 2025-11-23 00:37:14 -05:00
Storm Dragon c31612ca51 Improve messages for x86_64, replace configure stormux with the installer. 2025-11-23 00:24:46 -05:00
Storm Dragon 1964121c8c Configure for xlibre, set up accessibility variables, update installation script. 2025-11-22 21:51:36 -05:00
Storm Dragon 68cbe3c0bb Updates to the x86_64 image build. 2025-11-20 15:32:22 -05:00
Storm Dragon a7e28c018f Gstreamer packages added for Fenrir sound support. 2025-11-20 15:29:21 -05:00
Storm Dragon 3c5490ea24 Initial build setup for x86_64 Stormux. 2025-11-19 03:26:25 -05:00
Storm Dragon fffe426d29 Remove the pam.d/nodm file because it's now included in the package in the repository. 2025-08-20 13:59:15 -04:00
Storm Dragon 45b3c230e1 Fixed bug in disk resize code. 2025-08-19 17:08:29 -04:00
Storm Dragon fa9585a0e1 Removed noisy volume debug statement. 2025-08-19 16:45:51 -04:00
Storm Dragon 5e144e7955 Initial disk resize prompt should be less verbose. 2025-08-19 16:22:54 -04:00
Storm Dragon e39c1cedec Finally figure out why volume adjustment wasn't happening first thing, it was in the wrong file. This should finally be resolved once and for all. 2025-08-19 16:02:12 -04:00
Storm Dragon 8a7fb83be3 Lots of reorganization. Added stormux package repository. Removed unneeded packages. Switched to systemd management of logging to ram. Use existing tools to resize the image to fill the device, no more growpartfs. No packages installed from AUR any more. 2025-08-19 15:18:04 -04:00
Storm Dragon f80ea1e056 Move volume check to run first, before anything else. 2025-07-13 15:49:16 -04:00
Storm Dragon 696ddd9bfb Right alt key should now work as expected in the console. 2025-05-11 19:31:14 -04:00
Storm Dragon e0806e51da Simplify the internect connection dialog. 2025-05-08 04:54:44 -04:00
Storm Dragon 3612787274 More improvements to rename-user.sh 2025-04-26 16:54:35 -04:00
Storm Dragon 41a0592f20 Update linger file. with the rename. 2025-04-26 04:14:26 -04:00
Storm Dragon 3ebb6f36cf Added convience rename-user script in root's home directory. You still need a temp user if doing this from ssh. 2025-04-26 04:09:03 -04:00
Storm Dragon c88bd17709 Added volume check to the beginning of configure-stormux stub. 2025-04-25 16:07:11 -04:00
Storm Dragon c1af07bf18 2 characters over the limit for labels, so fixed it. 2025-04-22 21:36:31 -04:00
Storm Dragon a88a386ef4 Attempt to make images bootable no matter the source, micro sd, nvme, etc. 2025-04-22 21:27:34 -04:00
Storm Dragon 034c561a53 Moved the journald configuration to a file in it's drop in directory. Also, explicitly set volatile storage to cut down on SD card writes. 2025-02-09 17:36:02 -05:00
Storm Dragon 2108f02719 Removed some packages that can be installed after the build is completed. 2025-01-24 12:21:02 -05:00
Storm Dragon 06282f5ea8 Fixed the removal of packages at the end of image creation. 2025-01-17 01:35:21 -05:00
Storm Dragon 6bb5267b91 Chroot message just because I'm paranoid now. 2025-01-04 20:47:18 -05:00
Storm Dragon b87647756f After scary encounter with some problems, clearly indicate when build system is in chroot. 2025-01-04 20:34:49 -05:00
Storm Dragon 505028ab44 Dependencies added for package building and removed at completion. 2025-01-04 19:46:08 -05:00
Storm Dragon 9657719613 In process of moving headless X driver stuff to configure-stormux. 2025-01-04 19:19:44 -05:00
Storm Dragon 3e4c4d18ee Revert to old working headless driver. 2025-01-04 19:15:52 -05:00
Storm Dragon 9beea608ed Python accessible_output2 library added. 2025-01-04 15:40:08 -05:00
149 changed files with 8408 additions and 258 deletions
+8
View File
@@ -0,0 +1,8 @@
AGENTS.md
CLAUDE.md
*.qcow2
*.img
*.sha1sum
*.xz
*.zst
scripts/xlibre-video-dummy-with-vt/
+97
View File
@@ -0,0 +1,97 @@
# Stormux Pi Image Builder
Stormux provides an Arch-based Raspberry Pi image that already includes Fenrir, Orca, and other screen-reader friendly defaults. This repository hosts the scripts maintained by the Stormux community (originally crafted by Storm) so anyone can rebuild the image, customize the overlay, and share accessible Pi spins.
## Purpose & Scope
This repo captures the complete build pipeline for the Stormux Raspberry Pi images: bootstrapping Arch Linux ARM, layering the accessible defaults, installing Fenrir and helper tools, and producing a compressed ready-to-flash image. Running the script yourself lets you reproduce the same system the project ships for Raspberry Pi 4/5-class systems, tweak it, and publish new spins without reverse-engineering the original release.
## What You Need
- A 64-bit Arch Linux host (bare metal or VM) with at least 20GB free disk space.
- Root access on that host. The builder partitions loopback devices and must run as root.
- Required packages: `arch-install-scripts`, `dosfstools`, `parted`, `qemu-user-static`, `qemu-user-static-binfmt`, plus standard developer tools that Arch already ships.
- A Raspberry Pi 4, Raspberry Pi 400, Raspberry Pi 5, or compatible board, plus a microSD card (8 GB or larger recommended).
Install dependencies on the build host with:
```bash
sudo pacman -S arch-install-scripts dosfstools parted qemu-user-static qemu-user-static-binfmt
```
## Getting the Source
Clone the canonical repo (or your fork) someplace with plenty of space:
```bash
git clone https://git.stormux.org/storm/stormux.git
cd stormux
```
All build logic lives in `pi4/`. The `pi4/files/` tree mirrors the root filesystem you want inside the image (configs in `etc`, helper scripts in `usr/local/bin`, and so on).
### Where to Run the Builder
The script is designed for an x86_64 Arch host so it can emulate ARM binaries with QEMU while building. You can run it natively on an ARM board (including a Pi) running Arch or Stormux, but expect much longer build times and make sure the same packages are installed. Whatever the CPU, you **must** run as root and ensure `/mnt` is free so the loopback rootfs can mount safely.
## Building Your First Image
1. Become root (`sudo -i`) so the script can create loop devices and mount them.
2. From `/path/to/stormux`, run:
```bash
sudo ./pi4/build/build-stormux.sh -l en_US -s 6
```
- The builder creates an aarch64 image for Raspberry Pi 4/5-class systems.
- `-l en_US` selects the locale (use `es_ES`, `fr_FR`, etc.).
- `-s 6` sets the image size in gigabytes.
- Add `-n my-stormux.img` to override the default filename.
3. Grab coffee. The script bootstraps Arch Linux ARM, provisions packages (Fenrir, Orca, NetworkManager, PipeWire, etc.), copies the overlay from `pi4/files/`, installs the latest `sas`, cleans up, compresses the image, and writes a SHA-1 checksum.
4. When the `build-stormux.sh` command returns, you should have an `.img.xz` file and matching `.sha1sum` in your working directory (for example `stormux-rpi4-5-aarch64-YYYY-MM-DD.img.xz`).
If the build is interrupted, run `sudo umount -R /mnt && sudo losetup -D` to be sure nothing is still mounted.
## Customizing the Image
- **Overlay files**: Add or edit files in `pi4/files/` using the same paths they should have inside the Pi. Example: to add a custom MOTD, edit `pi4/files/etc/motd`.
- **Packages**: Edit the package list inside `pi4/build/build-stormux.sh` (search for `pacman -Su --needed`). Add or remove entries as needed, then rebuild.
- **Package sources**: The build does not fetch or compile packages from the AUR. Packages are installed from the official Arch Linux ARM repositories or the Stormux repository. The finished image includes `yay`, but it is installed as a prebuilt package from the Stormux repository and is not used during the build.
- **First-boot behavior**: Modify `pi4/files/usr/local/bin/configure-stormux` to change the guided setup that runs when the image boots the first time.
Remember to keep permissions sensible (`chmod 755` for scripts, `644` for configs) so rsync copies them correctly.
## Keeping Up with Upstream Updates
Storm and other contributors actively improve these scripts. To sync your local clone with the latest changes:
```bash
cd /path/to/stormux
git pull --ff-only
```
Rebuild after pulling so your custom image inherits any upstream fixes (new packages, service tweaks, security patches). If you maintain your own fork, merge or rebase onto the upstream `main` branch before publishing updated images.
## Flashing and Booting
1. Verify the image:
```bash
ls -lh stormux-rpi4-5-*.img.xz stormux-rpi4-5-*.img.xz.sha1sum
sha1sum -c stormux-rpi4-5-*.img.xz.sha1sum
```
2. Write it to an SD card (replace `/dev/sdX` with your card, not a partition):
```bash
xzcat stormux-rpi4-5-aarch64-*.img.xz | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync
```
3. Insert the card into your Raspberry Pi and power it on. You should hear Fenrir start speaking once the user session loads.
4. Log in using the default credentials (username `stormux`, password `stormux`). Root uses `root`/`root` until you change it.
5. Run `sudo configure-stormux` (or follow the automatic prompt) to finish the guided setup: configure networking via `nmtui`, update the clock, optionally resize the SD card with `growpartfs`, and toggle Fenrirs layout.
## Testing Without Hardware
You can boot the image in a container to verify services before flashing:
```bash
xz -dk stormux-rpi4-5-aarch64-*.img.xz
sudo systemd-nspawn -i stormux-rpi4-5-aarch64-*.img --boot
```
Use `machinectl` to connect to the console and ensure critical services start. For unit files you edit, run `systemd-analyze verify path/to/unit` inside the container or the Pi.
## Troubleshooting Tips
- **Locale or package errors**: Ensure your hosts pacman keyring is up to date (`sudo pacman -Sy archlinux-keyring`).
- **Loop device cleanup**: After failures run `sudo losetup -a` to find stray devices, then `sudo losetup -d /dev/loopX`.
- **Disk too small**: Increase the `-s` argument or remove packages.
- **Accessibility tweaks**: If Fenrir is too quiet or you prefer Orca, install the `orca` package inside the overlay and add it to the autostart configuration in `pi4/files/etc/xdg/autostart/`.
## Sharing Your Image
When you are happy with your customizations:
1. Rebuild so you have a clean `.img.xz` file named clearly.
2. Publish the compressed image, its `.sha1sum`, the exact commit you built from, and any extra scripts you used. The README and `AGENTS.md` in this repo explain the layout so others can reproduce your work.
## Huge Thanks to Storm
Storm did the heavy lifting—curating packages, wiring up Fenrir/Orca defaults, scripting the first-boot assistant, and sharing the whole build process so the community can remix it. This repo exists because accessibility was treated as a first-class feature from day one, and the documentation lets anyone extend that work. If the image improves your Pi experience, consider contributing improvements or telling Storm thanks.
With these steps you can take the same base image Storm released, tweak it to your needs, and ship a fully accessible Arch image for other Pi fans. Happy hacking!
+338 -125
View File
@@ -24,19 +24,116 @@ mounted=1
set -e # Don't want to destroy stuff if this goes majorly wrong.
trap cleanup EXIT # make sure the script cleans up after itself before closing.
# shellcheck disable=SC2329 # compress_image is invoked from finish_build
compress_image() {
local compressedImage="${imageName}.xz"
if [[ ! -s "${imageName}" ]]; then
echo "Image file ${imageName} was not created or is empty."
return 1
fi
echo "Compressing ${imageName} to ${compressedImage}..."
xz -T0 -9 "${imageName}"
echo "Creating SHA-1 checksum for ${compressedImage}..."
sha1sum "${compressedImage}" > "${compressedImage}.sha1sum"
echo "Image build complete: ${compressedImage}"
echo "Checksum: ${compressedImage}.sha1sum"
}
# shellcheck disable=SC2329 # cleanup_image is invoked from cleanup and finish_build
cleanup_image() {
local cleanupStatus=0
cleanup() {
if [[ $mounted -eq 0 ]]; then
umount -R /mnt
partx -d "${loopdev}"
losetup --detach "${loopdev}"
if ! umount -R /mnt; then
cleanupStatus=1
fi
fi
if [[ -n "${imageFileName}" ]]; then
rm "${imageFileName}"
if [[ -n "${loopdev:-}" ]]; then
if ! partx -d "${loopdev}"; then
cleanupStatus=1
fi
if ! losetup --detach "${loopdev}"; then
cleanupStatus=1
fi
fi
# Clean up temporary pacman config directory
if [[ -n "${tmpDir:-}" && -d "${tmpDir:-}" ]]; then
rm -rf "${tmpDir}"
fi
return "$cleanupStatus"
}
# shellcheck disable=SC2329 # cleanup is invoked via trap EXIT
cleanup() {
local status=$? # capture original exit status so failures propagate
local cleanupStatus=0
if ! cleanup_image; then
cleanupStatus=1
fi
if [[ $status -eq 0 && $cleanupStatus -ne 0 ]]; then
echo "Image build commands completed, but cleanup failed."
status=1
fi
exit "$status"
}
finish_build() {
trap - EXIT
if ! cleanup_image; then
echo "Image build commands completed, but cleanup failed."
exit 1
fi
if ! compress_image; then
exit 1
fi
exit 0
}
copy_rhvoice_english_fixes() {
local sourceDir="../files/etc/RHVoice/dicts/English"
local targetDir="/mnt/etc/RHVoice/dicts/English"
if [[ -d "$sourceDir" ]]; then
mkdir -p "$targetDir"
cp -a "$sourceDir/." "$targetDir/"
fi
}
# shellcheck disable=SC2329 # install_sas is emitted into the chroot heredoc with declare -f
install_sas() {
local sasRepo="https://git.stormux.org/storm/sas"
local sasPath="/usr/local/bin/sas"
local tempDir
local installStatus=0
tempDir="$(mktemp -d)"
echo "Installing latest sas..."
if ! git clone --depth 1 "$sasRepo" "$tempDir"; then
rm -rf "$tempDir"
return 1
fi
rm -f "$sasPath"
if ! install -m 755 "$tempDir/sas.py" "$sasPath"; then
installStatus=1
fi
rm -rf "$tempDir"
return "$installStatus"
}
help() {
echo -e "Usage:\n"
echo "With no arguments, build with default parameters."
@@ -50,9 +147,8 @@ help() {
declare -A command=(
[h]="This help screen."
[l:]="Language default is en_US."
[n:]="Image name, default is stormux-pi<32|64>-<yyyy-mm-dd>.img"
[s:]="image size in GB, default is 4."
[v:]="Version of the Raspberry Pi for which you are building. (32|64) default is 64."
[n:]="Image name, default is stormux-rpi4-5-aarch64-<yyyy-mm-dd>.img"
[s:]="image size in GB, default is 6."
)
# Convert the keys of the associative array to a format usable by getopts
@@ -75,14 +171,6 @@ while getopts "${args}" i ; do
exit 1
fi
;;
v)
if [[ "${OPTARG}" =~ ^32|64$ ]]; then
imageVersion="${OPTARG}"
else
echo "Image version must be 32 for 32 bit (armv7h), or 64 for 64 bit (aarch64 default)."
exit 1
fi
;;
*)
exit 1
;;
@@ -90,11 +178,8 @@ while getopts "${args}" i ; do
done
# make sure variables are set, or use defaults.
export imageVersion="${imageVersion:-64}"
export imageSize="${imageSize:-6G}"
imageName="${imageName:-stormux-pi4-${imageVersion}-$(date '+%Y-%m-%d').img}"
imageName="${imageName/-64-/-aarch64-}"
imageName="${imageName/-32-/-armv7h-}"
imageName="${imageName:-stormux-rpi4-5-aarch64-$(date '+%Y-%m-%d').img}"
export imageName
export imageLanguage="${imageLanguage:-en_US.UTF-8}"
@@ -103,6 +188,12 @@ if [[ -e "$imageName" ]]; then
echo "${imageName} exists, please remove or move it for this script to continue."
exit 1
fi
for outputFile in "${imageName}.xz" "${imageName}.xz.sha1sum"; do
if [[ -e "$outputFile" ]]; then
echo "${outputFile} exists, please remove or move it for this script to continue."
exit 1
fi
done
# Make sure this script is ran as root.
if [ "$(whoami)" != "root" ] ; then
@@ -121,93 +212,214 @@ if [[ "$(uname -m)" == "x86_64" ]]; then
exit 1
fi
fi
for i in arch-install-scripts dosfstools parted wget ; do
for i in arch-install-scripts dosfstools parted ; do
if ! pacman -Q $i &> /dev/null ; then
echo "Please install $i before continuing."
exit 1
fi
done
for i in sha1sum xz ; do
if ! command -v "$i" &> /dev/null ; then
echo "Please install ${i} before continuing."
exit 1
fi
done
# Url for the image to be downloaded.
url[32]="http://os.archlinuxarm.org/os/ArchLinuxARM-rpi-armv7-latest.tar.gz"
url[64]="http://os.archlinuxarm.org/os/ArchLinuxARM-rpi-aarch64-latest.tar.gz"
fallocate -l "$imageSize" "$imageName"
loopdev="$(losetup --find --show "${imageName}")"
parted --script "${loopdev}" mklabel msdos mkpart primary fat32 0% 200M mkpart primary ext4 200M 100%
mkfs.vfat -F32 "${loopdev}p1"
mkfs.ext4 -F "${loopdev}p2"
parted --script "${loopdev}" mklabel msdos mkpart primary fat32 0% 512M mkpart primary ext4 512M 100%
mkfs.vfat -F32 -n STRMX_BOOT "${loopdev}p1"
mkfs.ext4 -F -L STRMX_ROOT "${loopdev}p2"
mount "${loopdev}p2" /mnt
mkdir /mnt/boot
mount "${loopdev}p1" /mnt/boot
# Things are mounted now, so set mounted to 0 (bash true)
mounted=0
imageFileName=$(mktemp)
wget "${url[$imageVersion]}" -O "${imageFileName}"
if [[ $imageVersion -eq 32 ]]; then
# Workaround for bsdtar errors when extracting 32 bit image.
set +e
fi
bsdtar -xpf "${imageFileName}" -C /mnt
# Set -e in case it got unset for 32 bit image
set -e
arch-chroot /mnt << EOF
# Set up temporary pacman configuration for ARM bootstrap
echo "Setting up pacman configuration for aarch64..."
tmpDir=$(mktemp -d)
mkdir -p "${tmpDir}/pacman.d"
# Create mirrorlist for Arch Linux ARM
cat > "${tmpDir}/pacman.d/mirrorlist" << 'MIRRORLIST'
Server = http://mirror.archlinuxarm.org/$arch/$repo
Server = http://fl.us.mirror.archlinuxarm.org/$arch/$repo
Server = http://il.us.mirror.archlinuxarm.org/$arch/$repo
Server = http://tx.us.mirror.archlinuxarm.org/$arch/$repo
Server = http://nj.us.mirror.archlinuxarm.org/$arch/$repo
MIRRORLIST
# Create pacman.conf for ARM
cat > "${tmpDir}/pacman.conf" << PACMANCONF
[options]
HoldPkg = pacman glibc
Architecture = aarch64
CheckSpace
SigLevel = Never
LocalFileSigLevel = Never
[core]
Include = ${tmpDir}/pacman.d/mirrorlist
[extra]
Include = ${tmpDir}/pacman.d/mirrorlist
[alarm]
Include = ${tmpDir}/pacman.d/mirrorlist
PACMANCONF
# Bootstrap the system with pacstrap
echo "Running pacstrap to install base system..."
pacstrap -c -G -M -C "${tmpDir}/pacman.conf" /mnt base base-devel linux-rpi raspberrypi-bootloader firmware-raspberrypi archlinuxarm-keyring
# Clean up temporary config
rm -rf "${tmpDir}"
# Copy override files into place before chroot (except skel to avoid conflicts)
cp -rv ../files/boot/* /mnt/boot
cp -rv ../files/var/* /mnt/var
cp -rv ../files/usr/* /mnt/usr
# Copy etc files but exclude skel directory to avoid package conflicts
find ../files/etc -mindepth 1 -maxdepth 1 ! -name skel -exec cp -rv {} /mnt/etc/ \;
PS1="(Chroot) [\u@\h \W] \$" arch-chroot /mnt << EOF
echo "Chroot started."
set -euo pipefail
# Disable pacman sandboxing (doesn't work in chroot)
sed -i '/^\[options\]/a DisableSandbox' /etc/pacman.conf
# set up pacman
pacman-key --init
pacman-key --populate archlinuxarm
pacman -Syy
# Change kernels for aarch64
if [[ "$imageVersion" == "64" ]]; then
pacman -R --noconfirm linux-aarch64 uboot-raspberrypi
pacman -S --noconfirm linux-rpi
# Add stormux repository
echo "Downloading stormux repository key..."
curl -s https://packages.stormux.org/stormux_repo.pub > /tmp/stormux_repo.pub
echo "Adding key to pacman keyring..."
pacman-key --add /tmp/stormux_repo.pub
echo "Locally signing key..."
pacman-key --lsign-key 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82
rm /tmp/stormux_repo.pub
# Add repository before core in pacman.conf
sed -i '/^\[core\]/i[stormux]\nSigLevel = Required DatabaseOptional\nServer = https://packages.stormux.org/\$arch\n' /etc/pacman.conf
# Test mirrors and configure based on responsiveness
echo "Testing mirror responsiveness..."
mirrors=(
"mirror.archlinuxarm.org"
"fl.us.mirror.archlinuxarm.org"
"il.us.mirror.archlinuxarm.org"
"tx.us.mirror.archlinuxarm.org"
"nj.us.mirror.archlinuxarm.org"
)
working_mirrors=()
for mirror in "\${mirrors[@]}"; do
echo "Testing \$mirror..."
if curl -m 5 -f -s "http://\$mirror/aarch64/core/core.db" > /dev/null 2>&1; then
echo " \$mirror: OK"
working_mirrors+=("\$mirror")
else
echo " \$mirror: FAILED"
fi
done
# Create mirrorlist with working mirrors first, then fallback to all mirrors
> /etc/pacman.d/mirrorlist
if [[ \${#working_mirrors[@]} -gt 0 ]]; then
echo "Using \${#working_mirrors[@]} working mirrors"
for mirror in "\${working_mirrors[@]}"; do
echo "Server = http://\$mirror/\\\$arch/\\\$repo" >> /etc/pacman.d/mirrorlist
done
else
echo "No mirrors responded, using all mirrors as fallback"
for mirror in "\${mirrors[@]}"; do
echo "Server = http://\$mirror/\\\$arch/\\\$repo" >> /etc/pacman.d/mirrorlist
done
fi
# Install packages
pacman -Su --needed --noconfirm \
alsa-firmware \
alsa-utils \
base \
base-devel \
bash-completion \
bluez \
bluez-utils \
brltty \
cloud-utils \
cronie \
espeak-ng \
fake-hwclock \
firmware-raspberrypi \
git \
go \
magic-wormhole \
man \
man-pages \
networkmanager \
pipewire \
pipewire-alsa \
pipewire-jack \
pipewire-pulse \
poppler \
python-pyudev \
python-daemonize \
python-evdev \
python-dbus \
python-pyenchant \
python-pyte \
raspberrypi-utils \
realtime-privileges \
rhvoice-voice-bdl \
rng-tools \
rsync \
screen \
sox \
w3m \
wget \
wireless-regdb \
wireplumber \
xdg-user-dirs \
pacman -Syy
# Install all packages (stormux repo has priority since it's listed first)
packages=(
alsa-firmware
alsa-utils
base
base-devel
bash-completion
bluez
bluez-utils
brltty
cronie
curl
dialog
espeak-ng
fake-hwclock
fenrir
firmware-raspberrypi
git
gstreamer
gst-plugins-base
gst-plugins-good
ii
# Keep Pi onboard firmware plus common USB/network chipset firmware without
# pulling in unrelated desktop/server GPU firmware.
linux-firmware-atheros
linux-firmware-broadcom
linux-firmware-mediatek
linux-firmware-realtek
magic-wormhole
man
man-pages
networkmanager
openssh
parted
pipewire
pipewire-alsa
pipewire-jack
pipewire-pulse
poppler
python-dbus
python-gobject
python-pyenchant
python-pyte
python-pyperclip
raspberrypi-utils
socat
realtime-privileges
rhvoice-voice-bdl
rng-tools
rsync
screen
sox
speech-dispatcher
w3m-git
wget
wireless-regdb
wireplumber
vi
xdg-user-dirs
xdg-utils
yay
)
pacman -Su --needed --noconfirm "\${packages[@]}"
$(declare -f install_sas)
install_sas
# Fix mkinitcpio preset for linux-rpi - kms hook fails on aarch64
# See: https://archlinuxarm.org/forum/viewtopic.php?t=16672
if [[ -f /etc/mkinitcpio.d/linux-rpi.preset ]]; then
echo "Configuring mkinitcpio preset to skip kms hook..."
sed -i "s/^default_options=.*/default_options=\"--skiphook kms\"/" /etc/mkinitcpio.d/linux-rpi.preset
sed -i "s/^fallback_options=.*/fallback_options=\"-S autodetect --skiphook kms\"/" /etc/mkinitcpio.d/linux-rpi.preset
fi
# Regenerate initramfs with the corrected settings
echo "Regenerating initramfs..."
mkinitcpio -P
# Restart gpg agents.
gpgconf --kill all
@@ -221,43 +433,22 @@ systemctl enable rngd.service
# Set the distribution name.
echo 'Stormux \r (\l)' > /etc/issue
echo >> /etc/issue
# Change the alarm user to be stormux
usermod -a -g users -G wheel,realtime,audio,video,network,brlapi -m -d /home/stormux -l stormux alarm
# Create the stormux user
useradd -m -g users -G wheel,realtime,audio,video,network,brlapi -s /bin/bash stormux
# Grant sudo privileges to the stormux user for package installation.
echo 'stormux ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/wheel
# Set the password for the root user
echo -e "root\nroot" | passwd "root"
# Set the password for the stormux user
echo -e "stormux\nstormux" | passwd "stormux"
# Change to the stormux user and install some packages
# Change to the stormux user for user configuration
sudo -iu stormux
# suppress git spam about default branch name
git config --global init.defaultBranch master
# Create desktop, downloads, music, and other directories.
xdg-user-dirs-update
# Build AUR packages
export aurPackages=(fenrir \
growpartfs \
log2ram \
python-pythondialog \
yay)
export PKGDEST=~/packages
for p in "\${aurPackages[@]}" ; do
git clone https://aur.archlinux.org/\${p}.git
cd ~/\${p}
if [[ "\${p}" == "python-pythondialog" ]]; then
gpg --import keys/pgp/*.asc
fi
makepkg -A
cd ~
rm -rf \${p}
done
exit
# Install built packages
for p in /home/stormux/packages/* ; do
pacman -U --noconfirm \${p}
done
rm -rf /home/stormux/packages/
# Enable linger so that hopefully sound will start at login.
mkdir -p /var/lib/systemd/linger
touch /var/lib/systemd/linger/stormux
@@ -270,21 +461,43 @@ echo '%wheel ALL=(ALL) ALL' > /etc/sudoers.d/wheel
echo stormux > /etc/hostname
# Configure services
systemctl disable systemd-networkd.service systemd-networkd.socket
systemctl enable brltty.path cronie.service fake-hwclock.service fenrirscreenreader.service log2ram.service NetworkManager.service
# Cleanup packages
# Enable services
services=(
brltty.path
cronie.service
fake-hwclock.service
fenrirscreenreader.service
log-to-ram-setup.service
log-to-ram-sync.timer
log-to-ram-shutdown.service
NetworkManager.service
sshd.service
ssh-login-monitor.service
)
for service in "\${services[@]}"; do
echo "Enabling \$service..."
if systemctl enable "\$service"; then
echo " \$service: OK"
else
echo " \$service: FAILED"
fi
done
pacman -Sc --noconfirm
pacman -R --noconfirm go
# Re-enable pacman sandboxing for the final image
sed -i '/^DisableSandbox/d' /etc/pacman.conf
# Update fstab for Raspberry Pi 4. Not needed until linux-aarch64 works.
# [[ $imageVersion -eq 4 ]] && sed -i 's/mmcblk0/mmcblk1/g' /etc/fstab
EOF
# Copy override files into place.
cp -rv ../files/boot/* /mnt/boot
cp -rv ../files/etc/* /mnt/etc
cp -rv ../files/var/* /mnt/var
cp -rv ../files/usr/* /mnt/usr
copy_rhvoice_english_fixes
# Copy skel files to stormux user home (after user rename in chroot)
find ../files/etc/skel/ -mindepth 1 -exec cp -rv "{}" /mnt/home/stormux/ \;
# Exiting calls the cleanup function to unmount.
exit 0
# Copy boot files again to ensure custom config overrides any package changes
cp -rv ../files/boot/* /mnt/boot
# Clean up, verify, compress, and create the checksum.
finish_build
+198
View File
@@ -0,0 +1,198 @@
#! /bin/bash
#
# Copyright 2025, Stormux, <storm_dragon@stormux.org>
#
# This is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this package; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
set -euo pipefail
mountedPoints=()
loopDev=""
mountRoot=""
rootPart=""
bootPart=""
imagePath=""
cleanup() {
statusCode=$?
set +e
if [[ ${#mountedPoints[@]} -gt 0 ]]; then
for ((index=${#mountedPoints[@]}-1; index>=0; index--)); do
if mountpoint -q "${mountedPoints[$index]}"; then
umount "${mountedPoints[$index]}" || true
fi
done
fi
if [[ -n "${mountRoot}" && -d "${mountRoot}" && "${mountRoot}" != "/mnt" ]]; then
rmdir "${mountRoot}" 2>/dev/null || true
fi
if [[ -n "${loopDev}" ]]; then
partx -d "${loopDev}" 2>/dev/null || true
losetup --detach "${loopDev}" 2>/dev/null || true
fi
exit "$statusCode"
}
trap cleanup EXIT INT TERM
die() {
echo "Error: $*" >&2
exit 1
}
help() {
echo "Usage: $0 <image-file>"
echo "Mounts a Stormux image, chroots into it, and cleans up on exit."
exit 0
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
}
if [[ $# -ne 1 ]]; then
help
fi
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
help
fi
imagePath="$(readlink -f "$1" 2>/dev/null || true)"
if [[ -z "${imagePath}" ]]; then
die "Unable to resolve image path: $1"
fi
if [[ ! -f "${imagePath}" ]]; then
die "Image file not found: ${imagePath}"
fi
if [[ "$(whoami)" != "root" ]]; then
die "This script must be run as root."
fi
if command -v pacman >/dev/null 2>&1; then
for packageName in arch-install-scripts util-linux; do
if ! pacman -Q "$packageName" &>/dev/null; then
die "Please install ${packageName} before continuing."
fi
done
else
require_cmd arch-chroot
require_cmd losetup
require_cmd partx
require_cmd lsblk
require_cmd mount
require_cmd umount
require_cmd findmnt
require_cmd mountpoint
fi
if [[ "$(uname -m)" == "x86_64" ]]; then
if command -v pacman >/dev/null 2>&1; then
if ! pacman -Q qemu-user-static &>/dev/null; then
die "Please install qemu-user-static and qemu-user-static-binfmt before continuing."
fi
if ! pacman -Q qemu-user-static-binfmt &>/dev/null; then
die "Please install qemu-user-static and qemu-user-static-binfmt before continuing."
fi
else
require_cmd qemu-aarch64-static
fi
fi
require_cmd arch-chroot
require_cmd losetup
require_cmd partx
require_cmd lsblk
require_cmd mount
require_cmd umount
require_cmd findmnt
require_cmd mountpoint
baseMountDir="/mnt"
if [[ ! -d "${baseMountDir}" || ! -w "${baseMountDir}" ]]; then
die "/mnt is missing or not writable. Please create or fix permissions."
fi
if mountpoint -q "${baseMountDir}"; then
if [[ -t 0 ]]; then
echo "/mnt is already mounted. Unmount it to continue? [y/N]"
read -r answerText
case "${answerText}" in
y|Y|yes|YES)
umount "${baseMountDir}" || die "Failed to unmount /mnt"
;;
*)
die "Refusing to proceed while /mnt is mounted."
;;
esac
else
die "/mnt is already mounted. Refusing to proceed in non-interactive mode."
fi
fi
mountRoot="${baseMountDir}"
loopDev="$(losetup --find --show --partscan "${imagePath}")"
partx -u "${loopDev}" >/dev/null 2>&1 || true
declare -a rootCandidates=()
declare -a bootCandidates=()
while read -r deviceName fsType nodeType; do
if [[ "${nodeType}" != "part" ]]; then
continue
fi
case "${fsType}" in
ext4|ext3|ext2)
rootCandidates+=("${deviceName}")
;;
vfat|fat|fat32|fat16)
bootCandidates+=("${deviceName}")
;;
esac
done < <(lsblk -nrpo NAME,FSTYPE,TYPE "${loopDev}")
if [[ ${#rootCandidates[@]} -gt 0 ]]; then
rootPart="${rootCandidates[0]}"
elif [[ -b "${loopDev}p2" ]]; then
rootPart="${loopDev}p2"
else
die "Unable to locate root partition inside ${imagePath}"
fi
if [[ ${#bootCandidates[@]} -gt 0 ]]; then
bootPart="${bootCandidates[0]}"
elif [[ -b "${loopDev}p1" ]]; then
bootPart="${loopDev}p1"
fi
mount "${rootPart}" "${mountRoot}"
mountedPoints+=("${mountRoot}")
if [[ -n "${bootPart}" ]]; then
mkdir -p "${mountRoot}/boot"
mount "${bootPart}" "${mountRoot}/boot"
mountedPoints+=("${mountRoot}/boot")
fi
echo "Mounted image at ${mountRoot}. Entering chroot..."
PS1="(Chroot) [\\u@\\h \\W] \\$ " arch-chroot "${mountRoot}"
+1
View File
@@ -0,0 +1 @@
root=LABEL=STRMX_ROOT rw rootwait console=serial0,115200 console=tty1 fsck.repair=yes
@@ -0,0 +1,4 @@
bifrost=buyfraust
danestange=dainstanggey
stange=stangey
kde=kaydeee
@@ -0,0 +1,19 @@
asus=aysus
certificate=cirtifficate
douche=doosh*
espeak=easpeak*
freenginx=freeenginex
git=ghit*
github=ghittehub
gitea=ghittee
jolla=yolla
jenux=jennux
lightnin=lighttnin
nginx=enginex
rhvoice=ahraychvoice
shit=shitt
sync=sink*
timezone=timezoan
vinux=vinnux
wench=wentch*
youngin=younggin*
@@ -1,4 +0,0 @@
Section "Device"
Identifier "fbdev"
Driver "fbdev"
EndSection
@@ -1,20 +0,0 @@
Section "Monitor"
Identifier "dummy_monitor"
HorizSync 28.0-80.0
VertRefresh 48.0-75.0
Modeline "1920x1080" 172.80 1920 2040 2248 2576 1080 1081 1084 1118
EndSection
Section "Device"
Identifier "dummy_card"
VideoRam 256000
Driver "dummy"
EndSection
Section "Screen"
Identifier "dummy_screen"
Device "dummy_card"
Monitor "dummy_monitor"
SubSection "Display"
EndSubSection
EndSection
+7
View File
@@ -0,0 +1,7 @@
# Static information about the filesystems.
# See fstab(5) for details.
# <file system> <dir> <type> <options> <dump> <pass>
LABEL=STRMX_BOOT /boot vfat defaults 0 1
LABEL=STRMX_ROOT / ext4 defaults,noatime 0 1
tmpfs /var/log tmpfs defaults,noatime,nosuid,nodev,noexec,mode=0755,size=128M 0 0
-37
View File
@@ -1,37 +0,0 @@
# Configuration file for Log2Ram (https://github.com/azlux/log2ram) under MIT license.
# This configuration file is read by the log2ram service
# Size for the ram folder, it defines the size the log folder will reserve into the RAM.
# If it's not enough, log2ram will not be able to use ram. Check you /var/log size folder.
# The default is 40M and is basically enough for a lot of applications.
# You will need to increase it if you have a server and a lot of log for example.
SIZE=128M
# This variable can be set to true if you prefer "rsync" rather than "cp".
# I use the command cp -u and rsync -X, so I don't copy the all folder every time for optimization.
# You can choose which one you want. Be sure rsync is installed if you use it.
USE_RSYNC=true
# If there are some errors with available RAM space, a system mail will be send
# Change it to false and you will have only a log if there is no place on RAM anymore.
MAIL=false
# Variable for folders to put in RAM. You need to specify the real folder `/path/folder` , the `/path/hdd.folder` will be automatically created. Multiple path can be separeted by `;`. Do not add the final `/` !
# example : PATH_DISK="/var/log;/home/test/FolderInRam"
PATH_DISK="/var/log"
# **************** Zram backing conf *************************************************
# ZL2R Zram Log 2 Ram enables a zram drive when ZL2R=true ZL2R=false is mem only tmpfs
ZL2R=false
# COMP_ALG this is any compression algorithm listed in /proc/crypto
# lz4 is fastest with lightest load but deflate (zlib) and Zstandard (zstd) give far better compression ratios
# lzo is very close to lz4 and may with some binaries have better optimisation
# COMP_ALG=lz4 for speed or Zstd for compression, lzo or zlib if optimisation or availabilty is a problem
COMP_ALG=lz4
# LOG_DISK_SIZE is the uncompressed disk size. Note zram uses about 0.1% of the size of the disk when not in use
# LOG_DISK_SIZE is expected compression ratio of alg chosen multiplied by log SIZE
# lzo/lz4=2.1:1 compression ratio zlib=2.7:1 zstandard=2.9:1
# Really a guestimate of a bit bigger than compression ratio whilst minimising 0.1% mem usage of disk size
LOG_DISK_SIZE=100M
+1 -1
View File
@@ -6,5 +6,5 @@ Welcome to Stormux, powered by Arch Linux ARM
Stormux IRC: #stormux on irc.stormux.org
Arch Linux ARM IRC: #archlinuxarm on irc.libera.chat
Thank you Stormux supporters! https://ko-fi.com/stormux/leaderboard
Thank you Stormux supporters! https://patreon.com/stormux
-6
View File
@@ -1,6 +0,0 @@
#%PAM-1.0
auth include system-local-login
account include system-local-login
password include system-local-login
session include system-local-login
@@ -12,6 +12,34 @@ if ! [[ -x /usr/local/bin/configure-stormux ]]; then
return
fi
# Volume calibration FIRST
echo "Setting up audio volume..."
volume=50
wait=0
# Wait for pipewire to become available
while [[ $wait -lt 30 ]]; do
if pgrep pipewire &> /dev/null ; then
break
else
sleep 1
((wait++))
fi
done
# Volume calibration - this should be the VERY FIRST interactive part
while [[ $volume -le 130 ]]; do
clear
pactl set-sink-volume @DEFAULT_SINK@ "${volume}%" 2>/dev/null
spd-say "If this is loud enough, press enter."
if read -t4 ; then
echo "Volume set to ${volume}%"
break
else
((volume+=5))
fi
done
# For audible sudo prompts:
unset sudoFlags
if [[ -x /etc/audibleprompt.sh ]]; then
@@ -19,6 +47,7 @@ if [[ -x /etc/audibleprompt.sh ]]; then
export sudoFlags=("-A")
fi
clear
cat << "EOF"
Hello, and welcome to Stormux!
-47
View File
@@ -1,47 +0,0 @@
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Entries in this file show the compile time defaults. Local configuration
# should be created by either modifying this file, or by creating "drop-ins" in
# the journald.conf.d/ subdirectory. The latter is generally recommended.
# Defaults can be restored by simply deleting this file and all drop-ins.
#
# Use 'systemd-analyze cat-config systemd/journald.conf' to display the full config.
#
# See journald.conf(5) for details.
[Journal]
#Storage=auto
#Compress=yes
#Seal=yes
#SplitMode=uid
#SyncIntervalSec=5m
#RateLimitIntervalSec=30s
#RateLimitBurst=10000
SystemMaxUse=20M
#SystemKeepFree=
#SystemMaxFileSize=
#SystemMaxFiles=100
#RuntimeMaxUse=
#RuntimeKeepFree=
#RuntimeMaxFileSize=
#RuntimeMaxFiles=100
#MaxRetentionSec=
#MaxFileSec=1month
#ForwardToSyslog=no
#ForwardToKMsg=no
#ForwardToConsole=no
#ForwardToWall=yes
#TTYPath=/dev/console
#MaxLevelStore=debug
#MaxLevelSyslog=debug
#MaxLevelKMsg=notice
#MaxLevelConsole=info
#MaxLevelWall=emerg
#LineMax=48K
#ReadKMsg=yes
#Audit=yes
@@ -0,0 +1,3 @@
[Journal]
Storage=volatile
SystemMaxUse=20M
@@ -0,0 +1,16 @@
[Unit]
Description=Setup RAM logging and restore logs from disk
DefaultDependencies=false
After=local-fs.target
Before=sysinit.target
RequiresMountsFor=/var
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=/bin/mkdir -p /var/log.hdd
ExecStartPre=/bin/sh -c 'if [ -d /var/log.hdd ] && [ "$(ls -A /var/log.hdd 2>/dev/null)" ]; then cp -au /var/log.hdd/* /var/log/ 2>/dev/null || true; fi'
ExecStart=/bin/true
[Install]
WantedBy=sysinit.target
@@ -0,0 +1,14 @@
[Unit]
Description=Sync logs to disk before shutdown
DefaultDependencies=false
Before=shutdown.target reboot.target halt.target
Conflicts=shutdown.target reboot.target halt.target
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/bin/sh -c 'rsync -aXv --delete --exclude="*.hdd" /var/log/ /var/log.hdd/ 2>/dev/null || true'
TimeoutStopSec=30
[Install]
WantedBy=shutdown.target reboot.target halt.target
@@ -0,0 +1,8 @@
[Unit]
Description=Sync logs from RAM to disk
After=local-fs.target
[Service]
Type=oneshot
ExecStartPre=/bin/mkdir -p /var/log.hdd
ExecStart=/bin/sh -c 'rsync -aXv --delete --exclude="*.hdd" /var/log/ /var/log.hdd/ 2>/dev/null || true'
@@ -0,0 +1,11 @@
[Unit]
Description=Sync logs from RAM to disk every hour
Requires=log-to-ram-sync.service
[Timer]
OnBootSec=15min
OnUnitActiveSec=1h
Persistent=true
[Install]
WantedBy=timers.target
@@ -0,0 +1,17 @@
[Unit]
Description=Fenrir SSH Login Monitor
After=sshd.service
Wants=sshd.service
[Service]
Type=simple
ExecStart=/usr/share/fenrirscreenreader/scripts/ssh-login-monitor.sh
Restart=on-failure
RestartSec=5
# Security settings
NoNewPrivileges=true
PrivateTmp=false
[Install]
WantedBy=multi-user.target
@@ -0,0 +1 @@
SUBSYSTEM=="tty", KERNEL=="tty[0-9]*|hvc[0-9]*|sclp_line[0-9]*|ttysclp[0-9]*|3270/tty[0-9]*", GROUP="tty", MODE="0620"
+3
View File
@@ -0,0 +1,3 @@
# This is the fallback vconsole configuration provided by systemd.
KEYMAP=us_alt
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env bash
# Renames a user
# Required arguments: old user name, new user name
set -e
question() {
echo
echo "$@"
read -r answer
answer="${answer:0:1}"
answer="${answer^}"
}
if [[ $# -ne 2 ]]; then
echo "Usage: $0 old-user new-user"
exit 1
fi
oldUser="$1"
newUser="$2"
# Make sure old user exists
if ! id "$oldUser" >/dev/null 2>&1; then
echo "The user $oldUser does not exist."
exit 1
fi
# Make sure old user is not logged in
if pgrep -u "$oldUser" >/dev/null; then
echo "The user $oldUser is currently logged in or has running processes. Cannot continue."
question "Would you like to forcibly log out $oldUser? (Y/N)"
if [[ "$answer" != "Y" ]]; then
exit 1
fi
systemctl stop display-manager
loginctl terminate-user "$oldUser"
sleep 2
if pgrep -u "$oldUser" >/dev/null; then
echo "The user $oldUser still has running processes after termination. Cannot continue."
exit 1
fi
fi
# Make sure new user does not exist
if id "$newUser" >/dev/null 2>&1; then
echo "The user name $newUser is already taken."
exit 1
fi
# Make sure the new name is acceptable
if ! [[ "$newUser" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]]; then
echo "$newUser is not an acceptable user name."
exit 1
fi
# Passed all safety checks, proceed with the rename
echo "Renaming $oldUser to $newUser..."
groups=$(id -nG "$oldUser")
groups="${groups// /,}"
usermod -G "$groups" -m -d "/home/$newUser" -l "$newUser" "$oldUser"
# Update nodm.conf if it exists
if [[ -e /etc/nodm.conf ]]; then
echo "Updating /etc/nodm.conf..."
sed -i -e "s#^NODM_USER=.*#NODM_USER='$newUser'#" -e "s#^NODM_XSESSION=.*#NODM_XSESSION='/home/$newUser/.xinitrc'#" /etc/nodm.conf
fi
# Copy over crontab if it exists
if crontab -u "$oldUser" -l >/tmp/${oldUser}_crontab.bak 2>/dev/null; then
echo "Copying over crontab..."
if crontab -u "$newUser" "/tmp/${oldUser}_crontab.bak"; then
rm "/tmp/${oldUser}_crontab.bak"
else
echo "Warning: failed to restore crontab for $newUser."
rm "/tmp/${oldUser}_crontab.bak"
fi
fi
# Update linger
if [[ -e "/var/lib/systemd/linger/$oldUser" ]]; then
echo "Enabling linger..."
mv "/var/lib/systemd/linger/$oldUser" "/var/lib/systemd/linger/$newUser"
fi
# Optionally update files in new home directory
echo
echo "Would you like to search for references to $oldUser and update them to $newUser in files located in /home/$newUser?"
question "This can take a while. (Y/N)"
if [[ "$answer" == "Y" ]]; then
echo "Updating files..."
find "/home/$newUser" -type f -exec grep -Iq . "{}" \; -and -exec sed -i "s|$oldUser|$newUser|g" "{}" \;
fi
echo
echo "Rename complete. The user, $newUser, is now available."
question "Would you like to reboot?"
if [[ "${answer}" == "Y" ]]; then
reboot
fi
exit 0
+24 -18
View File
@@ -7,23 +7,26 @@ if [[ -x /etc/audibleprompt.sh ]]; then
export sudoFlags=("-A")
fi
trap cleanup EXIT
# shellcheck disable=SC2329
cleanup() {
popd &> /dev/null
popd &> /dev/null || true
if ! [[ -x /opt/configure-stormux/configure-stormux.sh ]]; then
echo "Initial setup is not complete."
echo "To continue setup, please run:"
echo "sudo configure-stormux"
fi
}
trap cleanup EXIT
if [[ -x /opt/configure-stormux/configure-stormux.sh ]]; then
pushd /opt/configure-stormux
pushd /opt/configure-stormux || exit 1
./configure-stormux.sh
exit 0
fi
# Volume calibration is now handled in stormux_first_boot.sh
export DIALOGOPTS='--insecure --no-lines --visit-items'
set_timezone() {
@@ -31,29 +34,29 @@ set_timezone() {
mapfile -t regions < <(timedatectl --no-pager list-timezones | cut -d '/' -f1 | sort -u)
# Use the same text twice here and just hide the tag field.
# shellcheck disable=SC2046
region=$(dialog --backtitle "Please select your Region" \
--no-tags \
--menu "Use up and down arrows or page-up and page-down to navigate the list, and press 'Enter' to make your selection." 0 0 0 \
$(for i in ${regions[@]} ; do echo "$i";echo "$i";done) --stdout)
$(for i in "${regions[@]}" ; do echo "$i";echo "$i";done) --stdout)
mapfile -t cities < <(timedatectl --no-pager list-timezones | grep "$region" | cut -d '/' -f2 | sort -u)
# Use the same text twice here and just hide the tag field.
# shellcheck disable=SC2046
city=$(dialog --backtitle "Please select a city near you" \
--no-tags \
--menu "Use up and down arrow or page-up and page-down to navigate the list." 0 0 10 \
$(for i in ${cities[@]} ; do echo "$i";echo "$i";done) --stdout)
$(for i in "${cities[@]}" ; do echo "$i";echo "$i";done) --stdout)
# Set the timezone
if [[ -f /etc/localtime ]]; then
rm /etc/localtime
fi
ln -sf /usr/share/zoneinfo/${region}/${city} /etc/localtime
ln -sf /usr/share/zoneinfo/"${region}"/"${city}" /etc/localtime
timedatectl set-ntp true
}
# Offer to switch fenrir layout.
echo "Would you like to switch Fenrir to laptop layout?"
echo "Press y for yes or n for no followed by enter."
@@ -75,23 +78,26 @@ if [[ $diskSize -le 7 ]]; then
read -r continue
continue="${continue::1}"
if [[ "${continue,}" == "y" ]];then
sudo "${sudoFlags[@]}" growpartfs $diskSource
# Extract base device name (handles mmcblk0p2, nvme0n1p2, sda2, etc.)
if [[ "$diskSource" =~ (.*[0-9]+)p[0-9]+$ ]]; then
# Handle mmcblk0p2, nvme0n1p2 style
diskDevice="${BASH_REMATCH[1]}"
else
# Handle sda2, sdb3 style
# shellcheck disable=SC2001
diskDevice="$(echo "$diskSource" | sed 's/[0-9]*$//')"
fi
echo "Yes" | sudo "${sudoFlags[@]}" parted ---pretend-input-tty "$diskDevice" resizepart 2 100%
sudo "${sudoFlags[@]}" resize2fs -f "$diskSource"
fi
fi
if ! ping -c1 stormux.org &> /dev/null ; then
echo "No internet connection detected. Press enter to open NetworkManager."
echo "Note, it is best to put Fenrir into highlight mode while using NetworkManager."
echo "In desktop layout this is done by pressing Fenrir+numpad asterisk."
echo "That is the key just above numpad 9."
echo "In laptop mode, press Fenrir+y."
echo "In desktop mode the Fenrir key is numpad insert."
echo "In laptop mode the Fenrir key is the Super key, sometimes called the Windows key."
echo "After connecting to the internet, remember to go back to cursor mode."
echo "It is the same key used to switch to highlight mode."
echo "Press enter to continue."
read -r continue
echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock
nmtui-connect
echo "setting set focus#highlight=False" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock
fi
# Check for internet connectivity
if ping -qc1 -W 1 stormux.org &> /dev/null; then
@@ -0,0 +1,185 @@
#!/usr/bin/env bash
# SSH Login Monitor for Fenrir Screen Reader
# Monitors SSH logins and announces them via Fenrir's speech system
# Configuration
fenrirSocket="/tmp/fenrirscreenreader-daemon.sock"
logFile="/var/log/auth.log"
stateFile="/tmp/fenrir-ssh-monitor.state"
checkInterval=2 # seconds between checks
# Voice settings
announceUser=true
announceIp=true
announceHostname=true
announceLogout=false # Announce SSH disconnections (disabled by default - may not work reliably on all systems)
# Function to send message to Fenrir
fenrirSay() {
local message="$1"
# Only announce if Fenrir socket exists (silently skip if not)
if [[ -S "$fenrirSocket" ]]; then
echo "command say ${message}" | socat - UNIX-CLIENT:"${fenrirSocket}" 2>/dev/null
fi
}
# Function to get last processed line number
getLastLine() {
if [[ -f "$stateFile" ]]; then
cat "$stateFile"
else
echo "0"
fi
}
# Function to save last processed line number
saveLastLine() {
echo "$1" > "$stateFile"
}
# Function to parse SSH login and announce
processLogin() {
local logLine="$1"
local user=""
local ip=""
local hostname=""
# Parse different SSH login patterns
# Pattern 1: "Accepted publickey for USER from IP"
# Pattern 2: "Accepted password for USER from IP"
if [[ "$logLine" =~ Accepted\ (publickey|password|keyboard-interactive/pam)\ for\ ([^[:space:]]+)\ from\ ([^[:space:]]+) ]]; then
user="${BASH_REMATCH[2]}"
ip="${BASH_REMATCH[3]}"
# Try to resolve hostname
if command -v host &> /dev/null && [[ "$announceHostname" == "true" ]]; then
hostname="$(host "$ip" 2>/dev/null | grep -oP 'domain name pointer \K[^.]+' | head -1)"
fi
# Build announcement message (concise format)
local message=""
if [[ "$announceUser" == "true" ]]; then
message+="${user} "
fi
message+="S S H login"
if [[ "$announceIp" == "true" ]]; then
message+=" from ${ip}"
fi
if [[ -n "$hostname" ]] && [[ "$announceHostname" == "true" ]]; then
message+=" ${hostname}"
fi
fenrirSay "$message"
return 0
fi
return 1
}
# Function to parse SSH logout and announce
processLogout() {
local logLine="$1"
local user=""
# Parse SSH disconnect patterns
# Pattern: "pam_unix(sshd:session): session closed for user USER"
if [[ "$logLine" =~ session\ closed\ for\ user\ ([^[:space:]]+) ]]; then
user="${BASH_REMATCH[1]}"
if [[ "$announceLogout" == "true" ]]; then
local message=""
if [[ "$announceUser" == "true" ]]; then
message+="${user} "
fi
message+="disconnected from S S H"
fenrirSay "$message"
fi
return 0
fi
return 1
}
# Function to monitor auth.log
monitorAuthLog() {
local lastLine
lastLine=$(getLastLine)
# Get total lines in log
local totalLines
totalLines=$(wc -l < "$logFile" 2>/dev/null || echo "0")
# If log was rotated, reset
if [[ $totalLines -lt $lastLine ]]; then
lastLine=0
fi
# Process new lines
if [[ $totalLines -gt $lastLine ]]; then
local newLines=$((totalLines - lastLine))
# Read only new lines
tail -n "$newLines" "$logFile" 2>/dev/null | while IFS= read -r line; do
if [[ "$line" =~ sshd.*Accepted ]]; then
processLogin "$line"
elif [[ "$line" =~ sshd.*session\ closed ]]; then
processLogout "$line"
fi
done
saveLastLine "$totalLines"
fi
}
# Function to monitor journalctl (alternative for systemd systems)
monitorJournalctl() {
# Follow journalctl for SSH logins and logouts
journalctl -u sshd -u ssh -f -n 0 --no-pager 2>/dev/null | while IFS= read -r line; do
if [[ "$line" =~ Accepted ]]; then
processLogin "$line"
elif [[ "$line" =~ session\ closed ]]; then
processLogout "$line"
fi
done
}
# Check if running as root
if [[ "$(id -u)" -ne 0 ]]; then
echo "Error: This script must be run with sudo privileges to access system logs."
exit 1
fi
# Note: We don't require Fenrir to be running at startup
# The script will silently skip announcements when Fenrir socket doesn't exist
# Determine monitoring method
if command -v journalctl &> /dev/null && systemctl is-active --quiet sshd 2>/dev/null; then
echo "Starting SSH login monitor (using journalctl)..."
fenrirSay "SSH login monitor started."
# Use journalctl for real-time monitoring
trap 'fenrirSay "SSH login monitor stopped."; exit 0' INT TERM
monitorJournalctl
elif [[ -f "$logFile" ]]; then
echo "Starting SSH login monitor (using auth.log)..."
fenrirSay "SSH login monitor started."
# Use auth.log polling
trap 'fenrirSay "SSH login monitor stopped."; rm -f "$stateFile"; exit 0' INT TERM
while true; do
monitorAuthLog
sleep "$checkInterval"
done
else
echo "Error: Cannot find SSH logs. Neither journalctl nor ${logFile} is available."
exit 1
fi
+141
View File
@@ -0,0 +1,141 @@
#!/usr/bin/env bash
set -euo pipefail
readonly repoName="stormux"
readonly repoKeyUrl="https://packages.stormux.org/stormux_repo.pub"
readonly repoKeyId="52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82"
readonly pacmanConf="${STORMUX_PACMAN_CONF:-/etc/pacman.conf}"
readonly osRelease="${STORMUX_OS_RELEASE:-/etc/os-release}"
readonly effectiveUid="${STORMUX_TEST_EUID:-${EUID}}"
keyFile=""
cleanup() {
if [[ -n "$keyFile" && -f "$keyFile" ]]; then
rm -f "$keyFile"
fi
}
die() {
printf 'Error: %s\n' "$1" >&2
exit 1
}
require_root() {
[[ "$effectiveUid" == "0" ]] || die "This script must be run as root."
}
require_command() {
local commandName="$1"
command -v "$commandName" >/dev/null 2>&1 || die "Required command not found: ${commandName}"
}
is_arch_based_system() {
local osId=""
local osIdLike=""
[[ -f "$osRelease" ]] || return 1
while IFS='=' read -r keyName keyValue; do
keyValue="${keyValue%\"}"
keyValue="${keyValue#\"}"
case "$keyName" in
ID)
osId="$keyValue"
;;
ID_LIKE)
osIdLike="$keyValue"
;;
esac
done < "$osRelease"
[[ "$osId" == "arch" || "$osId" == "archarm" || " ${osIdLike} " == *" arch "* ]]
}
check_prerequisites() {
require_root
require_command curl
require_command pacman
require_command pacman-key
is_arch_based_system || die "This script supports Arch-based pacman systems only."
[[ -f "$pacmanConf" ]] || die "pacman.conf not found: ${pacmanConf}"
}
download_and_trust_key() {
keyFile="$(mktemp)"
trap cleanup EXIT
printf 'Downloading Stormux repository key...\n'
curl -fsSL "$repoKeyUrl" > "$keyFile"
printf 'Adding Stormux repository key to pacman keyring...\n'
pacman-key --add "$keyFile"
printf 'Locally signing Stormux repository key...\n'
pacman-key --lsign-key "$repoKeyId"
}
update_pacman_conf() {
local tempConf
tempConf="$(mktemp)"
awk -v repoName="$repoName" '
function print_repo_block() {
print "[" repoName "]"
print "SigLevel = Required DatabaseOptional"
print "Server = https://packages.stormux.org/$arch"
}
/^\[stormux\][[:space:]]*$/ {
inStormux = 1
next
}
inStormux && /^\[[^]]+\][[:space:]]*$/ {
inStormux = 0
}
inStormux {
next
}
!inserted && /^\[core\][[:space:]]*$/ {
print_repo_block()
print ""
inserted = 1
}
{
print
}
END {
if (!inserted) {
print ""
print_repo_block()
}
}
' "$pacmanConf" > "$tempConf"
cat "$tempConf" > "$pacmanConf"
rm -f "$tempConf"
}
refresh_databases() {
printf 'Refreshing package databases...\n'
pacman -Sy
}
main() {
check_prerequisites
download_and_trust_key
update_pacman_conf
refresh_databases
printf 'Stormux repository is configured.\n'
}
main "$@"
+325
View File
@@ -0,0 +1,325 @@
#!/usr/bin/env bash
set -euo pipefail
repoUrl="https://github.com/X11Libre/pkgbuilds-arch-based.git"
rootfsUrl="http://os.archlinuxarm.org/os/ArchLinuxARM-aarch64-latest.tar.gz"
aarch64Packages=(
xlibre-xserver
xlibre-input-libinput
xlibre-video-fbdev
xlibre-video-amdgpu
xlibre-video-ati
xlibre-video-nouveau
)
outputDir=$(pwd -P)
workDir=""
chrootDir=""
binfmtMounted=false
binfmtRegistered=false
log() {
printf '%s %s\n' "$*" "$(date '+%Y-%m-%d %H:%M:%S')"
}
die() {
printf 'Error: %s\n' "$*" >&2
exit 1
}
cleanup() {
local exitStatus=$?
local cleanupFailed=false
local mountTarget
local chrootMounts=()
trap - EXIT INT TERM
if [[ -n "$workDir" && -d "$workDir" ]]; then
log "Removing temporary chroot"
if [[ -n "$chrootDir" ]]; then
mapfile -t chrootMounts < <(
findmnt -rn -o TARGET |
awk -v root="$chrootDir" '$0 == root || index($0, root "/") == 1' |
sort -r
)
fi
for mountTarget in "${chrootMounts[@]}"; do
if ! mountpoint -q "$mountTarget"; then
continue
fi
if ! umount --recursive "$mountTarget"; then
printf 'Warning: normal chroot unmount failed; detaching it lazily\n' >&2
umount --recursive --lazy "$mountTarget" || cleanupFailed=true
fi
done
rm -rf --one-file-system "$workDir" || cleanupFailed=true
if [[ -e "$workDir" ]]; then
printf 'Error: failed to remove temporary chroot: %s\n' "$workDir" >&2
cleanupFailed=true
fi
fi
if [[ "$binfmtRegistered" == true && -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]]; then
printf '%s' -1 > /proc/sys/fs/binfmt_misc/qemu-aarch64 || cleanupFailed=true
fi
if [[ "$binfmtMounted" == true ]]; then
umount /proc/sys/fs/binfmt_misc || cleanupFailed=true
fi
if [[ "$cleanupFailed" == true && "$exitStatus" -eq 0 ]]; then
exitStatus=1
fi
exit "$exitStatus"
}
require_commands() {
local commandName
local missingCommands=()
for commandName in arch-chroot awk bsdtar chown cp curl date findmnt grep install mkdir mktemp mount mountpoint rm sort tr umount; do
if ! command -v "$commandName" >/dev/null 2>&1; then
missingCommands+=("$commandName")
fi
done
if ((${#missingCommands[@]})); then
die "Missing required commands: ${missingCommands[*]}"
fi
[[ -x /usr/bin/qemu-aarch64-static ]] || die "/usr/bin/qemu-aarch64-static is required"
[[ -r /usr/lib/binfmt.d/qemu-aarch64-static.conf ]] || die "The qemu-user-static-binfmt package is required"
}
configure_binfmt() {
if ! mountpoint -q /proc/sys/fs/binfmt_misc; then
mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc
binfmtMounted=true
fi
if [[ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]]; then
tr -d '\n' < /usr/lib/binfmt.d/qemu-aarch64-static.conf > /proc/sys/fs/binfmt_misc/register
binfmtRegistered=true
fi
grep -qx 'enabled' /proc/sys/fs/binfmt_misc/qemu-aarch64 || die "aarch64 binfmt registration is not enabled"
}
create_build_script() {
local buildScript=$1
install -m 0755 /dev/stdin "$buildScript" <<'CHROOT_SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
repoUrl=$1
shift
packages=("$@")
repoDir=/build/pkgbuilds
log() {
printf '%s %s\n' "$*" "$(date '+%Y-%m-%d %H:%M:%S')"
}
install_dependencies() {
local packageDir=$1
local dependency
local outputPackage
local srcInfo
local dependencies=()
local filteredDependencies=()
local missingDependencies=()
local outputPackages=()
local -A internalPackages=()
# $1 is expanded by the nested builder shell, not this root shell.
# shellcheck disable=SC2016
srcInfo=$(runuser -u builder -- bash -c \
'cd "$1" && makepkg --printsrcinfo' _ "$packageDir")
mapfile -t outputPackages < <(
printf '%s\n' "$srcInfo" |
awk -F ' = ' '/^[[:space:]]*pkgname = / { print $2 }'
)
for outputPackage in "${outputPackages[@]}"; do
internalPackages["$outputPackage"]=1
done
mapfile -t dependencies < <(
printf '%s\n' "$srcInfo" |
awk -F ' = ' '
/^[[:space:]]*(depends|makedepends|checkdepends)(_aarch64)? = / {
dependency=$2
sub(/[<>=].*/, "", dependency)
print dependency
}
' |
sort -u
)
for dependency in "${dependencies[@]}"; do
if [[ -z ${internalPackages[$dependency]+present} ]]; then
filteredDependencies+=("$dependency")
fi
done
((${#filteredDependencies[@]})) || return 0
mapfile -t missingDependencies < <(pacman -T "${filteredDependencies[@]}" || true)
((${#missingDependencies[@]})) || return 0
log "Installing dependencies for ${packageDir##*/}"
pacman -S --asdeps --needed --noconfirm "${missingDependencies[@]}"
}
remove_installed_packages() {
local packageName
local installedPackages=()
for packageName in "$@"; do
if pacman -Q "$packageName" >/dev/null 2>&1; then
installedPackages+=("$packageName")
fi
done
((${#installedPackages[@]})) || return 0
log "Removing conflicting packages: ${installedPackages[*]}"
pacman -Rdd --noconfirm "${installedPackages[@]}"
}
build_package() {
local packageName=$1
local installAfterBuild=${2:-false}
local packageDir="$repoDir/$packageName"
local builtPackages=()
[[ -f "$packageDir/PKGBUILD" ]] || {
printf 'Missing PKGBUILD for %s\n' "$packageName" >&2
return 1
}
log "Building $packageName"
install_dependencies "$packageDir"
# $1 is expanded by the nested builder shell, not this root shell.
# shellcheck disable=SC2016
runuser -u builder -- bash -c \
'cd "$1" && makepkg --noconfirm --clean --cleanbuild' _ "$packageDir"
mapfile -d '' builtPackages < <(
find "$packageDir" -maxdepth 1 -type f -name '*.pkg.tar.*' ! -name '*.sig' -print0
)
((${#builtPackages[@]})) || {
printf 'No package artifacts were produced for %s\n' "$packageName" >&2
return 1
}
if [[ "$installAfterBuild" == true ]]; then
case "$packageName" in
xlibre-input-libinput)
remove_installed_packages xf86-input-libinput
;;
xlibre-xserver)
remove_installed_packages \
xorg-server \
xorg-server-common \
xorg-server-devel \
xorg-server-xephyr \
xorg-server-xnest \
xorg-server-xvfb \
glamor-egl \
xf86-video-modesetting
;;
esac
log "Installing $packageName for subsequent builds"
pacman -U --noconfirm "${builtPackages[@]}"
fi
}
log "Initializing Arch Linux ARM"
# Pacman's Landlock sandbox is unavailable through qemu-user emulation.
sed -i \
-e '/^[[:space:]]*DisableSandbox[[:space:]]*$/d' \
-e '/^\[options\][[:space:]]*$/a DisableSandbox' \
/etc/pacman.conf
pacman-key --init
pacman-key --populate archlinuxarm
pacman -Syu --noconfirm
pacman -S --needed --noconfirm base-devel git
useradd --create-home --shell /bin/bash builder
install -d -o builder -g builder /build
log "Cloning XLibre PKGBUILDs"
runuser -u builder -- git clone --depth 1 "$repoUrl" "$repoDir"
# The input driver must first be built against the stock xorg-server-devel.
build_package xlibre-input-libinput true
# Installing all server split packages replaces the stock Xorg server/devel
# packages and provides the ABI dependencies needed by the video drivers.
build_package xlibre-xserver true
for packageName in "${packages[@]}"; do
case "$packageName" in
xlibre-input-libinput|xlibre-xserver)
continue
;;
esac
build_package "$packageName"
done
install -d /output
find "$repoDir" -type f -name '*.pkg.tar.*' ! -name '*.sig' -exec cp -t /output -- {} +
log "All requested aarch64 packages built"
CHROOT_SCRIPT
}
main() {
local rootfsArchive
local packageFile
local copiedPackageCount=0
((EUID == 0)) || die "Run this script as root: sudo $0"
require_commands
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
configure_binfmt
workDir=$(mktemp -d /tmp/xlibre-aarch64.XXXXXX)
chrootDir="$workDir/root"
rootfsArchive="$workDir/ArchLinuxARM-aarch64-latest.tar.gz"
mkdir -p "$chrootDir"
log "Downloading a fresh Arch Linux ARM aarch64 root filesystem"
curl --fail --location --retry 3 --output "$rootfsArchive" "$rootfsUrl"
log "Creating aarch64 chroot"
bsdtar -xpf "$rootfsArchive" -C "$chrootDir"
install -m 0755 /usr/bin/qemu-aarch64-static "$chrootDir/usr/bin/qemu-aarch64-static"
cp --remove-destination /etc/resolv.conf "$chrootDir/etc/resolv.conf"
create_build_script "$chrootDir/root/build-xlibre"
# arch-chroot expects the chroot root to be a mountpoint.
mount --bind "$chrootDir" "$chrootDir"
log "Starting aarch64 package build"
arch-chroot "$chrootDir" /root/build-xlibre "$repoUrl" "${aarch64Packages[@]}"
shopt -s nullglob
for packageFile in "$chrootDir"/output/*.pkg.tar.*; do
[[ ${packageFile##*/} == *-aarch64.pkg.tar.* ]] || die "Unexpected non-aarch64 artifact: ${packageFile##*/}"
install -m 0644 "$packageFile" "$outputDir/"
((copiedPackageCount += 1))
if [[ -n ${SUDO_UID:-} && -n ${SUDO_GID:-} ]]; then
chown "$SUDO_UID:$SUDO_GID" "$outputDir/${packageFile##*/}"
fi
done
shopt -u nullglob
((copiedPackageCount > 0)) || die "No package artifacts were copied to $outputDir"
log "Copied $copiedPackageCount completed packages to $outputDir"
}
main "$@"
+230
View File
@@ -0,0 +1,230 @@
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
repoDir="/var/www/packages.stormux.org"
aurRpcUrl="https://aur.archlinux.org/rpc/v5/info"
exclude=("gzdoom")
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
printf 'Required command not found: %s\n' "$cmd" >&2
exit 1
fi
}
is_excluded() {
local packageName="$1"
local excludedPackage
for excludedPackage in "${exclude[@]}"; do
if [[ "$excludedPackage" == "$packageName" ]]; then
return 0
fi
done
return 1
}
record_local_package_version() {
local packageName="$1"
local packageVersion="$2"
local packageMapName="$3"
local -n packageMapRef="$packageMapName"
local existingVersion="${packageMapRef[$packageName]:-}"
if [[ -z "$existingVersion" ]] || (( $(vercmp "$packageVersion" "$existingVersion") > 0 )); then
packageMapRef["$packageName"]="$packageVersion"
fi
}
read_package_metadata() {
local packageFile="$1"
pacman -Qip "$packageFile" | parse_pacman_info
}
parse_pacman_info() {
awk '
/^Name[[:space:]]*:/ {
packageName=$0
sub(/^Name[[:space:]]*:[[:space:]]*/, "", packageName)
}
/^Version[[:space:]]*:/ {
packageVersion=$0
sub(/^Version[[:space:]]*:[[:space:]]*/, "", packageVersion)
}
END {
if (packageName == "" || packageVersion == "") {
exit 1
}
printf "%s\t%s\n", packageName, packageVersion
}
'
}
collect_local_packages() {
local packageMapName="$1"
# shellcheck disable=SC2178
local -n packageMapRef="$packageMapName"
local archDir packageFile metadata packageName packageVersion
local -a archDirs=("$repoDir/x86_64" "$repoDir/aarch64")
for archDir in "${archDirs[@]}"; do
if [[ ! -d "$archDir" ]]; then
continue
fi
for packageFile in "$archDir"/*.pkg.tar.zst "$archDir"/*.pkg.tar.xz; do
metadata="$(read_package_metadata "$packageFile")" || {
printf 'Unable to read package metadata: %s\n' "$packageFile" >&2
exit 1
}
packageName="${metadata%%$'\t'*}"
packageVersion="${metadata#*$'\t'}"
record_local_package_version "$packageName" "$packageVersion" "$packageMapName"
done
done
}
extract_aur_version_from_json() {
local packageName="$1"
jq -r --arg packageName "$packageName" '
.results[]
| select(.Name == $packageName)
| .Version
' | head -n1
}
fetch_aur_version() {
local packageName="$1"
curl -fsS --get \
--data-urlencode "arg[]=${packageName}" \
"$aurRpcUrl" |
extract_aur_version_from_json "$packageName"
}
print_outdated_packages() {
local packageMapName="$1"
# shellcheck disable=SC2178
local -n packageMapRef="$packageMapName"
local packageName localVersion aurVersion
local -a packageNames=()
mapfile -t packageNames < <(printf '%s\n' "${!packageMapRef[@]}" | sort)
for packageName in "${packageNames[@]}"; do
if is_excluded "$packageName"; then
continue
fi
localVersion="${packageMapRef[$packageName]}"
aurVersion="$(fetch_aur_version "$packageName" || true)"
if [[ -z "$aurVersion" ]]; then
continue
fi
if (( $(vercmp "$localVersion" "$aurVersion") < 0 )); then
printf '%s %s\n' "$packageName" "$aurVersion"
fi
done
}
assert_equals() {
local expected="$1"
local actual="$2"
local message="$3"
if [[ "$expected" != "$actual" ]]; then
printf 'FAIL: %s\nExpected: %s\nActual: %s\n' "$message" "$expected" "$actual" >&2
exit 1
fi
}
assert_success() {
local message="$1"
shift
if ! "$@"; then
printf 'FAIL: %s\n' "$message" >&2
exit 1
fi
}
assert_failure() {
local message="$1"
shift
if "$@"; then
printf 'FAIL: %s\n' "$message" >&2
exit 1
fi
}
main() {
if [[ "${1:-}" == "--self-test" ]]; then
run_self_tests
return
fi
require_cmd "curl"
require_cmd "jq"
require_cmd "pacman"
require_cmd "vercmp"
if [[ ! -d "$repoDir" ]]; then
printf 'Repo dir does not exist: %s\n' "$repoDir" >&2
exit 1
fi
# shellcheck disable=SC2034
declare -A localPackages=()
collect_local_packages localPackages
print_outdated_packages localPackages
}
run_self_tests() {
declare -A packageMap=()
local exactMatchJson noMatchJson
local extractedVersion=""
local pacmanInfo=""
assert_success "excluded package should match" is_excluded "gzdoom"
assert_failure "non-excluded package should not match" is_excluded "fenrir"
record_local_package_version "fenrir" "1:2026.01.20-1" packageMap
record_local_package_version "fenrir" "1:2026.01.28-1" packageMap
record_local_package_version "fenrir" "1:2026.01.10-1" packageMap
assert_equals "1:2026.01.28-1" "${packageMap[fenrir]}" "newest local version should win"
exactMatchJson='{"results":[{"Name":"fenrir-git","Version":"1:r3322.4672592d-1"},{"Name":"fenrir","Version":"1:2026.01.28-1"}]}'
extractedVersion="$(printf '%s\n' "$exactMatchJson" | extract_aur_version_from_json "fenrir")"
assert_equals "1:2026.01.28-1" "$extractedVersion" "exact package name should be selected from AUR JSON"
noMatchJson='{"results":[{"Name":"fenrir-git","Version":"1:r3322.4672592d-1"}]}'
extractedVersion="$(printf '%s\n' "$noMatchJson" | extract_aur_version_from_json "fenrir")"
assert_equals "" "$extractedVersion" "missing exact AUR match should stay empty"
pacmanInfo='Name : fenrir
Version : 1:2026.01.28-1
Description : A user space console screen reader written in python3'
extractedVersion="$(printf '%s\n' "$pacmanInfo" | parse_pacman_info)"
assert_equals $'fenrir\t1:2026.01.28-1' "$extractedVersion" "pacman metadata parsing should return name and version"
if (( $(vercmp "1:2026.01.20-1" "1:2026.01.28-1") >= 0 )); then
printf 'FAIL: older local version should compare lower than AUR version\n' >&2
exit 1
fi
printf 'Self-test passed\n'
}
main "$@"
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
# ---- Configurable Variables ----
repoDir="/var/www/packages.stormux.org"
keyId="52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82"
repoName="stormux"
dbName="${repoName}.db.tar.gz"
filesName="${repoName}.files.tar.gz"
rebuildDb="${REBUILD_DB:-false}"
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "❌ Required command not found: $cmd"
exit 1
fi
}
# ---- Safety Checks ----
if [[ ! -d "$repoDir" ]]; then
echo "❌ Repo dir does not exist: $repoDir"
exit 1
fi
require_cmd "gpg"
require_cmd "repo-add"
require_cmd "repo-remove"
# ---- Create Architecture Directories ----
mkdir -p "$repoDir/x86_64" "$repoDir/aarch64"
# ---- Process Each Architecture ----
process_arch() {
local arch="$1"
local archDir="$repoDir/$arch"
local pkgFiles=()
local selectedPkgFiles=()
local repoAddArgs=()
local -A currentPkgNames=()
local -A newestPkgByName=()
local repoPkgNames=()
local dbFile="$dbName"
local filesFile="$filesName"
local dbSig="${dbFile}.sig"
local filesSig="${filesFile}.sig"
local dbLink="${repoName}.db"
local filesLink="${repoName}.files"
echo "🏗️ Processing $arch packages..."
# Enter arch directory (packages should already be sorted by update.sh)
cd "$archDir" || return 1
# Select only the newest archive for each package name. repo-add cannot
# safely consume multiple versions of the same package in a single run.
pkgFiles=( *.pkg.tar.zst *.pkg.tar.xz )
for pkg in "${pkgFiles[@]}"; do
local pkgName pkgVersion existingPkg existingVersion
pkgName="$(bsdtar -xOqf "$pkg" .PKGINFO | sed -n 's/^pkgname = //p' | head -n1)"
pkgVersion="$(bsdtar -xOqf "$pkg" .PKGINFO | sed -n 's/^pkgver = //p' | head -n1)-$(bsdtar -xOqf "$pkg" .PKGINFO | sed -n 's/^pkgrel = //p' | head -n1)"
if [[ -z "$pkgName" || -z "$pkgVersion" ]]; then
echo "❌ Unable to determine package metadata for $pkg"
cd "$repoDir" || exit 1
return 1
fi
existingPkg="${currentPkgNames[$pkgName]:-}"
if [[ -z "$existingPkg" ]]; then
currentPkgNames["$pkgName"]="$pkg"
else
existingVersion="$(bsdtar -xOqf "$existingPkg" .PKGINFO | sed -n 's/^pkgver = //p' | head -n1)-$(bsdtar -xOqf "$existingPkg" .PKGINFO | sed -n 's/^pkgrel = //p' | head -n1)"
if (( $(vercmp "$pkgVersion" "$existingVersion") > 0 )); then
currentPkgNames["$pkgName"]="$pkg"
fi
fi
done
for pkgName in "${!currentPkgNames[@]}"; do
newestPkgByName["$pkgName"]="${currentPkgNames[$pkgName]}"
selectedPkgFiles+=( "${currentPkgNames[$pkgName]}" )
done
pkgFiles=( "${selectedPkgFiles[@]}" )
# Sign all unsigned selected packages
echo "🔏 Signing $arch packages..."
for pkg in "${pkgFiles[@]}"; do
if [[ ! -f "$pkg.sig" ]]; then
echo " 📝 Signing $pkg"
gpg --default-key "$keyId" --detach-sign "$pkg"
else
echo "$pkg already signed"
fi
done
# Track which package names should remain in the repo after this run.
for pkg in "${pkgFiles[@]}"; do
local pkgName
pkgName="$(bsdtar -xOqf "$pkg" .PKGINFO | sed -n 's/^pkgname = //p' | head -n1)"
if [[ -n "$pkgName" ]]; then
currentPkgNames["$pkgName"]=1
else
echo "❌ Unable to determine package name for $pkg"
cd "$repoDir" || exit 1
return 1
fi
done
# Rebuild database for this architecture
if [[ "$rebuildDb" == "true" ]]; then
echo "🗃️ Rebuilding $arch repo database..."
rm -f "$dbFile" "$dbSig" "$filesFile" "$filesSig" "$dbLink" "$filesLink"
elif [[ -f "$dbFile" ]]; then
echo "🗃️ Updating $arch repo database..."
else
echo "🆕 Creating new $arch repo database..."
fi
# Remove packages that still exist in the repo database but are no longer
# present in the current directory. repo-remove with --remove also deletes
# the matching package archive and detached signature from disk.
if [[ -f "$dbFile" ]]; then
mapfile -t repoPkgNames < <(
bsdtar -tf "$dbFile" |
awk -F/ 'NF == 2 && $2 == "desc" {print $1}' |
while read -r entry; do
bsdtar -xOf "$dbFile" "${entry}/desc" |
awk 'found {print; exit} /^%NAME%$/ {found=1}'
done |
sort -u
)
for pkgName in "${repoPkgNames[@]}"; do
if [[ -z "${currentPkgNames[$pkgName]:-}" ]]; then
echo "🧹 Removing stale repo package $pkgName"
repo-remove --sign --key "$keyId" --remove "$dbFile" "$pkgName"
fi
done
fi
# Only run repo-add if there are packages
if ((${#pkgFiles[@]} > 0)); then
repoAddArgs=(--sign --key "$keyId" --remove)
if [[ "$rebuildDb" != "true" && -f "$dbFile" ]]; then
repoAddArgs+=(--verify)
fi
repo-add "${repoAddArgs[@]}" "$dbFile" "${pkgFiles[@]}"
if [[ ! -f "$dbFile" ]]; then
echo "❌ repo-add did not create $dbFile"
cd "$repoDir" || exit 1
return 1
fi
if [[ ! -e "$dbLink" ]]; then
ln -s "$dbFile" "$dbLink"
fi
if [[ -f "$filesFile" && ! -e "$filesLink" ]]; then
ln -s "$filesFile" "$filesLink"
fi
echo "$arch repo updated successfully"
else
echo "️ No $arch packages found"
fi
# Remove orphaned package archives and detached signatures that are not part
# of the current package set. This keeps the on-disk repo contents aligned
# with the package database after rebuilds and package removals.
for pkg in *.pkg.tar.zst *.pkg.tar.xz; do
local pkgName
pkgName="$(bsdtar -xOqf "$pkg" .PKGINFO | sed -n 's/^pkgname = //p' | head -n1)"
if [[ -z "${newestPkgByName[$pkgName]:-}" || "${newestPkgByName[$pkgName]}" != "$pkg" ]]; then
echo "🧹 Removing orphaned package file $pkg"
rm -f "$pkg" "$pkg.sig"
fi
done
cd "$repoDir" || exit
}
# ---- Process Both Architectures ----
process_arch "x86_64"
process_arch "aarch64"
# ---- Done ----
echo "✅ All repositories updated and signed successfully."
@@ -0,0 +1,23 @@
pkgbase = xlibre-video-dummy-with-vt
pkgdesc = XLibre dummy video driver with an allocated vt
pkgver = 25.0.0
pkgrel = 5
url = https://github.com/X11Libre/xf86-video-dummy
arch = x86_64
arch = aarch64
groups = xlibre-drivers
license = MIT
license = X11
makedepends = xlibre-xserver-devel>=25.0
makedepends = xorgproto
depends = xlibre-xserver>=25.0
depends = glibc
provides = xf86-video-dummy
provides = x11win-video-dummy
conflicts = xf86-video-dummy
source = https://github.com/X11Libre/xf86-video-dummy/archive/refs/tags/xlibre-xf86-video-dummy-25.0.0.tar.gz
source = dummy_driver.patch
sha256sums = b56e610705cd3d4d86422a11c6b0d93357e4d4749a05178a85fd250301d357b9
sha256sums = 68cdcf21e9b54a7fdb8e968292e1ef9ad154ddb1361b141a0a635c2a13c92bfa
pkgname = xlibre-video-dummy-with-vt
@@ -0,0 +1,5 @@
*
!PKGBUILD
!.SRCINFO
!.gitignore
!.nvchecker.toml
@@ -0,0 +1,5 @@
[xlibre-video-dummy]
source = "git"
git = "https://github.com/x11libre/xf86-video-dummy.git"
include_regex = "xlibre-xf86-video-dummy-.*"
prefix = "xlibre-xf86-video-dummy-"
@@ -0,0 +1,59 @@
# Maintainer: Storm Dragon <storm_dragon@linux-a11y.org>
pkgname=xlibre-video-dummy-with-vt
pkgver=25.0.0
pkgrel=5
pkgdesc="XLibre dummy video driver with an allocated vt"
arch=(x86_64 aarch64)
_pkgname=xf86-video-dummy
url="https://github.com/X11Libre/${_pkgname}"
license=('MIT' 'X11')
depends=("xlibre-xserver>=${pkgver%.*}" 'glibc')
makedepends=("xlibre-xserver-devel>=${pkgver%.*}" 'xorgproto')
conflicts=("${_pkgname}")
provides=("${_pkgname}" 'x11win-video-dummy')
source=("${url}/archive/refs/tags/xlibre-${_pkgname}-${pkgver}.tar.gz"
"dummy_driver.patch")
groups=('xlibre-drivers')
sha256sums=('b56e610705cd3d4d86422a11c6b0d93357e4d4749a05178a85fd250301d357b9'
'68cdcf21e9b54a7fdb8e968292e1ef9ad154ddb1361b141a0a635c2a13c92bfa')
prepare() {
cd "${srcdir}/${_pkgname}-xlibre-${_pkgname}-${pkgver}/src"
patch -i "${srcdir}/dummy_driver.patch"
}
build() {
case "$CARCH" in
"x86_64")
CFLAGS=" -march=x86-64"
;;
"aarch64")
CFLAGS=" -march=armv8-a"
;;
*)
CFLAGS=" -march=native"
;;
esac
CFLAGS+=" -mtune=generic -O2 -pipe -fexceptions -Wp,-D_FORTIFY_SOURCE=3 -Wformat -Werror=format-security"
CFLAGS+=" -fstack-clash-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
LDFLAGS=" -Wl,-O1 -Wl,--sort-common -Wl,--as-needed -Wl,-z,lazy -Wl,-z,relro -Wl,-z,pack-relative-relocs"
if [[ $CARCH != 'aarch64' ]]; then
CFLAGS+=" -fcf-protection"
fi
CXXFLAGS="${CFLAGS} -Wp,-D_GLIBCXX_ASSERTIONS"
export CFLAGS="${CFLAGS}"
export CXXFLAGS="${CXXFLAGS}"
export LDFLAGS="${LDFLAGS}"
cd "${srcdir}/${_pkgname}-xlibre-${_pkgname}-${pkgver}"
./autogen.sh
./configure --prefix=/usr
make
}
package() {
cd "${srcdir}/${_pkgname}-xlibre-${_pkgname}-${pkgver}"
make DESTDIR="${pkgdir}" install
install -Dm644 COPYING "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}
@@ -0,0 +1,17 @@
--- a/src/dummy_driver.c
+++ b/src/dummy_driver.c
@@ -1016,10 +1016,10 @@ dummyDriverFunc(ScrnInfoPtr pScrn, xorgDriverFuncOp op, pointer ptr)
CARD32 *flag;
switch (op) {
- case GET_REQUIRED_HW_INTERFACES:
- flag = (CARD32*)ptr;
- (*flag) = HW_SKIP_CONSOLE;
- return TRUE;
+ case GET_REQUIRED_HW_INTERFACES:
+ flag = (CARD32*)ptr;
+ /* Allow the driver to allocate a VT instead of skipping the console. */
+ return TRUE;
default:
return FALSE;
}
+15
View File
@@ -0,0 +1,15 @@
# x86_64 ISO build artifacts
out/
work/
logs/
# Build outputs
*.iso
*.iso.sig
*.sha256
# Documentation (not tracked)
*.md
# User-specific
.claude/
+24
View File
@@ -0,0 +1,24 @@
# Fenrir console audio support
# Prevents bluetooth disconnection when switching TTY
monitor.bluez.properties = {
# Disable logind integration to prevent bluetooth device suspension on TTY switch
bluez5.enable-sbc-xq = true
bluez5.enable-msbc = true
bluez5.enable-hw-volume = true
}
monitor.bluez.rules = [
{
matches = [
{
device.name = "~bluez_card.*"
}
]
actions = {
update-props = {
session.suspend-timeout-seconds = 0
}
}
}
]
+24
View File
@@ -0,0 +1,24 @@
# Fenrir console audio support
# Adds secondary socket for console applications like Fenrir
pulse.properties = {
# the addresses this server listens on
server.address = [
"unix:native"
"unix:/tmp/pulse.sock" # console access socket
]
}
# client/stream specific properties
pulse.rules = [
{
# speech dispatcher asks for too small latency and then underruns.
matches = [ { application.name = "~speech-dispatcher*" } ]
actions = {
update-props = {
pulse.min.req = 1024/48000 # 21ms
pulse.min.quantum = 1024/48000 # 21ms
}
}
}
]
+35
View File
@@ -0,0 +1,35 @@
# Fenrir console audio support
# Prevents audio device suspension when switching to TTY console
monitor.alsa.rules = [
{
matches = [
{
device.name = "~alsa_card.*"
}
]
actions = {
update-props = {
api.alsa.use-acp = true
api.acp.auto-profile = false
api.acp.auto-port = false
session.suspend-timeout-seconds = 0
}
}
}
{
matches = [
{
node.name = "~alsa_input.*"
}
{
node.name = "~alsa_output.*"
}
]
actions = {
update-props = {
session.suspend-timeout-seconds = 0
}
}
}
]
+201
View File
@@ -0,0 +1,201 @@
# Stormux x86_64 ISO Build
This directory contains the archiso profile for building a Stormux x86_64 live/install ISO image.
## Overview
The x86_64 build uses archiso (Arch Linux ISO build system) and is configured for accessibility with the Fenrir screen reader. The `airootfs/` tree is maintained as the x86_64 live-system overlay; shared behavior may mirror the Pi image, but files are not copied from `pi4/files/` during the build.
## Directory Structure
```
x86_64/
├── build.sh # Main build script
├── profiledef.sh # Archiso profile definition
├── pacman.conf # Pacman config with Stormux repository
├── packages.x86_64 # Package list for installation
├── grub/ # UEFI boot configuration
│ ├── grub.cfg
│ └── loopback.cfg
├── syslinux/ # BIOS boot configuration
│ ├── archiso_head.cfg
│ ├── archiso_pxe.cfg
│ ├── archiso_pxe-linux.cfg
│ ├── archiso_sys.cfg
│ ├── archiso_sys-linux.cfg
│ ├── archiso_tail.cfg
│ ├── splash.png
│ └── syslinux.cfg
└── airootfs/ # Live system overlay files
├── etc/ # Live-system configuration
├── root/ # x86_64-specific scripts
├── usr/ # Live-system utilities and installer
└── var/ # Live-system variable data
```
## Overlay Maintenance
Edit files directly under `airootfs/` using the final filesystem path they should have in the live ISO. For example, live-system scripts belong under `airootfs/usr/local/bin/`, and systemd units belong under `airootfs/etc/systemd/system/`.
Some files intentionally match the Pi overlay, such as shell defaults, Fenrir support scripts, and shared Stormux helper behavior. Keep both trees in sync when a change is meant to affect both image families.
Pi hardware files remain Pi-only and should not be added to the x86_64 profile unless there is a specific PC use for them:
- `boot/cmdline.txt` - Pi boot command line
- `boot/config.txt` - Pi hardware configuration
- `etc/modprobe.d/brcmfmac.conf` - Pi wireless driver config
## Prerequisites
Building requires an Arch Linux host system with:
- `archiso` package installed
- Root privileges
- Internet connection for package downloads and build-time helper installs
## Building the ISO
```bash
cd x86_64
sudo ./build.sh
```
### Build Options
- `-o <dir>` - Output directory (default: `./out`)
- `-w <dir>` - Work directory (default: `./work`)
- `-h` - Show help
### Build Process
1. Uses the maintained `airootfs/` overlay as-is
2. Adds Stormux repository GPG key to the build host's keyring
- Uses the included `stormux_repo.pub` file
- Key fingerprint: 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82
3. Runs `mkarchiso` to build the ISO
- Packages from Stormux repo can be installed during build
- The latest `sas` helper is installed into the live environment during image creation
4. Renames the ISO to `stormux-x86_64-YYYY-MM-DD.iso` when possible and writes a matching `.sha1sum`
The Stormux repository key is also embedded in the ISO at `/usr/share/stormux/stormux_repo.pub` and automatically imported on first boot via the `stormux-repo-init.service`, ensuring the live environment can install additional packages from the Stormux repository.
## Key Features
### Accessibility
- **Fenrir screen reader** starts automatically (not speakup)
- **Pipewire audio** properly initialized before Fenrir starts
- **Speech-dispatcher** integration for speech synthesis
- GRUB plays an audible tune on boot for accessibility
- Boot menu defaults to accessible entry
- Service startup order ensures audio is ready before screen reader
- First-login live-environment setup calibrates volume, checks networking and time, then offers to run the installer
### Package Management
- Stormux repository configured with priority over Arch repos
- Custom packages from Stormux repo: fenrir, w3m-git, yay, etc.
### Default Configuration
- Default user: `stormux` / Password: `stormux`
- Root password: `root`
- NetworkManager for network configuration
- Braille terminal support (brltty)
- Multiple speech synthesizers (espeak-ng, rhvoice)
## Audio and Speech Initialization
The live environment uses a carefully orchestrated startup sequence to ensure Fenrir has working audio:
1. **stormux-audio-setup.service** - Runs after sound hardware is detected
- Enables systemd user linger for the stormux user
- Starts pipewire user services
- Unmutes audio and sets volume to 70%
2. **stormux-speech.service** - Runs after audio setup
- Waits 2 seconds for pipewire to fully initialize
- Starts fenrirscreenreader.service
3. **fenrirscreenreader.service** - Screen reader with dependencies
- Configured to wait for pipewire, speech-dispatcher, and sound.target
- Uses speech-dispatcher for TTS output
This ensures Fenrir never starts without working audio, preventing system freezes or silent boot.
## Live Setup and Installer Flow
On first login to tty1, the live environment runs a short setup script. It calibrates speech volume, checks for network access, updates the live environment clock, copies the detected timezone into the installer defaults, and then asks whether to run `install-stormux`.
The installer gives one last chance to accept or change the timezone before installation. The live setup changes are for the temporary live environment only; the installed system is configured by `install-stormux`.
## Differences from Pi4 Build
1. **No ARM-specific packages** - Uses x86_64 standard Linux kernel
2. **Fenrir instead of speakup** - More feature-rich screen reader
3. **UEFI and BIOS support** - Boots on both modern and legacy systems
4. **ISO format** - Live/install medium instead of disk image
5. **No Pi hardware configs** - Standard x86_64 PC configuration
6. **Different audio startup** - Pipewire user services instead of system-wide
## Testing
After building, test the ISO with:
- QEMU/KVM virtual machine
- VirtualBox
- Physical hardware (USB/CD)
For VM testing with audio:
```bash
./qemu-boot.sh
```
After installing to the test disk, boot the installed system with:
```bash
./qemu-boot.sh -i
```
## Customization
### Adding Packages
Edit `packages.x86_64` and add package names (one per line).
### Modifying Boot Configuration
- BIOS: Edit `syslinux/archiso_sys-linux.cfg`
- UEFI: Edit `grub/grub.cfg`
### Adding Overlay Files
Place files in `airootfs/` following the target filesystem structure.
The x86_64 build uses this overlay directly.
## Troubleshooting
### Build fails with GPG errors
The build script should automatically add the Stormux repository key. If it fails:
1. Check that `x86_64/stormux_repo.pub` exists
2. Manually add the key to your build host:
```bash
sudo pacman-key --add x86_64/stormux_repo.pub
sudo pacman-key --lsign-key 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82
```
### Missing packages
Ensure the Stormux repository is accessible:
```bash
curl -I https://packages.stormux.org/x86_64/
```
### Disk space issues
The build requires significant space:
- Work directory: ~3-4 GB
- Output ISO: ~1-2 GB
Ensure adequate free space in work and output directories.
@@ -0,0 +1,4 @@
bifrost=buyfraust
danestange=dainstanggey
stange=stangey
kde=kaydeee
@@ -0,0 +1,19 @@
asus=aysus
certificate=cirtifficate
douche=doosh*
espeak=easpeak*
freenginx=freeenginex
git=ghit*
github=ghittehub
gitea=ghittee
jolla=yolla
jenux=jennux
lightnin=lighttnin
nginx=enginex
rhvoice=ahraychvoice
shit=shitt
sync=sink*
timezone=timezoan
vinux=vinnux
wench=wentch*
youngin=younggin*
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
play -qnV0 synth 3 pluck D3 pluck A3 pluck D4 pluck F4 pluck A4 delay 0 .1 .2 .3 .4 remix - chorus 0.9 0.9 38 0.75 0.3 0.5 -t &
read -rsp "$*"$'\n' password
echo "$password"
exit 0
+12
View File
@@ -0,0 +1,12 @@
#
# This file is parsed by pam_env module
#
# Syntax: simple "KEY=VAL" pairs on separate lines
#
# Accessibility variables
export ACCESSIBILITY_ENABLED=1
export GTK_MODULES=gail:atk-bridge
export GNOME_ACCESSIBILITY=1
export QT_ACCESSIBILITY=1
export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1
export SAL_USE_VCLPLUGIN=gtk3
+6
View File
@@ -0,0 +1,6 @@
# Static information about the filesystems.
# See fstab(5) for details.
# <file system> <dir> <type> <options> <dump> <pass>
# Live ISO - root filesystem is handled by archiso, only tmpfs mounts needed
tmpfs /var/log tmpfs defaults,noatime,nosuid,nodev,noexec,mode=0755,size=128M 0 0
+1
View File
@@ -0,0 +1 @@
stormux
+5
View File
@@ -0,0 +1,5 @@
# Static table lookup for hostnames.
# See hosts(5) for details.
127.0.0.1 localhost
::1 localhost
127.0.1.1 stormux.localdomain stormux
@@ -0,0 +1,3 @@
HOOKS=(base udev microcode modconf kms memdisk archiso archiso_loop_mnt archiso_pxe_common archiso_pxe_nbd archiso_pxe_http archiso_pxe_nfs block filesystems keyboard)
COMPRESSION="xz"
COMPRESSION_OPTIONS=(-9e)
@@ -0,0 +1,8 @@
# mkinitcpio preset file for the 'linux' package on archiso
PRESETS=('archiso')
ALL_kver='/boot/vmlinuz-linux'
archiso_config='/etc/mkinitcpio.conf.d/archiso.conf'
archiso_image="/boot/initramfs-linux.img"
+9
View File
@@ -0,0 +1,9 @@
Welcome to Stormux, powered by Arch Linux
Stormux Website: https://stormux.org
Arch Linux: https://archlinux.org
Stormux IRC: #stormux on irc.stormux.org
Thank you Stormux supporters! https://patreon.com/stormux
+105
View File
@@ -0,0 +1,105 @@
#
# /etc/pacman.conf
#
# See the pacman.conf(5) manpage for option and repository directives
#
# GENERAL OPTIONS
#
[options]
# The following paths are commented out with their default values listed.
# If you wish to use different paths, uncomment and update the paths.
#RootDir = /
#DBPath = /var/lib/pacman/
#CacheDir = /var/cache/pacman/pkg/
#LogFile = /var/log/pacman.log
#GPGDir = /etc/pacman.d/gnupg/
#HookDir = /etc/pacman.d/hooks/
HoldPkg = pacman glibc
#XferCommand = /usr/bin/curl -L -C - -f -o %o %u
#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u
#CleanMethod = KeepInstalled
Architecture = auto
# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup
#IgnorePkg =
#IgnoreGroup =
# Protect Stormux custom skel files from being overwritten by package updates
NoUpgrade = etc/skel/.bashrc etc/skel/.inputrc etc/skel/.screenrc etc/skel/.vimrc etc/skel/.vim/*
#NoExtract =
# Misc options
#UseSyslog
#Color
#NoProgressBar
# We cannot check disk space from within a chroot environment
#CheckSpace
#VerbosePkgLists
ParallelDownloads = 5
#DownloadUser = alpm
#DisableSandbox
# By default, pacman accepts packages signed by keys that its local keyring
# trusts (see pacman-key and its man page), as well as unsigned packages.
SigLevel = Required DatabaseOptional
LocalFileSigLevel = Optional
#RemoteFileSigLevel = Required
# NOTE: You must run `pacman-key --init` before first using pacman; the local
# keyring can then be populated with the keys of all official Arch Linux
# packagers with `pacman-key --populate archlinux`.
#
# REPOSITORIES
# - can be defined here or included from another file
# - pacman will search repositories in the order defined here
# - local/custom mirrors can be added here or in separate files
# - repositories listed first will take precedence when packages
# have identical names, regardless of version number
# - URLs will have $repo replaced by the name of the current repo
# - URLs will have $arch replaced by the name of the architecture
#
# Repository entries are of the format:
# [repo-name]
# Server = ServerName
# Include = IncludePath
#
# The header [repo-name] is crucial - it must be present and
# uncommented to enable the repo.
#
# The testing repositories are disabled by default. To enable, uncomment the
# repo name header and Include lines. You can add preferred servers immediately
# after the header, and they will be used before the default mirrors.
#[core-testing]
#Include = /etc/pacman.d/mirrorlist
[stormux]
SigLevel = Required DatabaseOptional
Server = https://packages.stormux.org/$arch
[core]
Include = /etc/pacman.d/mirrorlist
#[extra-testing]
#Include = /etc/pacman.d/mirrorlist
[extra]
Include = /etc/pacman.d/mirrorlist
# If you want to run 32 bit applications on your x86_64 system,
# enable the multilib repositories as required here.
#[multilib-testing]
#Include = /etc/pacman.d/mirrorlist
#[multilib]
#Include = /etc/pacman.d/mirrorlist
# An example of a custom package repository. See the pacman manpage for
# tips on creating your own repositories.
#[custom]
#SigLevel = Optional TrustAll
#Server = file:///home/custompkgs
@@ -0,0 +1,10 @@
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = *
[Action]
Description = Adding Stormux repository key...
When = PreTransaction
Exec = /bin/sh -c 'if [ ! -f /etc/pacman.d/.stormux-key-added ]; then pacman-key --add /usr/share/stormux/stormux_repo.pub && pacman-key --lsign-key 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82 && touch /etc/pacman.d/.stormux-key-added; fi'
@@ -0,0 +1,16 @@
# remove from airootfs!
# Stormux system setup hook - creates user, configures services
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = filesystem
[Action]
Description = Setting up Stormux user and system configuration...
When = PostTransaction
Depends = shadow
Depends = systemd
Depends = sudo
Exec = /usr/local/bin/stormux-setup.sh
+961
View File
@@ -0,0 +1,961 @@
##
## Arch Linux repository mirrorlist
## Generated on 2025-10-21
##
## Worldwide
Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch
Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = https://fastly.mirror.pkgbuild.com/$repo/os/$arch
#Server = https://ftpmirror.infania.net/mirror/archlinux/$repo/os/$arch
#Server = http://mirror.rackspace.com/archlinux/$repo/os/$arch
## Albania
#Server = https://al.arch.niranjan.co/$repo/os/$arch
## Armenia
#Server = http://mirrors.teamcloud.am/archlinux/$repo/os/$arch
#Server = https://mirrors.teamcloud.am/archlinux/$repo/os/$arch
## Australia
#Server = https://mirror.aarnet.edu.au/pub/archlinux/$repo/os/$arch
#Server = http://au.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://au.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://archlinux.mirror.digitalpacific.com.au/$repo/os/$arch
#Server = https://archlinux.mirror.digitalpacific.com.au/$repo/os/$arch
#Server = http://gsl-syd.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://gsl-syd.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://sydney.mirror.pkgbuild.com/$repo/os/$arch
#Server = http://ftp.iinet.net.au/pub/archlinux/$repo/os/$arch
#Server = http://mirror.internode.on.net/pub/archlinux/$repo/os/$arch
#Server = https://au.arch.niranjan.co/$repo/os/$arch
#Server = http://syd.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = https://syd.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = http://ftp.swin.edu.au/archlinux/$repo/os/$arch
## Austria
#Server = http://mirror.alwyzon.net/archlinux/$repo/os/$arch
#Server = https://mirror.alwyzon.net/archlinux/$repo/os/$arch
#Server = http://at.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://at.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.digitalnova.at/archlinux/$repo/os/$arch
#Server = http://mirror.easyname.at/archlinux/$repo/os/$arch
#Server = https://at.arch.mirror.kescher.at/$repo/os/$arch
#Server = https://at.arch.niranjan.co/$repo/os/$arch
#Server = https://at-vie.soulharsh007.dev/archlinux/$repo/os/$arch
## Azerbaijan
#Server = http://mirror.ourhost.az/archlinux/$repo/os/$arch
#Server = https://mirror.ourhost.az/archlinux/$repo/os/$arch
#Server = http://mirror.yer.az/archlinux/$repo/os/$arch
#Server = https://mirror.yer.az/archlinux/$repo/os/$arch
## Bangladesh
#Server = http://mirror.limda.net/archlinux/$repo/os/$arch
#Server = https://mirror.limda.net/archlinux/$repo/os/$arch
#Server = http://mirror.xeonbd.com/archlinux/$repo/os/$arch
#Server = https://mirror.xeonbd.com/archlinux/$repo/os/$arch
## Belarus
#Server = http://ftp.byfly.by/pub/archlinux/$repo/os/$arch
#Server = http://mirror.datacenter.by/pub/archlinux/$repo/os/$arch
## Belgium
#Server = http://mirror.1ago.be/archlinux/$repo/os/$arch
#Server = https://mirror.1ago.be/archlinux/$repo/os/$arch
#Server = http://mirror.jonas-prz.be/$repo/os/$arch
#Server = https://mirror.jonas-prz.be/$repo/os/$arch
#Server = https://archlinux.mirror-services.net/archlinux/$repo/os/$arch
#Server = http://mirror.tiguinet.net/arch/$repo/os/$arch
#Server = https://mirror.tiguinet.net/arch/$repo/os/$arch
## Brazil
#Server = http://archlinux.c3sl.ufpr.br/$repo/os/$arch
#Server = https://archlinux.c3sl.ufpr.br/$repo/os/$arch
#Server = http://br.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://br.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.ufscar.br/archlinux/$repo/os/$arch
#Server = https://mirror.ufscar.br/archlinux/$repo/os/$arch
#Server = http://mirrors.ic.unicamp.br/archlinux/$repo/os/$arch
#Server = https://mirrors.ic.unicamp.br/archlinux/$repo/os/$arch
## Bulgaria
#Server = http://mirror.host.ag/archlinux/$repo/os/$arch
#Server = http://mirror.telepoint.bg/archlinux/$repo/os/$arch
#Server = https://mirror.telepoint.bg/archlinux/$repo/os/$arch
#Server = http://mirrors.uni-plovdiv.net/archlinux/$repo/os/$arch
#Server = https://mirrors.uni-plovdiv.net/archlinux/$repo/os/$arch
## Canada
#Server = http://mirror.0xem.ma/arch/$repo/os/$arch
#Server = https://mirror.0xem.ma/arch/$repo/os/$arch
#Server = https://mirror.acadielinux.ca/mirror/arch/$repo/os/$arch
#Server = https://arch.mirror.winslow.cloud/$repo/os/$arch
#Server = http://ca.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://ca.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.cpsc.ucalgary.ca/mirror/archlinux.org/$repo/os/$arch
#Server = https://mirror.cpsc.ucalgary.ca/mirror/archlinux.org/$repo/os/$arch
#Server = http://mirror.csclub.uwaterloo.ca/archlinux/$repo/os/$arch
#Server = https://mirror.csclub.uwaterloo.ca/archlinux/$repo/os/$arch
#Server = http://mirror2.evolution-host.com/archlinux/$repo/os/$arch
#Server = https://mirror2.evolution-host.com/archlinux/$repo/os/$arch
#Server = https://stygian.failzero.net/mirror/archlinux/$repo/os/$arch
#Server = https://mirror.franscorack.com/archlinux/$repo/os/$arch
#Server = http://mirror.its.dal.ca/archlinux/$repo/os/$arch
#Server = http://mirror.quantum5.ca/archlinux/$repo/os/$arch
#Server = https://mirror.quantum5.ca/archlinux/$repo/os/$arch
#Server = http://muug.ca/mirror/archlinux/$repo/os/$arch
#Server = https://muug.ca/mirror/archlinux/$repo/os/$arch
#Server = http://mirrors.pablonara.com/archlinux/$repo/os/$arch
#Server = https://mirrors.pablonara.com/archlinux/$repo/os/$arch
#Server = https://upstream-1.pablonara.com/archlinux/$repo/os/$arch
#Server = http://archlinux.mirror.rafal.ca/$repo/os/$arch
#Server = http://mirror.scd31.com/arch/$repo/os/$arch
#Server = https://mirror.scd31.com/arch/$repo/os/$arch
#Server = http://mirror.xenyth.net/archlinux/$repo/os/$arch
#Server = https://mirror.xenyth.net/archlinux/$repo/os/$arch
## Chile
#Server = http://mirror.anquan.cl/archlinux/$repo/os/$arch
#Server = https://mirror.anquan.cl/archlinux/$repo/os/$arch
#Server = http://elmirror.cl/archlinux/$repo/os/$arch
#Server = https://elmirror.cl/archlinux/$repo/os/$arch
#Server = http://mirror.hnd.cl/archlinux/$repo/os/$arch
#Server = https://mirror.hnd.cl/archlinux/$repo/os/$arch
## China
#Server = http://mirrors.163.com/archlinux/$repo/os/$arch
#Server = http://mirrors.aliyun.com/archlinux/$repo/os/$arch
#Server = https://mirrors.aliyun.com/archlinux/$repo/os/$arch
#Server = http://mirrors.bfsu.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.bfsu.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.cqu.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.cqu.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.hit.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.hit.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.hust.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.hust.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.jlu.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.jlu.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.jxust.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.jxust.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirror.lzu.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.neusoft.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.neusoft.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.nju.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.nju.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirror.nyist.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirror.nyist.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.sjtug.sjtu.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.wsyu.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.wsyu.edu.cn/archlinux/$repo/os/$arch
#Server = https://mirrors.xjtu.edu.cn/archlinux/$repo/os/$arch
#Server = http://mirrors.zju.edu.cn/archlinux/$repo/os/$arch
## Colombia
#Server = http://mirrors.atlas.net.co/archlinux/$repo/os/$arch
#Server = https://mirrors.atlas.net.co/archlinux/$repo/os/$arch
#Server = http://edgeuno-bog2.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://edgeuno-bog2.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://mirrors.udenar.edu.co/archlinux/$repo/os/$arch
## Croatia
#Server = http://archlinux.iskon.hr/$repo/os/$arch
## Czechia
#Server = http://mirror.dkm.cz/archlinux/$repo/os/$arch
#Server = https://mirror.dkm.cz/archlinux/$repo/os/$arch
#Server = http://ftp.fi.muni.cz/pub/linux/arch/$repo/os/$arch
#Server = http://ftp.linux.cz/pub/linux/arch/$repo/os/$arch
#Server = https://europe.mirror.pkgbuild.com/$repo/os/$arch
#Server = http://gluttony.sin.cvut.cz/arch/$repo/os/$arch
#Server = https://gluttony.sin.cvut.cz/arch/$repo/os/$arch
#Server = https://archlinux.nic.cz/archlinux/$repo/os/$arch
#Server = http://ftp.sh.cvut.cz/arch/$repo/os/$arch
#Server = https://ftp.sh.cvut.cz/arch/$repo/os/$arch
#Server = http://mirror.vpsfree.cz/archlinux/$repo/os/$arch
## Denmark
#Server = http://mirrors.dotsrc.org/archlinux/$repo/os/$arch
#Server = https://mirrors.dotsrc.org/archlinux/$repo/os/$arch
#Server = http://mirror.group.one/archlinux/$repo/os/$arch
#Server = https://mirror.group.one/archlinux/$repo/os/$arch
#Server = https://mirror.it-privat.dk/arch/$repo/os/$arch
## Ecuador
#Server = http://mirror.cedia.org.ec/archlinux/$repo/os/$arch
#Server = https://mirror.linux.ec/archlinux/$repo/os/$arch
## Estonia
#Server = http://mirror.cspacehostings.com/archlinux/$repo/os/$arch
#Server = https://mirror.cspacehostings.com/archlinux/$repo/os/$arch
#Server = http://repo.br.ee/arch/$repo/os/$arch
#Server = https://repo.br.ee/arch/$repo/os/$arch
#Server = http://mirrors.xtom.ee/archlinux/$repo/os/$arch
#Server = https://mirrors.xtom.ee/archlinux/$repo/os/$arch
## Finland
#Server = https://archlinux.doridian.net/$repo/os/$arch
#Server = http://cdnmirror.com/archlinux/$repo/os/$arch
#Server = https://cdnmirror.com/archlinux/$repo/os/$arch
#Server = http://arch.mirror.far.fi/$repo/os/$arch
#Server = http://mirror.5i.fi/archlinux/$repo/os/$arch
#Server = https://mirror.5i.fi/archlinux/$repo/os/$arch
#Server = https://fi.arch.niranjan.co/$repo/os/$arch
#Server = https://mirror1.sl-chat.ru/archlinux/$repo/os/$arch
#Server = https://mirror.srv.fail/archlinux/$repo/os/$arch
#Server = http://mirror.wuki.li/archlinux/$repo/os/$arch
#Server = https://mirror.wuki.li/archlinux/$repo/os/$arch
#Server = http://arch.yhtez.xyz/$repo/os/$arch
#Server = https://arch.yhtez.xyz/$repo/os/$arch
## France
#Server = http://mirror.archlinux.ikoula.com/archlinux/$repo/os/$arch
#Server = https://elda.asgardius.company/archlinux/$repo/os/$arch
#Server = http://mirror.bakertelekom.fr/Arch/$repo/os/$arch
#Server = https://mirror.bakertelekom.fr/Arch/$repo/os/$arch
#Server = http://fr.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://fr.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.cyberbits.eu/archlinux/$repo/os/$arch
#Server = https://mirror.cyberbits.eu/archlinux/$repo/os/$arch
#Server = http://archlinux.datagr.am/$repo/os/$arch
#Server = https://mirrors.eric.ovh/arch/$repo/os/$arch
#Server = http://mirrors.gandi.net/archlinux/$repo/os/$arch
#Server = https://mirrors.gandi.net/archlinux/$repo/os/$arch
#Server = http://archmirror.hogwarts.fr/$repo/os/$arch
#Server = https://archmirror.hogwarts.fr/$repo/os/$arch
#Server = http://mirror.its-tps.fr/archlinux/$repo/os/$arch
#Server = https://mirror.its-tps.fr/archlinux/$repo/os/$arch
#Server = https://mirrors.jtremesay.org/archlinux/$repo/os/$arch
#Server = http://mirror.lastmikoi.net/archlinux/$repo/os/$arch
#Server = http://archlinux.mailtunnel.eu/$repo/os/$arch
#Server = https://archlinux.mailtunnel.eu/$repo/os/$arch
#Server = http://f.matthieul.dev/mirror/archlinux/$repo/os/$arch
#Server = https://f.matthieul.dev/mirror/archlinux/$repo/os/$arch
#Server = http://mir.archlinux.fr/$repo/os/$arch
#Server = http://mirror.oldsql.cc/archlinux/$repo/os/$arch
#Server = https://mirror.oldsql.cc/archlinux/$repo/os/$arch
#Server = http://archlinux.mirrors.ovh.net/archlinux/$repo/os/$arch
#Server = https://archlinux.mirrors.ovh.net/archlinux/$repo/os/$arch
#Server = http://mirror.peeres-telecom.fr/archlinux/$repo/os/$arch
#Server = https://mirror.peeres-telecom.fr/archlinux/$repo/os/$arch
#Server = http://mirror.rznet.fr/archlinux/$repo/os/$arch
#Server = https://mirror.rznet.fr/archlinux/$repo/os/$arch
#Server = https://mirror.smayzy.ovh/archlinux/$repo/os/$arch
#Server = http://arch.syxpi.fr/arch/$repo/os/$arch
#Server = https://arch.syxpi.fr/arch/$repo/os/$arch
#Server = https://mirror.thekinrar.fr/archlinux/$repo/os/$arch
#Server = http://mirror.theo546.fr/archlinux/$repo/os/$arch
#Server = https://mirror.theo546.fr/archlinux/$repo/os/$arch
#Server = http://mirror.trap.moe/archlinux/$repo/os/$arch
#Server = https://mirror.trap.moe/archlinux/$repo/os/$arch
#Server = http://ftp.u-strasbg.fr/linux/distributions/archlinux/$repo/os/$arch
#Server = https://mirror.wormhole.eu/archlinux/$repo/os/$arch
#Server = http://arch.yourlabs.org/$repo/os/$arch
#Server = https://arch.yourlabs.org/$repo/os/$arch
## Georgia
#Server = http://archlinux.grena.ge/$repo/os/$arch
#Server = https://archlinux.grena.ge/$repo/os/$arch
## Germany
#Server = http://mirror.23m.com/archlinux/$repo/os/$arch
#Server = https://mirror.23m.com/archlinux/$repo/os/$arch
#Server = http://ftp.agdsn.de/pub/mirrors/archlinux/$repo/os/$arch
#Server = https://ftp.agdsn.de/pub/mirrors/archlinux/$repo/os/$arch
#Server = http://mirrors.aminvakil.com/archlinux/$repo/os/$arch
#Server = https://mirrors.aminvakil.com/archlinux/$repo/os/$arch
#Server = http://artfiles.org/archlinux.org/$repo/os/$arch
#Server = https://mirror.bethselamin.de/$repo/os/$arch
#Server = https://de.repo.c48.uk/arch/$repo/os/$arch
#Server = http://de.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://de.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.clientvps.com/archlinux/$repo/os/$arch
#Server = https://mirror.clientvps.com/archlinux/$repo/os/$arch
#Server = http://mirror.cmt.de/archlinux/$repo/os/$arch
#Server = https://mirror.cmt.de/archlinux/$repo/os/$arch
#Server = http://os.codefionn.eu/archlinux/$repo/os/$arch
#Server = https://os.codefionn.eu/archlinux/$repo/os/$arch
#Server = http://mirror-de-1.cutie.dating/archlinux/$repo/os/$arch
#Server = https://mirror-de-1.cutie.dating/archlinux/$repo/os/$arch
#Server = https://mirror.dogado.de/archlinux/$repo/os/$arch
#Server = http://ftp.fau.de/archlinux/$repo/os/$arch
#Server = https://ftp.fau.de/archlinux/$repo/os/$arch
#Server = https://pkg.fef.moe/archlinux/$repo/os/$arch
#Server = https://dist-mirror.fem.tu-ilmenau.de/archlinux/$repo/os/$arch
#Server = https://berlin.mirror.pkgbuild.com/$repo/os/$arch
#Server = http://ftp.gwdg.de/pub/linux/archlinux/$repo/os/$arch
#Server = https://files.hadiko.de/pub/dists/arch/$repo/os/$arch
#Server = http://ftp.hosteurope.de/mirror/ftp.archlinux.org/$repo/os/$arch
#Server = http://ftp-stud.hs-esslingen.de/pub/Mirrors/archlinux/$repo/os/$arch
#Server = http://mirror.hugo-betrugo.de/archlinux/$repo/os/$arch
#Server = https://mirror.hugo-betrugo.de/archlinux/$repo/os/$arch
#Server = http://mirror.informatik.tu-freiberg.de/arch/$repo/os/$arch
#Server = https://mirror.informatik.tu-freiberg.de/arch/$repo/os/$arch
#Server = http://mirror.as20647.net/archlinux/$repo/os/$arch
#Server = http://mirror.ipb.de/archlinux/$repo/os/$arch
#Server = https://mirror.as20647.net/archlinux/$repo/os/$arch
#Server = https://mirror.ipb.de/archlinux/$repo/os/$arch
#Server = http://archlinux.mirror.iphh.net/$repo/os/$arch
#Server = http://mirrors.janbruckner.de/archlinux/$repo/os/$arch
#Server = https://mirrors.janbruckner.de/archlinux/$repo/os/$arch
#Server = http://arch.jensgutermuth.de/$repo/os/$arch
#Server = https://arch.jensgutermuth.de/$repo/os/$arch
#Server = https://de.arch.mirror.kescher.at/$repo/os/$arch
#Server = http://mirror.kumi.systems/archlinux/$repo/os/$arch
#Server = https://mirror.kumi.systems/archlinux/$repo/os/$arch
#Server = http://mirror.fra10.de.leaseweb.net/archlinux/$repo/os/$arch
#Server = https://mirror.fra10.de.leaseweb.net/archlinux/$repo/os/$arch
#Server = http://mirror.metalgamer.eu/archlinux/$repo/os/$arch
#Server = https://mirror.metalgamer.eu/archlinux/$repo/os/$arch
#Server = http://mirror.lcarilla.de/archlinux/$repo/os/$arch
#Server = https://mirror.lcarilla.de/archlinux/$repo/os/$arch
#Server = http://mirror.moson.org/arch/$repo/os/$arch
#Server = https://mirror.moson.org/arch/$repo/os/$arch
#Server = http://mirrors.n-ix.net/archlinux/$repo/os/$arch
#Server = https://mirrors.n-ix.net/archlinux/$repo/os/$arch
#Server = http://mirror.netcologne.de/archlinux/$repo/os/$arch
#Server = https://mirror.netcologne.de/archlinux/$repo/os/$arch
#Server = https://de.arch.niranjan.co/$repo/os/$arch
#Server = http://mirrors.niyawe.de/archlinux/$repo/os/$arch
#Server = https://mirrors.niyawe.de/archlinux/$repo/os/$arch
#Server = http://packages.oth-regensburg.de/archlinux/$repo/os/$arch
#Server = https://packages.oth-regensburg.de/archlinux/$repo/os/$arch
#Server = http://arch.owochle.app/$repo/os/$arch
#Server = https://arch.owochle.app/$repo/os/$arch
#Server = http://mirror.pagenotfound.de/archlinux/$repo/os/$arch
#Server = https://mirror.pagenotfound.de/archlinux/$repo/os/$arch
#Server = http://arch.phinau.de/$repo/os/$arch
#Server = https://arch.phinau.de/$repo/os/$arch
#Server = https://mirror.pseudoform.org/$repo/os/$arch
#Server = http://mirrors.purring.online/arch/$repo/os/$arch
#Server = https://mirrors.purring.online/arch/$repo/os/$arch
#Server = https://archlinux.richard-neumann.de/$repo/os/$arch
#Server = http://ftp.halifax.rwth-aachen.de/archlinux/$repo/os/$arch
#Server = https://ftp.halifax.rwth-aachen.de/archlinux/$repo/os/$arch
#Server = http://linux.rz.rub.de/archlinux/$repo/os/$arch
#Server = http://mirror.selfnet.de/archlinux/$repo/os/$arch
#Server = https://mirror.selfnet.de/archlinux/$repo/os/$arch
#Server = https://de-nue.soulharsh007.dev/archlinux/$repo/os/$arch
#Server = http://ftp.spline.inf.fu-berlin.de/mirrors/archlinux/$repo/os/$arch
#Server = https://ftp.spline.inf.fu-berlin.de/mirrors/archlinux/$repo/os/$arch
#Server = http://mirror.sunred.org/archlinux/$repo/os/$arch
#Server = https://mirror.sunred.org/archlinux/$repo/os/$arch
#Server = http://archlinux.thaller.ws/$repo/os/$arch
#Server = https://archlinux.thaller.ws/$repo/os/$arch
#Server = https://mirror.thereisno.page/archlinux/$repo/os/$arch
#Server = http://ftp.tu-chemnitz.de/pub/linux/archlinux/$repo/os/$arch
#Server = http://mirror.ubrco.de/archlinux/$repo/os/$arch
#Server = https://mirror.ubrco.de/archlinux/$repo/os/$arch
#Server = http://ftp.uni-bayreuth.de/linux/archlinux/$repo/os/$arch
#Server = http://ftp.uni-hannover.de/archlinux/$repo/os/$arch
#Server = http://ftp.uni-kl.de/pub/linux/archlinux/$repo/os/$arch
#Server = https://arch.unixpeople.org/$repo/os/$arch
#Server = http://mirrors.xtom.de/archlinux/$repo/os/$arch
#Server = https://mirrors.xtom.de/archlinux/$repo/os/$arch
## Greece
#Server = http://ftp.cc.uoc.gr/mirrors/linux/archlinux/$repo/os/$arch
#Server = https://repo.greeklug.gr/data/pub/linux/archlinux/$repo/os/$arch
#Server = http://ftp.otenet.gr/linux/archlinux/$repo/os/$arch
## Hong Kong
#Server = https://hk.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror-hk.koddos.net/archlinux/$repo/os/$arch
#Server = http://hkg.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = https://hkg.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = https://arch-mirror.wtako.net/$repo/os/$arch
#Server = http://mirror.xtom.com.hk/archlinux/$repo/os/$arch
#Server = https://mirror.xtom.com.hk/archlinux/$repo/os/$arch
## Hungary
#Server = https://ftp.ek-cer.hu/pub/mirrors/ftp.archlinux.org/$repo/os/$arch
#Server = http://nova.quantum-mirror.hu/mirrors/pub/archlinux/$repo/os/$arch
#Server = http://quantum-mirror.hu/mirrors/pub/archlinux/$repo/os/$arch
#Server = http://super.quantum-mirror.hu/mirrors/pub/archlinux/$repo/os/$arch
#Server = https://nova.quantum-mirror.hu/mirrors/pub/archlinux/$repo/os/$arch
#Server = https://quantum-mirror.hu/mirrors/pub/archlinux/$repo/os/$arch
#Server = https://super.quantum-mirror.hu/mirrors/pub/archlinux/$repo/os/$arch
## Iceland
#Server = http://is.mirror.flokinet.net/archlinux/$repo/os/$arch
#Server = https://is.mirror.flokinet.net/archlinux/$repo/os/$arch
## India
#Server = https://mirrors.abhy.me/archlinux/$repo/os/$arch
#Server = https://mirror.del2.albony.in/archlinux/$repo/os/$arch
#Server = https://mirror.maa.albony.in/archlinux/$repo/os/$arch
#Server = http://in.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://in.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://in-mirror.garudalinux.org/archlinux/$repo/os/$arch
#Server = https://in-mirror.garudalinux.org/archlinux/$repo/os/$arch
#Server = https://archlinux.kushwanthreddy.com/$repo/os/$arch
#Server = https://in.arch.niranjan.co/$repo/os/$arch
#Server = http://mirrors.nxtgen.com/archlinux-mirror/$repo/os/$arch
#Server = https://mirrors.nxtgen.com/archlinux-mirror/$repo/os/$arch
#Server = http://mirror.sahil.world/archlinux/$repo/os/$arch
#Server = https://mirror.sahil.world/archlinux/$repo/os/$arch
#Server = http://mirrors.saswata.cc/archlinux/$repo/os/$arch
#Server = https://mirrors.saswata.cc/archlinux/$repo/os/$arch
## Indonesia
#Server = http://mirror.citrahost.com/archlinux/$repo/os/$arch
#Server = https://mirror.citrahost.com/archlinux/$repo/os/$arch
#Server = http://mirror.gi.co.id/archlinux/$repo/os/$arch
#Server = https://mirror.gi.co.id/archlinux/$repo/os/$arch
#Server = http://kebo.pens.ac.id/archlinux/$repo/os/$arch
#Server = http://mirror.ditatompel.com/archlinux/$repo/os/$arch
#Server = https://mirror.ditatompel.com/archlinux/$repo/os/$arch
#Server = http://mirror.papua.go.id/archlinux/$repo/os/$arch
#Server = https://mirror.papua.go.id/archlinux/$repo/os/$arch
#Server = https://kacabenggala.uny.ac.id/archlinux/$repo/os/$arch
## Iran
#Server = http://mirror.arvancloud.ir/archlinux/$repo/os/$arch
#Server = https://mirror.arvancloud.ir/archlinux/$repo/os/$arch
#Server = http://repo.iut.ac.ir/repo/archlinux/$repo/os/$arch
#Server = http://mirror.mobinhost.com/archlinux/$repo/os/$arch
#Server = https://mirror.mobinhost.com/archlinux/$repo/os/$arch
## Israel
#Server = http://archlinux.interhost.co.il/$repo/os/$arch
#Server = https://archlinux.interhost.co.il/$repo/os/$arch
#Server = http://mirror.isoc.org.il/pub/archlinux/$repo/os/$arch
#Server = https://mirror.isoc.org.il/pub/archlinux/$repo/os/$arch
## Italy
#Server = http://it.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://it.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://archlinux.mirror.garr.it/archlinux/$repo/os/$arch
#Server = https://arch.mirror.hyperbit.it/$repo/os/$arch
#Server = http://archlinux.mirror.server24.net/$repo/os/$arch
#Server = https://archlinux.mirror.server24.net/$repo/os/$arch
## Japan
#Server = http://mirrors.cat.net/archlinux/$repo/os/$arch
#Server = https://mirrors.cat.net/archlinux/$repo/os/$arch
#Server = http://jp.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://jp.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://ftp.tsukuba.wide.ad.jp/Linux/archlinux/$repo/os/$arch
#Server = http://ftp.jaist.ac.jp/pub/Linux/ArchLinux/$repo/os/$arch
#Server = https://ftp.jaist.ac.jp/pub/Linux/ArchLinux/$repo/os/$arch
#Server = http://mirror.rain.ne.jp/archlinux/$repo/os/$arch
#Server = https://mirror.rain.ne.jp/archlinux/$repo/os/$arch
## Kazakhstan
#Server = http://mirror.ps.kz/archlinux/$repo/os/$arch
#Server = https://mirror.ps.kz/archlinux/$repo/os/$arch
## Kenya
#Server = http://archlinux.mirror.liquidtelecom.com/$repo/os/$arch
#Server = https://archlinux.mirror.liquidtelecom.com/$repo/os/$arch
## Latvia
#Server = http://ftp.linux.edu.lv/archlinux/$repo/os/$arch
#Server = https://ftp.linux.edu.lv/archlinux/$repo/os/$arch
#Server = http://archlinux.koyanet.lv/archlinux/$repo/os/$arch
#Server = https://archlinux.koyanet.lv/archlinux/$repo/os/$arch
## Lithuania
#Server = http://mirrors.atviras.lt/archlinux/$repo/os/$arch
#Server = https://mirrors.atviras.lt/archlinux/$repo/os/$arch
#Server = http://mirror.sinirlan.net/archlinux/$repo/os/$arch
#Server = https://mirror.sinirlan.net/archlinux/$repo/os/$arch
## Luxembourg
#Server = http://arch-lux.spirex.me/$repo/os/$arch
#Server = https://arch-lux.spirex.me/$repo/os/$arch
## Mauritius
#Server = http://archlinux-mirror.cloud.mu/$repo/os/$arch
#Server = https://archlinux-mirror.cloud.mu/$repo/os/$arch
## Mexico
#Server = http://lidsol.fi-b.unam.mx/archlinux/$repo/os/$arch
#Server = https://lidsol.fi-b.unam.mx/archlinux/$repo/os/$arch
#Server = https://arch.jsc.mx/$repo/os/$arch
## Moldova
#Server = http://mirror.hosthink.net/arch/$repo/os/$arch
#Server = https://mirror.hosthink.net/arch/$repo/os/$arch
#Server = http://mirror.ihost.md/archlinux/$repo/os/$arch
#Server = https://mirror.ihost.md/archlinux/$repo/os/$arch
#Server = http://mirror.mangohost.net/archlinux/$repo/os/$arch
#Server = https://mirror.mangohost.net/archlinux/$repo/os/$arch
## Morocco
#Server = http://mirror.abderraziq.com/archlinux/$repo/os/$arch
#Server = https://mirror.abderraziq.com/archlinux/$repo/os/$arch
## Netherlands
#Server = http://ams.nl.mirrors.bjg.at/arch/$repo/os/$arch
#Server = https://ams.nl.mirrors.bjg.at/arch/$repo/os/$arch
#Server = http://mirror.bouwhuis.network/archlinux/$repo/os/$arch
#Server = https://mirror.bouwhuis.network/archlinux/$repo/os/$arch
#Server = http://mirror.nl.cdn-perfprod.com/archlinux/$repo/os/$arch
#Server = https://mirror.nl.cdn-perfprod.com/archlinux/$repo/os/$arch
#Server = http://nl.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://nl.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.cj2.nl/archlinux/$repo/os/$arch
#Server = https://mirror.cj2.nl/archlinux/$repo/os/$arch
#Server = http://mirrors.evoluso.com/archlinux/$repo/os/$arch
#Server = http://nl.mirror.flokinet.net/archlinux/$repo/os/$arch
#Server = https://nl.mirror.flokinet.net/archlinux/$repo/os/$arch
#Server = https://mirror.iusearchbtw.nl/$repo/os/$arch
#Server = http://mirror.koddos.net/archlinux/$repo/os/$arch
#Server = http://arch.mirrors.lavatech.top/$repo/os/$arch
#Server = https://arch.mirrors.lavatech.top/$repo/os/$arch
#Server = http://mirror.ams1.nl.leaseweb.net/archlinux/$repo/os/$arch
#Server = https://mirror.ams1.nl.leaseweb.net/archlinux/$repo/os/$arch
#Server = http://archlinux.mirror.liteserver.nl/$repo/os/$arch
#Server = https://archlinux.mirror.liteserver.nl/$repo/os/$arch
#Server = http://mirror.lyrahosting.com/archlinux/$repo/os/$arch
#Server = https://mirror.lyrahosting.com/archlinux/$repo/os/$arch
#Server = http://mirror.mijn.host/archlinux/$repo/os/$arch
#Server = https://mirror.mijn.host/archlinux/$repo/os/$arch
#Server = http://ftp.nluug.nl/os/Linux/distr/archlinux/$repo/os/$arch
#Server = http://ftp.snt.utwente.nl/pub/os/linux/archlinux/$repo/os/$arch
#Server = http://archlinux.mirror.wearetriple.com/$repo/os/$arch
#Server = https://archlinux.mirror.wearetriple.com/$repo/os/$arch
#Server = http://mirrors.xtom.nl/archlinux/$repo/os/$arch
#Server = https://mirrors.xtom.nl/archlinux/$repo/os/$arch
## New Caledonia
#Server = http://mirror.lagoon.nc/pub/archlinux/$repo/os/$arch
#Server = http://archlinux.nautile.nc/archlinux/$repo/os/$arch
#Server = https://archlinux.nautile.nc/archlinux/$repo/os/$arch
## New Zealand
#Server = http://mirror.2degrees.nz/archlinux/$repo/os/$arch
#Server = https://mirror.2degrees.nz/archlinux/$repo/os/$arch
#Server = http://mirror.fsmg.org.nz/archlinux/$repo/os/$arch
#Server = https://mirror.fsmg.org.nz/archlinux/$repo/os/$arch
#Server = https://nz.arch.niranjan.co/$repo/os/$arch
#Server = https://archlinux.ourhome.kiwi/$repo/os/$arch
## North Macedonia
#Server = http://arch.softver.org.mk/archlinux/$repo/os/$arch
#Server = http://mirror.onevip.mk/archlinux/$repo/os/$arch
#Server = http://mirror.t-home.mk/archlinux/$repo/os/$arch
#Server = https://mirror.t-home.mk/archlinux/$repo/os/$arch
## Norway
#Server = http://mirror.archlinux.no/$repo/os/$arch
#Server = https://mirror.archlinux.no/$repo/os/$arch
#Server = http://archlinux.uib.no/$repo/os/$arch
#Server = http://mirror.neuf.no/archlinux/$repo/os/$arch
#Server = https://mirror.neuf.no/archlinux/$repo/os/$arch
## Poland
#Server = http://ftp.icm.edu.pl/pub/Linux/dist/archlinux/$repo/os/$arch
#Server = https://ftp.icm.edu.pl/pub/Linux/dist/archlinux/$repo/os/$arch
#Server = http://mirror.juniorjpdj.pl/archlinux/$repo/os/$arch
#Server = https://mirror.juniorjpdj.pl/archlinux/$repo/os/$arch
#Server = http://arch.midov.pl/arch/$repo/os/$arch
#Server = https://arch.midov.pl/arch/$repo/os/$arch
#Server = https://mirror.przekichane.pl/archlinux/$repo/os/$arch
#Server = http://ftp.psnc.pl/linux/archlinux/$repo/os/$arch
#Server = https://ftp.psnc.pl/linux/archlinux/$repo/os/$arch
#Server = http://arch.sakamoto.pl/$repo/os/$arch
#Server = https://arch.sakamoto.pl/$repo/os/$arch
## Portugal
#Server = http://mirror.barata.pt/archlinux/$repo/os/$arch
#Server = https://mirror.barata.pt/archlinux/$repo/os/$arch
#Server = http://glua.ua.pt/pub/archlinux/$repo/os/$arch
#Server = https://glua.ua.pt/pub/archlinux/$repo/os/$arch
#Server = http://mirrors.up.pt/pub/archlinux/$repo/os/$arch
#Server = https://mirrors.up.pt/pub/archlinux/$repo/os/$arch
#Server = http://ftp.rnl.tecnico.ulisboa.pt/pub/archlinux/$repo/os/$arch
#Server = https://ftp.rnl.tecnico.ulisboa.pt/pub/archlinux/$repo/os/$arch
## Romania
#Server = http://mirror.ro.cdn-perfprod.com/archlinux/$repo/os/$arch
#Server = https://mirror.ro.cdn-perfprod.com/archlinux/$repo/os/$arch
#Server = http://mirrors.chroot.ro/archlinux/$repo/os/$arch
#Server = https://mirrors.chroot.ro/archlinux/$repo/os/$arch
#Server = http://mirror.efect.ro/archlinux/$repo/os/$arch
#Server = https://mirror.efect.ro/archlinux/$repo/os/$arch
#Server = http://ro.mirror.flokinet.net/archlinux/$repo/os/$arch
#Server = https://ro.mirror.flokinet.net/archlinux/$repo/os/$arch
#Server = http://mirrors.hosterion.ro/archlinux/$repo/os/$arch
#Server = https://mirrors.hosterion.ro/archlinux/$repo/os/$arch
#Server = http://mirrors.hostico.ro/archlinux/$repo/os/$arch
#Server = https://mirrors.hostico.ro/archlinux/$repo/os/$arch
#Server = http://archlinux.mirrors.linux.ro/$repo/os/$arch
#Server = http://mirrors.nav.ro/archlinux/$repo/os/$arch
#Server = https://ro.arch.niranjan.co/$repo/os/$arch
#Server = http://mirrors.nxthost.com/archlinux/$repo/os/$arch
#Server = https://mirrors.nxthost.com/archlinux/$repo/os/$arch
#Server = http://mirrors.pidginhost.com/arch/$repo/os/$arch
#Server = https://mirrors.pidginhost.com/arch/$repo/os/$arch
## Russia
#Server = http://archlinux.gay/archlinux/$repo/os/$arch
#Server = https://archlinux.gay/archlinux/$repo/os/$arch
#Server = http://ru.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://ru.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.kpfu.ru/archlinux/$repo/os/$arch
#Server = https://mirror.kpfu.ru/archlinux/$repo/os/$arch
#Server = http://mirror.nw-sys.ru/archlinux/$repo/os/$arch
#Server = https://mirror.nw-sys.ru/archlinux/$repo/os/$arch
#Server = http://mirrors.powernet.com.ru/archlinux/$repo/os/$arch
#Server = http://repository.su/archlinux/$repo/os/$arch
#Server = https://repository.su/archlinux/$repo/os/$arch
#Server = http://web.sketserv.ru/archlinux/$repo/os/$arch
#Server = https://web.sketserv.ru/archlinux/$repo/os/$arch
#Server = https://mirror2.sl-chat.ru/archlinux/$repo/os/$arch
#Server = https://mirror3.sl-chat.ru/archlinux/$repo/os/$arch
#Server = http://mirror.truenetwork.ru/archlinux/$repo/os/$arch
#Server = https://mirror.truenetwork.ru/archlinux/$repo/os/$arch
#Server = http://vladivostokst.ru/archlinux/$repo/os/$arch
#Server = https://vladivostokst.ru/archlinux/$repo/os/$arch
#Server = http://mirror.yandex.ru/archlinux/$repo/os/$arch
#Server = https://mirror.yandex.ru/archlinux/$repo/os/$arch
## Réunion
#Server = http://arch.mithril.re/$repo/os/$arch
## Saudi Arabia
#Server = http://sa.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://sa.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.maeen.sa/arch-mirror/$repo/os/$arch
#Server = https://mirror.maeen.sa/arch-mirror/$repo/os/$arch
## Serbia
#Server = http://mirror.pmf.kg.ac.rs/archlinux/$repo/os/$arch
#Server = http://mirror1.sox.rs/archlinux/$repo/os/$arch
#Server = https://mirror1.sox.rs/archlinux/$repo/os/$arch
## Singapore
#Server = http://mirror.aktkn.sg/archlinux/$repo/os/$arch
#Server = https://mirror.aktkn.sg/archlinux/$repo/os/$arch
#Server = http://mirror.sg.cdn-perfprod.com/archlinux/$repo/os/$arch
#Server = https://mirror.sg.cdn-perfprod.com/archlinux/$repo/os/$arch
#Server = http://sg.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://sg.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://download.nus.edu.sg/mirror/archlinux/$repo/os/$arch
#Server = https://singapore.mirror.pkgbuild.com/$repo/os/$arch
#Server = http://mirror.guillaumea.fr/archlinux/$repo/os/$arch
#Server = https://mirror.guillaumea.fr/archlinux/$repo/os/$arch
#Server = http://mirror.jingk.ai/archlinux/$repo/os/$arch
#Server = https://mirror.jingk.ai/archlinux/$repo/os/$arch
#Server = https://sg.arch.niranjan.co/$repo/os/$arch
#Server = http://ossmirror.mycloud.services/os/linux/archlinux/$repo/os/$arch
#Server = http://mirror.sg.gs/archlinux/$repo/os/$arch
#Server = https://mirror.sg.gs/archlinux/$repo/os/$arch
## Slovakia
#Server = http://ftp.energotel.sk/pub/linux/arch/$repo/os/$arch
#Server = https://ftp.energotel.sk/pub/linux/arch/$repo/os/$arch
#Server = http://mirror.lnx.sk/pub/linux/archlinux/$repo/os/$arch
#Server = https://mirror.lnx.sk/pub/linux/archlinux/$repo/os/$arch
#Server = http://tux.rainside.sk/archlinux/$repo/os/$arch
## Slovenia
#Server = https://mirror.archlinux.si/$repo/os/$arch
#Server = https://www.sooftware.com/mirrors/Arch-Linux/$repo/os/$arch
#Server = http://mirror.tux.si/arch/$repo/os/$arch
#Server = https://mirror.tux.si/arch/$repo/os/$arch
## South Africa
#Server = http://archlinux.za.mirror.allworldit.com/archlinux/$repo/os/$arch
#Server = https://archlinux.za.mirror.allworldit.com/archlinux/$repo/os/$arch
#Server = http://za.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://za.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://johannesburg.mirror.pkgbuild.com/$repo/os/$arch
#Server = http://mirror.is.co.za/mirror/archlinux.org/$repo/os/$arch
#Server = http://mirrors.urbanwave.co.za/archlinux/$repo/os/$arch
#Server = https://mirrors.urbanwave.co.za/archlinux/$repo/os/$arch
## South Korea
#Server = http://kr.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://kr.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.techlabs.co.kr/archlinux/$repo/os/$arch
#Server = https://mirror.techlabs.co.kr/archlinux/$repo/os/$arch
#Server = http://mirror.distly.kr/archlinux/$repo/os/$arch
#Server = https://mirror.distly.kr/archlinux/$repo/os/$arch
#Server = http://ftp.kaist.ac.kr/ArchLinux/$repo/os/$arch
#Server = http://ftp.hrts.kr/archlinux/$repo/os/$arch
#Server = https://ftp.hrts.kr/archlinux/$repo/os/$arch
#Server = http://mirror.keiminem.com/archlinux/$repo/os/$arch
#Server = http://mirror2.keiminem.com/archlinux/$repo/os/$arch
#Server = https://mirror.keiminem.com/archlinux/$repo/os/$arch
#Server = https://mirror2.keiminem.com/archlinux/$repo/os/$arch
#Server = https://mirror.krfoss.org/archlinux/$repo/os/$arch
#Server = http://ftp.lanet.kr/pub/archlinux/$repo/os/$arch
#Server = https://ftp.lanet.kr/pub/archlinux/$repo/os/$arch
#Server = http://mirror.siwoo.org/archlinux/$repo/os/$arch
#Server = https://mirror.siwoo.org/archlinux/$repo/os/$arch
## Spain
#Server = http://mirror.es.cdn-perfprod.com/archlinux/$repo/os/$arch
#Server = https://mirror.es.cdn-perfprod.com/archlinux/$repo/os/$arch
#Server = http://es.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://es.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://nox.panibrez.com/archlinux/$repo/os/$arch
#Server = http://mirror.raiolanetworks.com/archlinux/$repo/os/$arch
#Server = https://mirror.raiolanetworks.com/archlinux/$repo/os/$arch
#Server = https://ftp.rediris.es/mirror/archlinux/$repo/os/$arch
## Sweden
#Server = http://mirror.accum.se/mirror/archlinux/$repo/os/$arch
#Server = https://mirror.accum.se/mirror/archlinux/$repo/os/$arch
#Server = https://mirror.braindrainlan.nu/archlinux/$repo/os/$arch
#Server = http://ftpmirror.infania.net/mirror/archlinux/$repo/os/$arch
#Server = http://ftp.ludd.ltu.se/mirrors/archlinux/$repo/os/$arch
#Server = https://ftp.ludd.ltu.se/mirrors/archlinux/$repo/os/$arch
#Server = http://ftp.lysator.liu.se/pub/archlinux/$repo/os/$arch
#Server = https://ftp.lysator.liu.se/pub/archlinux/$repo/os/$arch
#Server = http://mirror.bahnhof.net/pub/archlinux/$repo/os/$arch
#Server = https://mirror.bahnhof.net/pub/archlinux/$repo/os/$arch
#Server = http://ftp.myrveln.se/pub/linux/archlinux/$repo/os/$arch
#Server = https://ftp.myrveln.se/pub/linux/archlinux/$repo/os/$arch
#Server = https://mirror.osbeck.com/archlinux/$repo/os/$arch
#Server = http://mirror.retropc.se/archlinux/$repo/os/$arch
#Server = https://mirror.retropc.se/archlinux/$repo/os/$arch
#Server = http://mirror.tedwall.se/archlinux/$repo/os/$arch
#Server = https://mirror.tedwall.se/archlinux/$repo/os/$arch
## Switzerland
#Server = http://pkg.adfinis-on-exoscale.ch/archlinux-pkgbuild/$repo/os/$arch
#Server = http://pkg.adfinis-on-exoscale.ch/archlinux/$repo/os/$arch
#Server = https://pkg.adfinis-on-exoscale.ch/archlinux-pkgbuild/$repo/os/$arch
#Server = https://pkg.adfinis-on-exoscale.ch/archlinux/$repo/os/$arch
#Server = http://mirror.arch-linux.ch/archlinux/$repo/os/$arch
#Server = https://mirror.arch-linux.ch/archlinux/$repo/os/$arch
#Server = https://archlinux.lan.brgn.ch/archlinux/$repo/os/$arch
#Server = http://ch.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://ch.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.hb9hil.org/archlinux/$repo/os/$arch
#Server = https://mirror.hb9hil.org/archlinux/$repo/os/$arch
#Server = http://mirror.init7.net/archlinux/$repo/os/$arch
#Server = https://mirror.init7.net/archlinux/$repo/os/$arch
#Server = http://mirror.metanet.ch/archlinux/$repo/os/$arch
#Server = https://mirror.metanet.ch/archlinux/$repo/os/$arch
#Server = http://mirror.puzzle.ch/archlinux/$repo/os/$arch
#Server = https://mirror.puzzle.ch/archlinux/$repo/os/$arch
#Server = https://theswissbay.ch/archlinux/$repo/os/$arch
#Server = https://mirror.ungleich.ch/mirror/packages/archlinux/$repo/os/$arch
#Server = https://mirror.worldhotspot.org/archlinux/$repo/os/$arch
## Taiwan
#Server = http://mirror.archlinux.tw/ArchLinux/$repo/os/$arch
#Server = https://mirror.archlinux.tw/ArchLinux/$repo/os/$arch
#Server = http://archlinux.ccns.ncku.edu.tw/archlinux/$repo/os/$arch
#Server = https://archlinux.ccns.ncku.edu.tw/archlinux/$repo/os/$arch
#Server = http://tw.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://tw.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://free.nchc.org.tw/arch/$repo/os/$arch
#Server = https://taipei.mirror.pkgbuild.com/$repo/os/$arch
#Server = http://archlinux.cs.nycu.edu.tw/$repo/os/$arch
#Server = https://archlinux.cs.nycu.edu.tw/$repo/os/$arch
#Server = http://mirror.twds.com.tw/archlinux/$repo/os/$arch
#Server = https://mirror.twds.com.tw/archlinux/$repo/os/$arch
## Thailand
#Server = https://mirror.cyberbits.asia/archlinux/$repo/os/$arch
#Server = http://mirror.kku.ac.th/archlinux/$repo/os/$arch
#Server = https://mirror.kku.ac.th/archlinux/$repo/os/$arch
## Türkiye
#Server = http://ftp.linux.org.tr/archlinux/$repo/os/$arch
#Server = https://tr.arch.niranjan.co/$repo/os/$arch
#Server = http://mirror.nucc.tr/arch/$repo/os/$arch
#Server = https://mirror.nucc.tr/arch/$repo/os/$arch
#Server = http://mirror.timtal.com.tr/archlinux/$repo/os/$arch
#Server = https://mirror.timtal.com.tr/archlinux/$repo/os/$arch
## Ukraine
#Server = http://distrohub.kyiv.ua/archlinux/$repo/os/$arch
#Server = https://distrohub.kyiv.ua/archlinux/$repo/os/$arch
#Server = http://repo.hyron.dev/archlinux/$repo/os/$arch
#Server = https://repo.hyron.dev/archlinux/$repo/os/$arch
#Server = http://mirror.hntr.pp.ua/archlinux/$repo/os/$arch
#Server = https://mirror.hntr.pp.ua/archlinux/$repo/os/$arch
#Server = http://mirror.hostiko.network/archlinux/$repo/os/$arch
#Server = https://mirror.hostiko.network/archlinux/$repo/os/$arch
#Server = http://archlinux.ip-connect.vn.ua/$repo/os/$arch
#Server = https://archlinux.ip-connect.vn.ua/$repo/os/$arch
#Server = http://mirror.mirohost.net/archlinux/$repo/os/$arch
#Server = https://mirror.mirohost.net/archlinux/$repo/os/$arch
#Server = http://mirrors.reitarovskyi.com.ua/archlinux/$repo/os/$arch
#Server = https://mirrors.reitarovskyi.com.ua/archlinux/$repo/os/$arch
## United Arab Emirates
#Server = https://mirror.hafeezh.com/archlinux/$repo/os/$arch
## United Kingdom
#Server = http://archlinux.uk.mirror.allworldit.com/archlinux/$repo/os/$arch
#Server = https://archlinux.uk.mirror.allworldit.com/archlinux/$repo/os/$arch
#Server = https://repo.c48.uk/arch/$repo/os/$arch
#Server = https://uk.repo.c48.uk/arch/$repo/os/$arch
#Server = http://gb.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://gb.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://london.mirror.pkgbuild.com/$repo/os/$arch
#Server = http://mirror.marcusn.net/archlinux/$repo/os/$arch
#Server = https://mirror.marcusn.net/archlinux/$repo/os/$arch
#Server = http://mirrors.melbourne.co.uk/archlinux/$repo/os/$arch
#Server = https://mirrors.melbourne.co.uk/archlinux/$repo/os/$arch
#Server = http://www.mirrorservice.org/sites/ftp.archlinux.org/$repo/os/$arch
#Server = https://www.mirrorservice.org/sites/ftp.archlinux.org/$repo/os/$arch
#Server = http://mirror.netweaver.uk/archlinux/$repo/os/$arch
#Server = https://mirror.netweaver.uk/archlinux/$repo/os/$arch
#Server = http://lon.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = https://lon.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = http://mirror.server.net/archlinux/$repo/os/$arch
#Server = https://mirror.server.net/archlinux/$repo/os/$arch
#Server = https://repo.slithery.uk/$repo/os/$arch
#Server = https://mirror.st2projects.com/archlinux/$repo/os/$arch
#Server = http://mirrors.ukfast.co.uk/sites/archlinux.org/$repo/os/$arch
#Server = https://mirrors.ukfast.co.uk/sites/archlinux.org/$repo/os/$arch
#Server = http://mirror.cov.ukservers.com/archlinux/$repo/os/$arch
#Server = https://mirror.cov.ukservers.com/archlinux/$repo/os/$arch
## United States
#Server = http://mirrors.acm.wpi.edu/archlinux/$repo/os/$arch
#Server = http://mirror.adectra.com/archlinux/$repo/os/$arch
#Server = https://mirror.adectra.com/archlinux/$repo/os/$arch
#Server = https://mirror.akane.network/archmirror/$repo/os/$arch
#Server = http://arlm.tyzoid.com/$repo/os/$arch
#Server = https://arlm.tyzoid.com/$repo/os/$arch
#Server = http://ny.us.mirrors.bjg.at/arch/$repo/os/$arch
#Server = https://ny.us.mirrors.bjg.at/arch/$repo/os/$arch
#Server = http://mirrors.bloomu.edu/archlinux/$repo/os/$arch
#Server = https://mirrors.bloomu.edu/archlinux/$repo/os/$arch
#Server = https://arch-mirror.brightlight.today/$repo/os/$arch
#Server = http://mirrors.cat.pdx.edu/archlinux/$repo/os/$arch
#Server = http://us.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = https://us.mirrors.cicku.me/archlinux/$repo/os/$arch
#Server = http://mirror.clarkson.edu/archlinux/$repo/os/$arch
#Server = https://mirror.clarkson.edu/archlinux/$repo/os/$arch
#Server = http://mirror.colonelhosting.com/archlinux/$repo/os/$arch
#Server = https://mirror.colonelhosting.com/archlinux/$repo/os/$arch
#Server = http://arch.mirror.constant.com/$repo/os/$arch
#Server = https://arch.mirror.constant.com/$repo/os/$arch
#Server = http://mirror.cs.odu.edu/archlinux/$repo/os/$arch
#Server = https://mirror.cs.odu.edu/archlinux/$repo/os/$arch
#Server = http://mirror.cs.vt.edu/pub/ArchLinux/$repo/os/$arch
#Server = http://repo.customcomputercare.com/archlinux/$repo/os/$arch
#Server = https://repo.customcomputercare.com/archlinux/$repo/os/$arch
#Server = http://distro.ibiblio.org/archlinux/$repo/os/$arch
#Server = http://codingflyboy.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://coresite.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://forksystems.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://irltoolkit.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://mirror.fcix.net/archlinux/$repo/os/$arch
#Server = http://mnvoip.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://nnenix.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://nocix.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://ohioix.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://opencolo.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://southfront.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://volico.mm.fcix.net/archlinux/$repo/os/$arch
#Server = http://ziply.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://codingflyboy.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://coresite.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://forksystems.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://irltoolkit.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://mirror.fcix.net/archlinux/$repo/os/$arch
#Server = https://mnvoip.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://nnenix.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://nocix.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://ohioix.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://opencolo.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://southfront.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://volico.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://ziply.mm.fcix.net/archlinux/$repo/os/$arch
#Server = https://losangeles.mirror.pkgbuild.com/$repo/os/$arch
#Server = http://mirrors.gigenet.com/archlinux/$repo/os/$arch
#Server = https://mirror.givebytes.net/archlinux/$repo/os/$arch
#Server = https://arch.goober.cloud/$repo/os/$arch
#Server = http://arch.hu.fo/archlinux/$repo/os/$arch
#Server = https://arch.hu.fo/archlinux/$repo/os/$arch
#Server = http://arch.hugeblank.dev/$repo/os/$arch
#Server = https://arch.hugeblank.dev/$repo/os/$arch
#Server = http://repo.ialab.dsu.edu/archlinux/$repo/os/$arch
#Server = https://repo.ialab.dsu.edu/archlinux/$repo/os/$arch
#Server = http://mirrors.iu13.net/archlinux/$repo/os/$arch
#Server = https://mirrors.iu13.net/archlinux/$repo/os/$arch
#Server = https://arch.mirror.k0.ae/$repo/os/$arch
#Server = http://mirrors.kernel.org/archlinux/$repo/os/$arch
#Server = https://mirrors.kernel.org/archlinux/$repo/os/$arch
#Server = https://mirrors.lahansons.com/archlinux/$repo/os/$arch
#Server = http://mirror.sfo12.us.leaseweb.net/archlinux/$repo/os/$arch
#Server = http://mirror.wdc1.us.leaseweb.net/archlinux/$repo/os/$arch
#Server = https://mirror.sfo12.us.leaseweb.net/archlinux/$repo/os/$arch
#Server = https://mirror.wdc1.us.leaseweb.net/archlinux/$repo/os/$arch
#Server = http://mirrors.liquidweb.com/archlinux/$repo/os/$arch
#Server = http://mirrors.lug.mtu.edu/archlinux/$repo/os/$arch
#Server = https://mirrors.lug.mtu.edu/archlinux/$repo/os/$arch
#Server = https://m.lqy.me/arch/$repo/os/$arch
#Server = https://arch.mirror.marcusspencer.us:4443/archlinux/$repo/os/$arch
#Server = http://mirror.math.princeton.edu/pub/archlinux/$repo/os/$arch
#Server = http://mirror.metrocast.net/archlinux/$repo/os/$arch
#Server = http://arch.miningtcup.me/$repo/os/$arch
#Server = https://arch.miningtcup.me/$repo/os/$arch
#Server = https://mirrors.shr.cx/arch/$repo/os/$arch
#Server = http://iad.mirrors.misaka.one/archlinux/$repo/os/$arch
#Server = https://iad.mirrors.misaka.one/archlinux/$repo/os/$arch
#Server = http://repo.miserver.it.umich.edu/archlinux/$repo/os/$arch
#Server = http://mirrors.mit.edu/archlinux/$repo/os/$arch
#Server = https://mirrors.mit.edu/archlinux/$repo/os/$arch
#Server = http://mirror.mra.sh/archlinux/$repo/os/$arch
#Server = https://mirror.mra.sh/archlinux/$repo/os/$arch
#Server = https://us.arch.niranjan.co/$repo/os/$arch
#Server = http://mirrors.ocf.berkeley.edu/archlinux/$repo/os/$arch
#Server = https://mirrors.ocf.berkeley.edu/archlinux/$repo/os/$arch
#Server = http://archmirror1.octyl.net/$repo/os/$arch
#Server = https://archmirror1.octyl.net/$repo/os/$arch
#Server = http://ftp.osuosl.org/pub/archlinux/$repo/os/$arch
#Server = https://ftp.osuosl.org/pub/archlinux/$repo/os/$arch
#Server = https://mirror.pilotfiber.com/archlinux/$repo/os/$arch
#Server = http://dfw.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = http://iad.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = http://ord.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = https://dfw.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = https://iad.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = https://ord.mirror.rackspace.com/archlinux/$repo/os/$arch
#Server = http://plug-mirror.rcac.purdue.edu/archlinux/$repo/os/$arch
#Server = https://plug-mirror.rcac.purdue.edu/archlinux/$repo/os/$arch
#Server = http://mirrors.rit.edu/archlinux/$repo/os/$arch
#Server = https://mirrors.rit.edu/archlinux/$repo/os/$arch
#Server = http://mirror.siena.edu/archlinux/$repo/os/$arch
#Server = http://mirrors.smeal.xyz/arch-linux/$repo/os/$arch
#Server = https://mirrors.smeal.xyz/arch-linux/$repo/os/$arch
#Server = http://mirrors.sonic.net/archlinux/$repo/os/$arch
#Server = https://mirrors.sonic.net/archlinux/$repo/os/$arch
#Server = https://us-mnz.soulharsh007.dev/archlinux/$repo/os/$arch
#Server = http://mirror.pit.teraswitch.com/archlinux/$repo/os/$arch
#Server = https://mirror.pit.teraswitch.com/archlinux/$repo/os/$arch
#Server = https://mirror.theash.xyz/arch/$repo/os/$arch
#Server = http://mirror.umd.edu/archlinux/$repo/os/$arch
#Server = https://mirror.umd.edu/archlinux/$repo/os/$arch
#Server = http://mirrors.vectair.net/archlinux/$repo/os/$arch
#Server = https://mirrors.vectair.net/archlinux/$repo/os/$arch
#Server = http://mirror.vtti.vt.edu/archlinux/$repo/os/$arch
#Server = http://wcbmedia.io:8000/$repo/os/$arch
#Server = http://mirrors.xmission.com/archlinux/$repo/os/$arch
#Server = http://mirrors.xtom.com/archlinux/$repo/os/$arch
#Server = https://mirrors.xtom.com/archlinux/$repo/os/$arch
#Server = https://yonderly.org/mirrors/archlinux/$repo/os/$arch
#Server = https://mirror.zackmyers.io/archlinux/$repo/os/$arch
#Server = https://zxcvfdsa.com/arch/$repo/os/$arch
## Uzbekistan
#Server = http://mirror.dc.uz/arch/$repo/os/$arch
#Server = https://mirror.dc.uz/arch/$repo/os/$arch
## Vietnam
#Server = http://mirror.bizflycloud.vn/archlinux/$repo/os/$arch
#Server = https://mirrors.huongnguyen.dev/arch/$repo/os/$arch
#Server = https://mirror.meowsmp.net/arch/$repo/os/$arch
#Server = https://mirrors.nguyenhoang.cloud/archlinux/$repo/os/$arch
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# Stormux accessibility environment variables
# Available to all users and sessions
# Accessibility support
export ACCESSIBILITY_ENABLED=1
export GTK_MODULES=gail:atk-bridge:canberra-gtk-module
export GNOME_ACCESSIBILITY=1
export QT_ACCESSIBILITY=1
export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1
# Dialog accessibility options
export DIALOGOPTS='--no-lines --visit-items'
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
if [[ "$(tty)" != "/dev/tty1" ]]; then
return
fi
if [[ -x /opt/configure-stormux/configure-stormux.sh ]]; then
return
fi
if ! [[ -x /usr/local/bin/configure-stormux ]]; then
return
fi
# Volume calibration FIRST
echo "Setting up audio volume..."
volume=50
wait=0
# Wait for pipewire to become available
while [[ $wait -lt 30 ]]; do
if pgrep pipewire &> /dev/null ; then
break
else
sleep 1
((wait++))
fi
done
# Volume calibration - this should be the VERY FIRST interactive part
while [[ $volume -le 130 ]]; do
clear
pactl set-sink-volume @DEFAULT_SINK@ "${volume}%" 2>/dev/null
spd-say "If this is loud enough, press enter."
if read -t4 ; then
echo "Volume set to ${volume}%"
break
else
((volume+=5))
fi
done
# For audible sudo prompts:
unset sudoFlags
if [[ -x /etc/audibleprompt.sh ]]; then
export SUDO_ASKPASS=/etc/audibleprompt.sh
export sudoFlags=("-A")
fi
clear
cat << "EOF"
Hello, and welcome to Stormux!
Let's get you set up.
Please press enter to continue.
EOF
read -r
sudo "${sudoFlags[@]}" configure-stormux
@@ -0,0 +1,4 @@
# Raspberry Pi Information
alias pi-temp='/usr/bin/vcgencmd measure_temp'
alias pi-version='cat /sys/firmware/devicetree/base/model;echo'
alias pi-ip='ip a | grep -v "127.0.0.1" | grep -E -o "([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}"'
@@ -0,0 +1,21 @@
memuse() {
ps axo rss,comm,pid \
| awk '{ proc_list[$2] += $1; } END \
{ for (proc in proc_list) { printf("%d\t%s\n", proc_list[proc],proc); }}' \
| sort -n | tail -n 10 | sort -rn \
| awk '{$1/=1024;printf "%.0fMB\t",$1}{print $2}'
}
pdf()
{
if [[ $# -ne 1 ]]; then
echo 'Usage: pdf <file>' >&2
else
local dir=$(mktemp -d -p /tmp pdf_conversion.XXXXXX)
local outFile="${1##*/}"
local outFile="${outFile%.*}"
pdftohtml -noframes -i -s "$1" "${dir}/${outFile}.html"
w3m -s "${dir}/${outFile}.html"
fi
}
+24
View File
@@ -0,0 +1,24 @@
#
# ~/.bashrc
#
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
#Change directories without using cd
shopt -s autocd
# Keep bash history in screen
export HISTFILE="${HISTFILE}${WINDOW:+.${WINDOW}}"
# load Aliases and functions
[[ -f ".bash_aliases" ]] && source .bash_aliases
[[ -f ".bash_functions" ]] && source .bash_functions
#Invironment variables
PS1='[\u@\h \W] \$ '
export DIALOGOPTS='--no-lines --visit-items'
GPG_TTY=$(tty)
export GPG_TTY
# Don't put commands prefixed with space, or duplicate commands in history
export HISTCONTROL=ignoreboth
+6
View File
@@ -0,0 +1,6 @@
# Reload changes with control+x followed by control+r
set echo-control-characters off
# History searching with up and down arrows.
"\e[A": history-search-backward
"\e[B": history-search-forward
+18
View File
@@ -0,0 +1,18 @@
vbell off
bell_msg ""
hardstatus off
startup_message off
defscrollback 4096
bind ! select 10
bind @ select 11
bind \# select 12
bind $ select 13
bind % select 14
bind ^ select 15
bind & select 16
bind * select 17
bind ( select 18
bind ) select 19
bind b eval "writebuf" 'exec !!! xclip -selection "clipboard" -i /tmp/screen-exchange'
#termcapinfo xterm|xterms|xs|rxvt ti@:te@
termcapinfo xterm* ti@:te@
+43
View File
@@ -0,0 +1,43 @@
#
# ~/.bashrc
#
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
# Change directories without using cd
shopt -s autocd
# Load aliases and functions if they exist
[[ -f "$HOME/.bash_aliases" ]] && . "$HOME/.bash_aliases"
[[ -f "$HOME/.bash_functions" ]] && . "$HOME/.bash_functions"
# Environment variables
export QT_ACCESSIBILITY=1
export QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1
# Set prompt
PS1='[\u@\h \W] \$ '
# Dialog accessibility options
export DIALOGOPTS='--no-lines --visit-items'
# Browser selection based on environment
if [[ -z "$DISPLAY" ]]; then
export BROWSER=w3m
fi
# GPG TTY
GPG_TTY=$(tty)
export GPG_TTY
# Don't put commands prefixed with space, or duplicate commands in history
export HISTCONTROL=ignoreboth
# Increase history size
export HISTSIZE=10000
export HISTFILESIZE=20000
# Add user's local bin directories to PATH
PATH="$HOME/.local/bin:$HOME/bin:$PATH"
export PATH
+25
View File
@@ -0,0 +1,25 @@
# ~/.inputrc
# Readline configuration for bash
# Reload changes with control+x followed by control+r
# Don't echo control characters
set echo-control-characters off
# History searching with up and down arrows
"\e[A": history-search-backward
"\e[B": history-search-forward
# Shift+Tab sequences for console (backward menu completion)
"\e[Z": menu-complete-backward
"\eZ": menu-complete-backward
# Alt+comma as alternative
"\e,": menu-complete-backward
# Music player keybindings (requires playerctl)
"\eX": "\C-k \C-u playerctl play\C-M"
"\eC": "\C-k \C-u playerctl play-pause\C-M"
"\eV": "\C-k \C-u playerctl stop\C-M"
"\eB": "\C-k \C-u playerctl next\C-M"
"\eU": "\C-k \C-u playerctl metadata -f '{{artist}} - {{album}} - {{title}}'\C-M"
"\e_": "\C-k \C-u playerctl volume 0.05-\C-M"
"\e+": "\C-k \C-u playerctl volume 0.05+\C-M"
@@ -0,0 +1,33 @@
# ~/.screenrc
# GNU Screen configuration
# Disable visual bell
vbell off
bell_msg ""
# Disable hardstatus
hardstatus off
# Disable startup message
startup_message off
# Set scrollback buffer size
defscrollback 4096
# Extended window numbering (0-19 instead of 0-9)
bind ! select 10
bind @ select 11
bind \# select 12
bind $ select 13
bind % select 14
bind ^ select 15
bind & select 16
bind * select 17
bind ( select 18
bind ) select 19
# Copy buffer to system clipboard (requires xclip)
bind b eval "writebuf" 'exec !!! xclip -selection "clipboard" -i /tmp/screen-exchange'
# Disable alternate screen switching (allows scrollback in terminal)
termcapinfo xterm* ti@:te@
@@ -0,0 +1,29 @@
" Date and time insertion functions
" Insert current date in format: Wednesday, January 15, 2025
function! InsertDate()
let date_string = strftime("%A, %B %d, %Y")
execute "normal! a" . date_string
endfunction
" Insert current time in format: 09:15PM
function! InsertTime()
let time_string = strftime("%I:%M%p")
execute "normal! a" . time_string
endfunction
" Insert current date and time in format: Wednesday, January 15, 2025, 09:15PM
function! InsertDateTime()
let datetime_string = strftime("%A, %B %d, %Y, %I:%M%p")
execute "normal! a" . datetime_string
endfunction
" Key mappings
nnoremap <leader>date :call InsertDate()<CR>
inoremap <leader>date <C-o>:call InsertDate()<CR>
nnoremap <leader>time :call InsertTime()<CR>
inoremap <leader>time <C-o>:call InsertTime()<CR>
nnoremap <leader>datetime :call InsertDateTime()<CR>
inoremap <leader>datetime <C-o>:call InsertDateTime()<CR>
@@ -0,0 +1,39 @@
" Emoji insertion mappings
" Basic expressions
inoremap ,:) 😊
inoremap ,:( 😢
inoremap ,:D 😃
inoremap ,;) 😉
inoremap ,:P 😛
inoremap ,:o 😮
inoremap ,:\| 😐
" Hearts and love
inoremap ,<3
inoremap ,heart
inoremap ,love 💕
inoremap ,kiss 😘
" Special expressions
inoremap ,>:) 😈
inoremap ,devil 😈
inoremap ,:* 😘
inoremap ,cool 😎
inoremap ,nerd 🤓
inoremap ,angry 😠
" Hands and gestures
inoremap ,thumbs 👍
inoremap ,clap 👏
inoremap ,wave 👋
inoremap ,peace
inoremap ,pray 🙏
" Common symbols
inoremap ,check
inoremap ,x
inoremap ,star
inoremap ,fire 🔥
inoremap ,rocket 🚀
inoremap ,tada 🎉
+77
View File
@@ -0,0 +1,77 @@
" ~/.vimrc
" Vim configuration for Stormux
" Ensure vim directory structure exists
" mkdir -p ~/.vim/{backup,swap,views}
set nocompatible " Use Vim settings (not Vi compatible)
filetype off " Required for some plugins
" Terminal and encoding
set term=linux " Make arrow and other keys work
scriptencoding utf-8
" History and undo
set history=1000 " Store more history (default is 20)
set undoreload=10000 " Maximum lines to save for undo on buffer reload
" Backup and swap file configuration
set backup " Enable backups
set backupdir=$HOME/.vim/backup//
set directory=$HOME/.vim/swap//
set viewdir=$HOME/.vim/views//
" Search settings
set ignorecase " Case insensitive search
set smartcase " Case sensitive when uppercase present
set gdefault " /g flag on :s substitutions by default
" Spell checking
set spell " Enable spell checking
" Display settings
set wrap " Wrap long lines
set linebreak " Break lines at word boundaries
set noruler " Disable ruler (for accessibility)
set laststatus=0 " Never show status line
set noshowcmd " Don't show commands in status
" Indentation settings
set shiftwidth=4 " Use indents of 4 spaces
set expandtab " Tabs are spaces, not tabs
set tabstop=4 " An indentation every four columns
set softtabstop=4 " Let backspace delete indent
" Leader key
let mapleader = ','
" Remap semicolon to colon for easier commands
nnoremap ; :
" Yank from cursor to end of line (consistent with C and D)
nnoremap Y y$
" Em-dash insertion
inoremap ,-
" Working directory shortcuts
cmap cwd lcd %:p:h
cmap cd. lcd %:p:h
" Write with sudo if you forgot to open with sudo
cmap w!! w !sudo tee % >/dev/null
" Disable automatic comment continuation
autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o
" Load vim helper functions if they exist
if filereadable(expand("~/.vim/dates.vim"))
source ~/.vim/dates.vim
endif
if filereadable(expand("~/.vim/emoji.vim"))
source ~/.vim/emoji.vim
endif
" Enable filetype detection and indenting
filetype plugin indent on
@@ -0,0 +1,7 @@
# SSH configuration for Stormux Live ISO
# This configuration allows root login for the live environment
# Users should change the root password and disable this after installation
PermitRootLogin yes
PasswordAuthentication yes
ChallengeResponseAuthentication no
@@ -0,0 +1,15 @@
# Stormux Assistance System - Client Configuration
[server]
host = assistance.stormux.org
ssh_port = 22
ssh_user = stormux-assist
tunnel_port = 2222
[session]
# Session timeout in seconds (14400 = 4 hours)
timeout = 14400
[client]
log_file = /var/log/sas.log
log_dir = ~/stormux-assist-logs/
+1
View File
@@ -0,0 +1 @@
%wheel ALL=(ALL:ALL) ALL
@@ -0,0 +1 @@
kernel.printk = 3 3 3 3
@@ -0,0 +1,3 @@
[Journal]
Storage=volatile
SystemMaxUse=20M
@@ -0,0 +1,2 @@
[Resolve]
DNSSEC=no
@@ -0,0 +1,5 @@
[Unit]
# Ensure audio and speech-dispatcher are available before starting Fenrir
# Wait for user audio initialization to complete
After=sound.target stormux-audio-setup.service speech-dispatcherd.service user@1000.service
Wants=stormux-audio-setup.service speech-dispatcherd.service user@1000.service
@@ -0,0 +1 @@
/usr/lib/systemd/system/fenrirscreenreader.service
@@ -0,0 +1,3 @@
[Service]
ExecStart=
ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin root - $TERM
@@ -0,0 +1,3 @@
[Service]
ExecStart=
ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin stormux - $TERM
@@ -0,0 +1,16 @@
[Unit]
Description=Setup RAM logging and restore logs from disk
DefaultDependencies=false
After=local-fs.target
Before=sysinit.target
RequiresMountsFor=/var
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=/bin/mkdir -p /var/log.hdd
ExecStartPre=/bin/sh -c 'if [ -d /var/log.hdd ] && [ "$(ls -A /var/log.hdd 2>/dev/null)" ]; then cp -au /var/log.hdd/* /var/log/ 2>/dev/null || true; fi'
ExecStart=/bin/true
[Install]
WantedBy=sysinit.target
@@ -0,0 +1,14 @@
[Unit]
Description=Sync logs to disk before shutdown
DefaultDependencies=false
Before=shutdown.target reboot.target halt.target
Conflicts=shutdown.target reboot.target halt.target
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/bin/sh -c 'rsync -aXv --delete --exclude="*.hdd" /var/log/ /var/log.hdd/ 2>/dev/null || true'
TimeoutStopSec=30
[Install]
WantedBy=shutdown.target reboot.target halt.target
@@ -0,0 +1,8 @@
[Unit]
Description=Sync logs from RAM to disk
After=local-fs.target
[Service]
Type=oneshot
ExecStartPre=/bin/mkdir -p /var/log.hdd
ExecStart=/bin/sh -c 'rsync -aXv --delete --exclude="*.hdd" /var/log/ /var/log.hdd/ 2>/dev/null || true'
@@ -0,0 +1,11 @@
[Unit]
Description=Sync logs from RAM to disk every hour
Requires=log-to-ram-sync.service
[Timer]
OnBootSec=15min
OnUnitActiveSec=1h
Persistent=true
[Install]
WantedBy=timers.target
@@ -0,0 +1 @@
/usr/lib/systemd/system/NetworkManager.service
@@ -0,0 +1 @@
/usr/lib/systemd/system/brltty.path
@@ -0,0 +1 @@
../ssh-login-monitor.service
@@ -0,0 +1 @@
../stormux-repo-init.service
@@ -0,0 +1,13 @@
[Unit]
Description=Setup Pipewire for Live Environment
After=sound.target
Before=fenrirscreenreader.service speech-dispatcherd.service
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/setup-pipewire-live.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1 @@
/usr/lib/systemd/system/sshd.socket
@@ -0,0 +1 @@
../stormux-audio-setup.service
@@ -0,0 +1,17 @@
[Unit]
Description=Fenrir SSH Login Monitor
After=sshd.service
Wants=sshd.service
[Service]
Type=simple
ExecStart=/usr/share/fenrirscreenreader/scripts/ssh-login-monitor.sh
Restart=on-failure
RestartSec=5
# Security settings
NoNewPrivileges=true
PrivateTmp=false
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,17 @@
[Unit]
Description=Setup audio for Stormux live environment
# Wait for audio hardware to be ready
Wants=systemd-udev-settle.service
After=systemd-udev-settle.service sound.target
[Service]
Type=oneshot
RemainAfterExit=yes
# Enable linger so pipewire can run without login
ExecStartPre=/bin/mkdir -p /var/lib/systemd/linger
ExecStartPre=/bin/touch /var/lib/systemd/linger/stormux
# Unmute and set volume
ExecStart=/usr/local/bin/livecd-sound
[Install]
WantedBy=sound.target
@@ -0,0 +1,13 @@
[Unit]
Description=Initialize Stormux repository key
After=pacman-init.service
Requires=pacman-init.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/pacman-key --add /usr/share/stormux/stormux_repo.pub
ExecStart=/usr/bin/pacman-key --lsign-key 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,16 @@
[Unit]
Description=Stormux screen reader and speech service
After=stormux-audio-setup.service sound.target
Wants=stormux-audio-setup.service
Before=getty@tty1.service
[Service]
Type=oneshot
RemainAfterExit=yes
# Give pipewire a moment to initialize
ExecStartPre=/bin/sleep 2
# Start Fenrir screen reader
ExecStart=/bin/systemctl start fenrirscreenreader.service
[Install]
WantedBy=multi-user.target
@@ -0,0 +1 @@
/dev/null
@@ -0,0 +1,11 @@
[Unit]
Description=Update system time from worldtimeapi.org
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/sync_time.sh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1 @@
SUBSYSTEM=="tty", KERNEL=="tty[0-9]*|hvc[0-9]*|sclp_line[0-9]*|ttysclp[0-9]*|3270/tty[0-9]*", GROUP="tty", MODE="0620"
+3
View File
@@ -0,0 +1,3 @@
# This is the fallback vconsole configuration provided by systemd.
KEYMAP=us_alt

Some files were not shown because too many files have changed in this diff Show More