Compare commits
35 Commits
3c5490ea24
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| eb36d6d976 | |||
| 040eecca09 | |||
| 973b2573c8 | |||
| 9a0a6fef00 | |||
| 9496559875 | |||
| ddab0d827f | |||
| aacdf8eb4a | |||
| a6c65ca973 | |||
| 19fd4b1ed4 | |||
| 185d098bdd | |||
| 140a1c8f88 | |||
| 695b9e2f75 | |||
| acf8327949 | |||
| 08123ea6e9 | |||
| db6a4880b3 | |||
| 1a36316cfd | |||
| ccec75c4c5 | |||
| d8056278d2 | |||
| 3d62262849 | |||
| fbd1c3f671 | |||
| f4e91f7ee2 | |||
| 3f2dc0b492 | |||
| aceda3f1f8 | |||
| 013b3d11ac | |||
| dfd2609461 | |||
| b4704295ec | |||
| 855be1afcd | |||
| 3df5017766 | |||
| 34f1e24e10 | |||
| 3176d951b6 | |||
| 370d351887 | |||
| c31612ca51 | |||
| 1964121c8c | |||
| 68cbe3c0bb | |||
| a7e28c018f |
@@ -0,0 +1,7 @@
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
*.qcow2
|
||||
*.img
|
||||
*.sha1sum
|
||||
*.xz
|
||||
*.zst
|
||||
@@ -0,0 +1,57 @@
|
||||
# XLibre Updater Design
|
||||
|
||||
## Goal
|
||||
Add a repository-managed workflow for updating and building the Stormux XLibre package set in the correct dependency order, while keeping `xlibre-video-dummy-with-vt` manually maintained and reviewed against upstream `xlibre-video-dummy`.
|
||||
|
||||
## Scope
|
||||
This design covers:
|
||||
- correcting the package build order in `scripts/upgrade-xlibre.sh`
|
||||
- reviewing and updating `scripts/xlibre-video-dummy-with-vt/PKGBUILD`
|
||||
- regenerating `.SRCINFO` after PKGBUILD changes
|
||||
|
||||
This design does not automate AUR publication or rewrite the downstream package from upstream sources.
|
||||
|
||||
## Current State
|
||||
The updater script currently builds packages in an order that does not match the XLibre dependency chain. `xlibre-video-dummy-with-vt` is also version-skewed relative to the current AUR `xlibre-video-dummy` package and needs a manual rebase of relevant packaging changes.
|
||||
|
||||
## Dependency Order
|
||||
The package build order will be:
|
||||
|
||||
1. `xlibre-xserver-common`
|
||||
2. `xlibre-xserver-devel`
|
||||
3. `xlibre-input-libinput`
|
||||
4. `xlibre-xserver`
|
||||
5. `xlibre-video-fbdev`
|
||||
6. `xlibre-video-dummy-with-vt`
|
||||
|
||||
This order reflects current AUR dependencies: `xlibre-xserver` requires `xlibre-xserver-common` and `xlibre-input-libinput`, while the input and video driver packages require `xlibre-xserver-devel` to build.
|
||||
|
||||
## Downstream Package Policy
|
||||
`xlibre-video-dummy-with-vt` remains a separate, manually maintained package because it has a different maintainer and should not be auto-derived from the upstream AUR package.
|
||||
|
||||
The maintenance rule is:
|
||||
- treat AUR `xlibre-video-dummy` as the packaging baseline
|
||||
- manually port relevant upstream PKGBUILD changes into `xlibre-video-dummy-with-vt`
|
||||
- keep only the intentional downstream delta needed for VT behavior
|
||||
|
||||
## Implementation Shape
|
||||
`scripts/upgrade-xlibre.sh` will continue cloning packages from AUR and building them locally, but with the corrected order.
|
||||
|
||||
`scripts/xlibre-video-dummy-with-vt/PKGBUILD` will be updated to match the current upstream package structure where appropriate:
|
||||
- current version and release
|
||||
- current `depends` and `makedepends`
|
||||
- current build flags and source layout
|
||||
- regenerated checksums
|
||||
|
||||
The VT-specific patch remains the only behavioral divergence.
|
||||
|
||||
## Error Handling
|
||||
The script should fail fast on clone or build errors and stop rather than continuing with a broken package chain. Because package order is intentional, partial success should be considered incomplete.
|
||||
|
||||
## Verification
|
||||
Verification will be task-focused:
|
||||
- confirm the updater script contains the corrected package order
|
||||
- compare the downstream PKGBUILD against current upstream `xlibre-video-dummy`
|
||||
- regenerate and verify `.SRCINFO`
|
||||
- run `shellcheck` on the updater script
|
||||
- run `makepkg --printsrcinfo` in the downstream package directory
|
||||
@@ -0,0 +1,96 @@
|
||||
# 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: downloading Arch Linux ARM, layering the accessible defaults, compiling Fenrir and helper tools, and producing a ready-to-flash `.img`. Running the script yourself lets you reproduce the same system the project ships for the Pi 4/400, 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 20 GB 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`, `wget`, `qemu-user-static`, `qemu-user-static-binfmt`, plus standard developer tools that Arch already ships.
|
||||
- A Raspberry Pi 4/400 or similar board (a Pi 3 works with the `-v 32` option) and a microSD card (8 GB or larger recommended).
|
||||
|
||||
Install dependencies on the build host with:
|
||||
```bash
|
||||
sudo pacman -S arch-install-scripts dosfstools parted wget 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 -v 64 -l en_US -s 6
|
||||
```
|
||||
- `-v 64` builds the aarch64 image (use `-v 32` for the Pi 3/armv7h).
|
||||
- `-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 downloads the latest Arch Linux ARM tarball, provisions packages (Fenrir, Orca, NetworkManager, PipeWire, etc.), copies the overlay from `pi4/files/`, and cleans up.
|
||||
4. When the `build-stormux.sh` command returns, you should have an `.img` file in your working directory (for example `stormux-pi4-aarch64-YYYY-MM-DD.img`).
|
||||
|
||||
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.
|
||||
- **AUR components**: The script already compiles Fenrir (`fenrir-git`), `growpartfs`, `log2ram`, and `yay`. To add more AUR packages, append them to the `aurPackages` array near the middle of the script.
|
||||
- **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-pi4-*.img
|
||||
```
|
||||
2. Write it to an SD card (replace `/dev/sdX` with your card, not a partition):
|
||||
```bash
|
||||
sudo dd if=stormux-pi4-aarch64-*.img of=/dev/sdX bs=4M status=progress conv=fsync
|
||||
```
|
||||
3. Insert the card into your Pi 4/400 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 Fenrir’s layout.
|
||||
|
||||
## Testing Without Hardware
|
||||
You can boot the image in a container to verify services before flashing:
|
||||
```bash
|
||||
sudo systemd-nspawn -i stormux-pi4-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 host’s 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` file named clearly (for example, `stormux-pi4-a11y-v1.img`).
|
||||
2. Compress it (`xz -T0 -z stormux-pi4-a11y-v1.img`).
|
||||
3. Publish the image along with 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!
|
||||
+177
-23
@@ -24,16 +24,91 @@ 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 # verify_image is invoked from cleanup, which is invoked via trap EXIT
|
||||
verify_image() {
|
||||
echo "Checking completed image filesystems..."
|
||||
fsck.vfat -n "${loopdev}p1"
|
||||
e2fsck -fn "${loopdev}p2"
|
||||
}
|
||||
|
||||
# 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 verifyFilesystems="${1:-false}"
|
||||
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
|
||||
elif [[ "$verifyFilesystems" == true ]]; then
|
||||
if ! verify_image; then
|
||||
cleanupStatus=1
|
||||
fi
|
||||
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 false; then
|
||||
cleanupStatus=1
|
||||
fi
|
||||
|
||||
if [[ $status -eq 0 && $cleanupStatus -ne 0 ]]; then
|
||||
echo "Image build commands completed, but cleanup or filesystem verification failed."
|
||||
status=1
|
||||
fi
|
||||
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
finish_build() {
|
||||
trap - EXIT
|
||||
|
||||
if ! cleanup_image true; then
|
||||
echo "Image build commands completed, but cleanup or filesystem verification failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! compress_image; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -91,6 +166,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
|
||||
@@ -109,20 +190,23 @@ 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 e2fsck fsck.vfat sha1sum xz ; do
|
||||
if ! command -v "$i" &> /dev/null ; then
|
||||
echo "Please install ${i} before continuing."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
# Url for the aarch64 image to be downloaded.
|
||||
imageUrl="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%
|
||||
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
|
||||
@@ -130,9 +214,46 @@ mkdir /mnt/boot
|
||||
mount "${loopdev}p1" /mnt/boot
|
||||
# Things are mounted now, so set mounted to 0 (bash true)
|
||||
mounted=0
|
||||
imageFileName=$(mktemp)
|
||||
wget "${imageUrl}" -O "${imageFileName}"
|
||||
bsdtar -xpf "${imageFileName}" -C /mnt
|
||||
|
||||
# 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
|
||||
@@ -143,6 +264,9 @@ 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
|
||||
@@ -194,9 +318,6 @@ else
|
||||
fi
|
||||
|
||||
pacman -Syy
|
||||
# Change to the Pi kernel for aarch64
|
||||
pacman -R --noconfirm linux-aarch64 uboot-raspberrypi
|
||||
pacman -S --noconfirm linux-rpi
|
||||
|
||||
# Install all packages (stormux repo has priority since it's listed first)
|
||||
packages=(
|
||||
@@ -209,15 +330,27 @@ packages=(
|
||||
bluez-utils
|
||||
brltty
|
||||
cronie
|
||||
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
|
||||
@@ -225,9 +358,9 @@ packages=(
|
||||
pipewire-pulse
|
||||
poppler
|
||||
python-dbus
|
||||
python-gobject
|
||||
python-pyenchant
|
||||
python-pyte
|
||||
python-setuptools-scm
|
||||
python-pyperclip
|
||||
raspberrypi-utils
|
||||
socat
|
||||
@@ -241,6 +374,7 @@ packages=(
|
||||
wget
|
||||
wireless-regdb
|
||||
wireplumber
|
||||
vi
|
||||
xdg-user-dirs
|
||||
xdg-utils
|
||||
yay
|
||||
@@ -248,6 +382,18 @@ packages=(
|
||||
|
||||
pacman -Su --needed --noconfirm "\${packages[@]}"
|
||||
|
||||
# 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
|
||||
# set the language
|
||||
@@ -260,8 +406,8 @@ 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
|
||||
@@ -298,6 +444,8 @@ services=(
|
||||
log-to-ram-sync.timer
|
||||
log-to-ram-shutdown.service
|
||||
NetworkManager.service
|
||||
sshd.service
|
||||
ssh-login-monitor.service
|
||||
)
|
||||
|
||||
for service in "\${services[@]}"; do
|
||||
@@ -308,13 +456,19 @@ for service in "\${services[@]}"; do
|
||||
echo " \$service: FAILED"
|
||||
fi
|
||||
done
|
||||
# Cleanup packages
|
||||
|
||||
pacman -Sc --noconfirm
|
||||
|
||||
# Re-enable pacman sandboxing for the final image
|
||||
sed -i '/^DisableSandbox/d' /etc/pacman.conf
|
||||
|
||||
EOF
|
||||
|
||||
# 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
|
||||
|
||||
Executable
+198
@@ -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}"
|
||||
@@ -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
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
Executable
+582
@@ -0,0 +1,582 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
import signal
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import pwd
|
||||
import threading
|
||||
|
||||
|
||||
stormuxAdmin = ("storm",)
|
||||
ircServer = "irc.stormux.org"
|
||||
ircPort = 6667
|
||||
ircChannel = "#stormux"
|
||||
remoteHost = "billysballoons.com"
|
||||
sasUser = "sas"
|
||||
pingIntervalSeconds = 180
|
||||
pingCount = 5
|
||||
maxWormholeFailures = 3
|
||||
|
||||
sudoKeepaliveThread = None
|
||||
sudoKeepaliveStop = threading.Event()
|
||||
|
||||
|
||||
def speak_message(message):
|
||||
try:
|
||||
subprocess.run(["spd-say", message], check=False)
|
||||
except FileNotFoundError:
|
||||
print(message, flush=True)
|
||||
|
||||
|
||||
def say_or_print(message, useSpeech):
|
||||
if useSpeech:
|
||||
speak_message(message)
|
||||
else:
|
||||
print(message, flush=True)
|
||||
|
||||
|
||||
def run_command(command, inputText=None, check=False, env=None):
|
||||
return subprocess.run(
|
||||
command,
|
||||
input=inputText,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=check,
|
||||
env=env,
|
||||
)
|
||||
|
||||
|
||||
def ensure_sudo(useSpeech):
|
||||
if os.geteuid() == 0:
|
||||
return True
|
||||
if useSpeech:
|
||||
speak_message("Sudo password required. Please enter your password now.")
|
||||
result = run_command(["sudo", "-v"])
|
||||
if result.returncode == 0:
|
||||
start_sudo_keepalive()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def start_sudo_keepalive():
|
||||
global sudoKeepaliveThread
|
||||
if sudoKeepaliveThread and sudoKeepaliveThread.is_alive():
|
||||
return
|
||||
|
||||
def keepalive_loop():
|
||||
while not sudoKeepaliveStop.wait(240):
|
||||
run_command(["sudo", "-n", "-v"])
|
||||
|
||||
sudoKeepaliveThread = threading.Thread(target=keepalive_loop, daemon=True)
|
||||
sudoKeepaliveThread.start()
|
||||
|
||||
|
||||
def run_privileged(command, useSpeech, inputText=None, check=True):
|
||||
if os.geteuid() == 0:
|
||||
fullCommand = command
|
||||
else:
|
||||
if not ensure_sudo(useSpeech):
|
||||
raise RuntimeError("sudo authentication failed")
|
||||
fullCommand = ["sudo"] + command
|
||||
return run_command(fullCommand, inputText=inputText, check=check)
|
||||
|
||||
|
||||
def run_as_user(userName, command, useSpeech, check=True):
|
||||
if os.geteuid() == 0:
|
||||
fullCommand = ["sudo", "-u", userName, "-H"] + command
|
||||
else:
|
||||
if not ensure_sudo(useSpeech):
|
||||
raise RuntimeError("sudo authentication failed")
|
||||
fullCommand = ["sudo", "-u", userName, "-H"] + command
|
||||
return run_command(fullCommand, check=check)
|
||||
|
||||
|
||||
def user_exists(userName):
|
||||
result = run_command(["getent", "passwd", userName])
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def ensure_wheel(userName, useSpeech):
|
||||
result = run_command(["id", "-nG", userName])
|
||||
groups = result.stdout.strip().split()
|
||||
if "wheel" not in groups:
|
||||
run_privileged(["usermod", "-a", "-G", "wheel", userName], useSpeech)
|
||||
|
||||
|
||||
def generate_password():
|
||||
allowedChars = string.ascii_letters + string.digits
|
||||
length = secrets.randbelow(5) + 6
|
||||
return "".join(secrets.choice(allowedChars) for _ in range(length))
|
||||
|
||||
|
||||
def get_user_home(userName):
|
||||
return pwd.getpwnam(userName).pw_dir
|
||||
|
||||
|
||||
def set_password(userName, password, useSpeech):
|
||||
run_privileged(["chpasswd"], useSpeech, inputText=f"{userName}:{password}\n")
|
||||
|
||||
|
||||
def generate_ssh_key(userName, useSpeech):
|
||||
userHome = get_user_home(userName)
|
||||
sshDir = os.path.join(userHome, ".ssh")
|
||||
privateKeyPath = os.path.join(sshDir, "id_ed25519")
|
||||
publicKeyPath = f"{privateKeyPath}.pub"
|
||||
|
||||
run_privileged(["mkdir", "-p", sshDir], useSpeech)
|
||||
run_privileged(["chown", f"{userName}:{userName}", sshDir], useSpeech)
|
||||
run_privileged(["chmod", "700", sshDir], useSpeech)
|
||||
for entry in list_subdirs(sshDir):
|
||||
if os.path.isfile(entry) or os.path.islink(entry):
|
||||
run_privileged(["rm", "-f", entry], useSpeech, check=False)
|
||||
|
||||
run_as_user(
|
||||
userName,
|
||||
["ssh-keygen", "-t", "ed25519", "-N", "", "-f", privateKeyPath],
|
||||
useSpeech,
|
||||
)
|
||||
|
||||
run_privileged(["chmod", "600", privateKeyPath], useSpeech)
|
||||
run_privileged(["chmod", "644", publicKeyPath], useSpeech)
|
||||
run_privileged(["chown", f"{userName}:{userName}", privateKeyPath, publicKeyPath], useSpeech)
|
||||
|
||||
return privateKeyPath, publicKeyPath
|
||||
|
||||
|
||||
def path_exists_for_user(path, userName, useSpeech):
|
||||
result = run_as_user(userName, ["stat", path], useSpeech, check=False)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def extract_message_text(line):
|
||||
if "> " in line:
|
||||
return line.split("> ", 1)[1].strip()
|
||||
if ": " in line:
|
||||
return line.split(": ", 1)[1].strip()
|
||||
return line.strip()
|
||||
|
||||
|
||||
def parse_sender(line):
|
||||
match = re.search(r"<([^>]+)>", line)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def find_wormhole_code(message):
|
||||
lowered = message.strip().lower()
|
||||
if lowered in ("yes", "accept"):
|
||||
return None
|
||||
match = re.search(r"\b\d+-[A-Za-z0-9-]+\b", message)
|
||||
if match:
|
||||
return match.group(0)
|
||||
return None
|
||||
|
||||
|
||||
class IrcSession:
|
||||
def __init__(self, server, port, nick, channel, baseDir):
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.nick = nick
|
||||
self.channel = channel
|
||||
self.baseDir = baseDir
|
||||
self.serverDir = None
|
||||
self.serverInPath = None
|
||||
self.channelInPath = None
|
||||
self.iiProcess = None
|
||||
self.pmOffsets = {}
|
||||
|
||||
def start(self):
|
||||
if not shutil_which("ii"):
|
||||
raise RuntimeError("ii is not installed")
|
||||
supportsI = ii_supports_i()
|
||||
processEnv = os.environ.copy()
|
||||
iiCommand = ["ii", "-s", self.server, "-p", str(self.port), "-n", self.nick]
|
||||
if supportsI:
|
||||
iiCommand += ["-i", self.baseDir]
|
||||
else:
|
||||
processEnv["HOME"] = self.baseDir
|
||||
self.iiProcess = subprocess.Popen(
|
||||
iiCommand,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
env=processEnv,
|
||||
)
|
||||
self.serverDir = self.wait_for_server_dir()
|
||||
self.serverInPath = os.path.join(self.serverDir, "in")
|
||||
|
||||
def stop(self):
|
||||
if self.channelInPath and os.path.exists(self.channelInPath):
|
||||
try:
|
||||
self.write_line(self.channelInPath, f"/part {self.channel}")
|
||||
except OSError:
|
||||
pass
|
||||
if self.serverInPath and os.path.exists(self.serverInPath):
|
||||
try:
|
||||
self.write_line(self.serverInPath, "/quit")
|
||||
except OSError:
|
||||
pass
|
||||
if self.iiProcess and self.iiProcess.poll() is None:
|
||||
self.iiProcess.terminate()
|
||||
try:
|
||||
self.iiProcess.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.iiProcess.kill()
|
||||
|
||||
def join_channel(self):
|
||||
joinMessage = f"/join {self.channel}"
|
||||
channelDir = os.path.join(self.serverDir, self.channel)
|
||||
channelAltDir = os.path.join(self.serverDir, self.channel.lstrip("#"))
|
||||
startTime = time.monotonic()
|
||||
nextJoinTime = startTime
|
||||
while time.monotonic() - startTime < 60:
|
||||
if time.monotonic() >= nextJoinTime:
|
||||
self.write_line(self.serverInPath, joinMessage)
|
||||
nextJoinTime = time.monotonic() + 5
|
||||
for candidate in (channelDir, channelAltDir):
|
||||
inPath = os.path.join(candidate, "in")
|
||||
if os.path.exists(inPath):
|
||||
self.channelInPath = inPath
|
||||
return
|
||||
time.sleep(0.5)
|
||||
self.channelInPath = None
|
||||
|
||||
def send_channel_message(self, message):
|
||||
if not self.channelInPath:
|
||||
self.refresh_channel_in_path()
|
||||
if self.channelInPath and os.path.exists(self.channelInPath):
|
||||
self.write_line(self.channelInPath, message)
|
||||
else:
|
||||
self.write_line(self.serverInPath, f"/msg {self.channel} {message}")
|
||||
|
||||
def send_private_message(self, nick, message):
|
||||
nickDir = os.path.join(self.serverDir, nick)
|
||||
inPath = os.path.join(nickDir, "in")
|
||||
if os.path.exists(inPath):
|
||||
self.write_line(inPath, message)
|
||||
else:
|
||||
self.write_line(self.serverInPath, f"/msg {nick} {message}")
|
||||
|
||||
def get_private_messages(self, allowedUsers):
|
||||
messages = []
|
||||
for nick in allowedUsers:
|
||||
nickDir = os.path.join(self.serverDir, nick)
|
||||
outPath = os.path.join(nickDir, "out")
|
||||
if not os.path.exists(outPath):
|
||||
continue
|
||||
lastPos = self.pmOffsets.get(outPath, 0)
|
||||
with open(outPath, "r", encoding="utf-8", errors="ignore") as fileHandle:
|
||||
fileHandle.seek(lastPos)
|
||||
for line in fileHandle:
|
||||
sender = parse_sender(line)
|
||||
if sender and sender == self.nick:
|
||||
continue
|
||||
if sender and sender != nick:
|
||||
continue
|
||||
messageText = extract_message_text(line)
|
||||
if messageText:
|
||||
messages.append((nick, messageText))
|
||||
self.pmOffsets[outPath] = fileHandle.tell()
|
||||
return messages
|
||||
|
||||
def refresh_channel_in_path(self):
|
||||
channelDir = os.path.join(self.serverDir, self.channel)
|
||||
channelAltDir = os.path.join(self.serverDir, self.channel.lstrip("#"))
|
||||
for candidate in (channelDir, channelAltDir):
|
||||
inPath = os.path.join(candidate, "in")
|
||||
if os.path.exists(inPath):
|
||||
self.channelInPath = inPath
|
||||
return
|
||||
|
||||
def wait_for_server_dir(self):
|
||||
for _ in range(120):
|
||||
for rootDir in [self.baseDir] + list_subdirs(self.baseDir):
|
||||
if not os.path.isdir(rootDir):
|
||||
continue
|
||||
for entry in os.listdir(rootDir):
|
||||
path = os.path.join(rootDir, entry)
|
||||
if os.path.isdir(path) and self.server in entry:
|
||||
inPath = os.path.join(path, "in")
|
||||
if os.path.exists(inPath):
|
||||
return path
|
||||
time.sleep(0.5)
|
||||
raise RuntimeError("ii server directory not found")
|
||||
|
||||
@staticmethod
|
||||
def write_line(path, message):
|
||||
with open(path, "w", encoding="utf-8", errors="ignore") as fileHandle:
|
||||
fileHandle.write(message + "\n")
|
||||
fileHandle.flush()
|
||||
|
||||
|
||||
def ii_supports_i():
|
||||
result = run_command(["ii", "-h"])
|
||||
output = (result.stdout or "") + (result.stderr or "")
|
||||
return "-i" in output
|
||||
|
||||
|
||||
def list_subdirs(path):
|
||||
try:
|
||||
return [os.path.join(path, entry) for entry in os.listdir(path)]
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
|
||||
def shutil_which(command):
|
||||
for path in os.environ.get("PATH", "").split(os.pathsep):
|
||||
candidate = os.path.join(path, command)
|
||||
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def build_nick():
|
||||
baseUser = os.environ.get("SUDO_USER") or os.environ.get("USER") or "sas"
|
||||
return f"{baseUser}-{int(time.time())}"
|
||||
|
||||
|
||||
def main():
|
||||
say_or_print("Checking accessibility. Is your screen reader working? (y/n)", True)
|
||||
answer = input().strip().lower()
|
||||
useSpeech = answer in ("n", "no")
|
||||
|
||||
shouldRemoveUser = False
|
||||
cleanupDone = False
|
||||
tempDir = tempfile.mkdtemp(prefix="sas-ii-")
|
||||
ircSession = None
|
||||
sshProcess = None
|
||||
|
||||
def cleanup(exitMessage=None):
|
||||
nonlocal cleanupDone
|
||||
if cleanupDone:
|
||||
return
|
||||
cleanupDone = True
|
||||
nonlocal sshProcess
|
||||
if exitMessage:
|
||||
say_or_print(exitMessage, useSpeech)
|
||||
|
||||
if sshProcess and sshProcess.poll() is None:
|
||||
sshProcess.terminate()
|
||||
try:
|
||||
sshProcess.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
sshProcess.kill()
|
||||
|
||||
if ircSession:
|
||||
ircSession.stop()
|
||||
|
||||
if shouldRemoveUser:
|
||||
try:
|
||||
run_privileged(["pkill", "-u", sasUser], useSpeech, check=False)
|
||||
time.sleep(1)
|
||||
result = run_privileged(["userdel", "-r", sasUser], useSpeech, check=False)
|
||||
run_privileged(["rm", "-rf", f"/home/{sasUser}"], useSpeech, check=False)
|
||||
if result.returncode != 0 and user_exists(sasUser):
|
||||
say_or_print(
|
||||
"Cleanup warning: failed to remove sas user. Please remove it manually.",
|
||||
useSpeech,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sudoKeepaliveStop.set()
|
||||
if sudoKeepaliveThread:
|
||||
sudoKeepaliveThread.join(timeout=2)
|
||||
|
||||
try:
|
||||
remove_tree(tempDir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def handle_signal(signum, frame):
|
||||
cleanup("Interrupted. Cleaning up.")
|
||||
sys.exit(1)
|
||||
|
||||
signal.signal(signal.SIGINT, handle_signal)
|
||||
signal.signal(signal.SIGTERM, handle_signal)
|
||||
|
||||
try:
|
||||
if not user_exists(sasUser):
|
||||
run_privileged(
|
||||
["useradd", "-m", "-d", f"/home/{sasUser}", "-s", "/bin/bash", "-G", "wheel", sasUser],
|
||||
useSpeech,
|
||||
)
|
||||
shouldRemoveUser = True
|
||||
else:
|
||||
say_or_print(
|
||||
"User 'sas' exists. Remove and recreate it? This will delete /home/sas. (y/n)",
|
||||
useSpeech,
|
||||
)
|
||||
response = input().strip().lower()
|
||||
if response not in ("y", "yes"):
|
||||
cleanup("The sas user is unavailable. Remove it manually and try again.")
|
||||
return 1
|
||||
run_privileged(["pkill", "-u", sasUser], useSpeech, check=False)
|
||||
run_privileged(["userdel", "-r", sasUser], useSpeech, check=False)
|
||||
run_privileged(["rm", "-rf", f"/home/{sasUser}"], useSpeech, check=False)
|
||||
run_privileged(
|
||||
["useradd", "-m", "-d", f"/home/{sasUser}", "-s", "/bin/bash", "-G", "wheel", sasUser],
|
||||
useSpeech,
|
||||
)
|
||||
shouldRemoveUser = True
|
||||
|
||||
ensure_wheel(sasUser, useSpeech)
|
||||
|
||||
password = generate_password()
|
||||
set_password(sasUser, password, useSpeech)
|
||||
|
||||
privateKeyPath, publicKeyPath = generate_ssh_key(sasUser, useSpeech)
|
||||
sasHome = get_user_home(sasUser)
|
||||
knownHostsPath = os.path.join(sasHome, ".ssh", "known_hosts_sas")
|
||||
run_privileged(["touch", knownHostsPath], useSpeech)
|
||||
run_privileged(["chmod", "600", knownHostsPath], useSpeech)
|
||||
run_privileged(["chown", f"{sasUser}:{sasUser}", knownHostsPath], useSpeech)
|
||||
|
||||
nick = build_nick()
|
||||
ircSession = IrcSession(ircServer, ircPort, nick, ircChannel, tempDir)
|
||||
ircSession.start()
|
||||
ircSession.join_channel()
|
||||
|
||||
say_or_print("Waiting for assistance on IRC.", useSpeech)
|
||||
startTime = time.monotonic()
|
||||
nextPingTime = startTime
|
||||
pingsSent = 0
|
||||
confirmedAdmin = None
|
||||
|
||||
while time.monotonic() - startTime < pingIntervalSeconds * pingCount:
|
||||
now = time.monotonic()
|
||||
if pingsSent < pingCount and now >= nextPingTime:
|
||||
ircSession.send_channel_message(f"{nick} is requesting assistance.")
|
||||
pingsSent += 1
|
||||
nextPingTime = startTime + (pingsSent * pingIntervalSeconds)
|
||||
|
||||
for adminNick, messageText in ircSession.get_private_messages(stormuxAdmin):
|
||||
if messageText.strip().lower() in ("yes", "accept"):
|
||||
confirmedAdmin = adminNick
|
||||
break
|
||||
if confirmedAdmin:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
if not confirmedAdmin:
|
||||
cleanup("No one was available to help, please try again later.")
|
||||
return 1
|
||||
|
||||
ircSession.send_private_message(
|
||||
confirmedAdmin,
|
||||
f'password: "{password}" please send wormhole ssh invite code',
|
||||
)
|
||||
|
||||
failures = 0
|
||||
while failures < maxWormholeFailures:
|
||||
inviteCode = None
|
||||
while inviteCode is None:
|
||||
for adminNick, messageText in ircSession.get_private_messages(stormuxAdmin):
|
||||
inviteCode = find_wormhole_code(messageText)
|
||||
if inviteCode:
|
||||
break
|
||||
if inviteCode:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
if not path_exists_for_user(publicKeyPath, sasUser, useSpeech):
|
||||
raise RuntimeError(f"Public key missing: {publicKeyPath}")
|
||||
|
||||
wormholeCommand = [
|
||||
"wormhole",
|
||||
"ssh",
|
||||
"accept",
|
||||
"--yes",
|
||||
inviteCode,
|
||||
]
|
||||
result = run_as_user(sasUser, wormholeCommand, useSpeech, check=False)
|
||||
if result.returncode == 0:
|
||||
say_or_print("Wormhole key transfer succeeded.", useSpeech)
|
||||
break
|
||||
|
||||
failures += 1
|
||||
errorTextFull = (result.stderr or result.stdout or "").strip()
|
||||
if errorTextFull and not useSpeech:
|
||||
print("Wormhole ssh accept error:", flush=True)
|
||||
print(errorTextFull, flush=True)
|
||||
errorText = errorTextFull
|
||||
if errorText:
|
||||
errorText = " ".join(errorText.split())
|
||||
if len(errorText) > 400:
|
||||
errorText = errorText[:400] + "..."
|
||||
ircSession.send_private_message(
|
||||
confirmedAdmin,
|
||||
f"Wormhole ssh accept failed: {errorText}",
|
||||
)
|
||||
ircSession.send_private_message(
|
||||
confirmedAdmin,
|
||||
"Wormhole ssh accept failed. Please send a new invite code.",
|
||||
)
|
||||
|
||||
if failures >= maxWormholeFailures:
|
||||
cleanup("Wormhole failed too many times. Exiting.")
|
||||
return 1
|
||||
|
||||
say_or_print("Starting reverse SSH tunnel. Press Ctrl+C to stop.", useSpeech)
|
||||
sshCommand = [
|
||||
"ssh",
|
||||
"-N",
|
||||
"-R",
|
||||
"localhost:2232:localhost:22",
|
||||
"-o",
|
||||
"ExitOnForwardFailure=yes",
|
||||
"-o",
|
||||
"BatchMode=yes",
|
||||
"-o",
|
||||
"ServerAliveInterval=30",
|
||||
"-o",
|
||||
"ServerAliveCountMax=3",
|
||||
"-o",
|
||||
"StrictHostKeyChecking=accept-new",
|
||||
"-o",
|
||||
f"UserKnownHostsFile={knownHostsPath}",
|
||||
"-i",
|
||||
privateKeyPath,
|
||||
f"{sasUser}@{remoteHost}",
|
||||
]
|
||||
sshCommand = ["sudo", "-u", sasUser, "-H"] + sshCommand
|
||||
sshProcess = subprocess.Popen(sshCommand)
|
||||
sshProcess.wait()
|
||||
|
||||
except Exception as exc:
|
||||
cleanup(f"Error: {exc}")
|
||||
return 1
|
||||
finally:
|
||||
cleanup()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def remove_tree(path):
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
for rootDir, dirNames, fileNames in os.walk(path, topdown=False):
|
||||
for fileName in fileNames:
|
||||
try:
|
||||
os.unlink(os.path.join(rootDir, fileName))
|
||||
except OSError:
|
||||
pass
|
||||
for dirName in dirNames:
|
||||
try:
|
||||
os.rmdir(os.path.join(rootDir, dirName))
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
os.rmdir(path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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-deamon.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
|
||||
Executable
+194
@@ -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."
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
startDir="$(pwd)"
|
||||
buildDir="${startDir}/xlibre-build"
|
||||
|
||||
packageList=(
|
||||
xlibre-input-libinput
|
||||
xlibre-xserver
|
||||
xlibre-video-amdgpu
|
||||
xlibre-video-ati
|
||||
xlibre-video-fbdev
|
||||
xlibre-video-intel
|
||||
xlibre-video-nouveau
|
||||
xlibre-video-vesa
|
||||
xlibre-video-dummy-with-vt
|
||||
)
|
||||
|
||||
mkdir -p "${buildDir}"
|
||||
|
||||
for i in "${packageList[@]}" ; do
|
||||
yay -Ga "$i"
|
||||
pushd "$i"
|
||||
makepkg -Acrsf
|
||||
cp -v ./*.pkg.tar.* "${buildDir}/"
|
||||
popd
|
||||
done
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,10 +1,9 @@
|
||||
Welcome to Stormux, powered by Arch Linux ARM
|
||||
Welcome to Stormux, powered by Arch Linux
|
||||
|
||||
Stormux Website: https://stormux.org
|
||||
Arch Linux ARM Forum: https://archlinuxarm.org/forum
|
||||
Arch Linux: https://archlinux.org
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ Architecture = auto
|
||||
#IgnorePkg =
|
||||
#IgnoreGroup =
|
||||
|
||||
#NoUpgrade =
|
||||
# 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
|
||||
|
||||
@@ -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
@@ -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'
|
||||
@@ -51,11 +51,7 @@ clear
|
||||
cat << "EOF"
|
||||
Hello, and welcome to Stormux!
|
||||
|
||||
Let's get you set up. After you press enter, you will be prompted for the sudo password.
|
||||
When that happens, type the word stormux and press enter.
|
||||
You will not receive any speech feedback for this process.
|
||||
That is completely normal, and speech will return after you have typed the password.
|
||||
Once again, the password is stormux in all lower case letters.
|
||||
Let's get you set up.
|
||||
|
||||
Please press enter to continue.
|
||||
EOF
|
||||
|
||||
@@ -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
|
||||
@@ -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 🎉
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
../ssh-login-monitor.service
|
||||
@@ -0,0 +1 @@
|
||||
/usr/lib/systemd/system/sshd.socket
|
||||
@@ -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"
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Archiso airootfs customization script
|
||||
# This runs in the airootfs chroot during ISO build
|
||||
|
||||
set -e -u
|
||||
|
||||
# Initialize pacman keyring
|
||||
echo "Initializing pacman keyring..."
|
||||
pacman-key --init
|
||||
pacman-key --populate archlinux
|
||||
|
||||
# Add Stormux repository key
|
||||
echo "Adding Stormux repository key..."
|
||||
if [ -f /root/stormux_repo.pub ]; then
|
||||
pacman-key --add /root/stormux_repo.pub
|
||||
pacman-key --lsign-key 52ADA49000F1FF0456F8AEEFB4CDE1CD56EF8E82
|
||||
echo "Stormux repository key added and locally signed"
|
||||
rm /root/stormux_repo.pub
|
||||
else
|
||||
echo "Warning: Stormux repository key not found at /root/stormux_repo.pub"
|
||||
fi
|
||||
|
||||
# Set locale
|
||||
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
|
||||
locale-gen
|
||||
|
||||
# Enable system services
|
||||
systemctl enable NetworkManager.service
|
||||
systemctl enable fenrirscreenreader.service
|
||||
systemctl enable brltty.path
|
||||
|
||||
# Enable user services globally for all users
|
||||
systemctl --global enable pipewire.service pipewire-pulse.service
|
||||
|
||||
# Set permissions for stormux user home
|
||||
if [ -d /home/stormux ]; then
|
||||
chown -R 1000:1000 /home/stormux
|
||||
fi
|
||||
|
||||
# Copy Stormux custom skel files to /etc/skel
|
||||
# This is done after package installation to avoid conflicts with packages like screen
|
||||
echo "Copying Stormux custom skel files..."
|
||||
if [ -d /etc/skel.stormux ]; then
|
||||
cp -a /etc/skel.stormux/. /etc/skel/
|
||||
echo "Stormux skel files copied to /etc/skel"
|
||||
else
|
||||
echo "Warning: /etc/skel.stormux not found"
|
||||
fi
|
||||
|
||||
echo "Airootfs customization complete"
|
||||
@@ -0,0 +1,29 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGhTtIcBEADTCqY/+das1n52HTTlp3Uf5+ER5r0fNA2HgJq4GzEeFBdqdgSF
|
||||
y05P9B94d4JWtWvt0ckjsNfku19O9IquKiYP5x9/sYT8RGeRUrTlN9qlCrytTsdM
|
||||
a8ij9esGQOXDeyRJQTs5igbFWQ26MrJpOkmO8xMXGmfD29yOppRLzVS8Jed0mokk
|
||||
aEajui3QSWcefDnFMBYNaMF04iuWgVxv/7102adh3u3+V632+fQQhFtu+x92iosU
|
||||
GtWlGGe3UkEhWJIb6wf/VCYIYQAjxTQBLul2WByXqwCXmobzc7DJbte3FSEPvPis
|
||||
nJiUmT0fsIIC9c1UbTJt5Gqb6o/oI61tEJEeXieBViXFE7HwBI7FUxaIXoozgiYA
|
||||
BxaLHQT0Qv9k1SXbBm82xNydjqFHIAolYSp5s1wRIhErlOaG9w3+Tb8zFSekPsbg
|
||||
cgS/b3PpWmnigXBJQ94Ee+6KoPMVrwEKIu/IHexRUVAm+66Xs55ccgMY+MUupIjX
|
||||
Bdn9jCYB8NwpC3UNWoSv2oQ7F2hVbXZYC/dx7gKmyvzfrw5MLOX7yEoSGlJJp2iK
|
||||
fLLP8nw+x7LnDFEnAjPjtmeH7e+JANH1scP/7YMKDszO1THSK3hBAK1aeq0aEFs8
|
||||
9AO++xZrNbCrF2MnqcGaXs/753k6kf8zyrW6t7A1opxjPHIz3WrQ8SbXXwARAQAB
|
||||
tExTdG9ybXV4IChGb3Igc2lnbmluZyBzdG9ybXV4IHJlcG9zaXRvcnkgcGFja2Fn
|
||||
ZXMpIDxzdG9ybV9kcmFnb25Ac3Rvcm11eC5vcmc+iQJOBBMBCgA4FiEEUq2kkADx
|
||||
/wRW+K7vtM3hzVbvjoIFAmhTtIcCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AA
|
||||
CgkQtM3hzVbvjoLiLQ/9HJzlwr2JNGdR/LaM1Y6VuhASGDZsUsi5XLJ7qKUKN/eA
|
||||
kq0LMO7TzHO++otsEolodMr8fTkthQTPqSnbLZsW9Y3wK9U2vajryblRSEBaY6VB
|
||||
1ds0pAg5SXz7O2eI2Ne05dkYB176G72KcjrOiSanKjI9sqh6pdTHcQM3XbRxxEVn
|
||||
or7fKbLs/eveFWRPW3EAwuVnWI1aw3Xw9BQ3TE1eEDPvBh2GVKZ6qskbzdwfZtUB
|
||||
kxAa30M+ftlNDaV7d6mNfREefEJfZjYFrY2XiqTK3+w1Q1ZaugK0Bbd/YdAoGMdt
|
||||
6gbd2h/IZtxduWW/HhvWX2wtKqn4fNxAyYcY3aWm8kuq0pXlyEZ96gZas6a2lu+f
|
||||
+yLvIh3UtC1IimFMDMQnKCTbWgdhd0HMnG9ULnoOroXVGltnBQwlO0hrHSVD+qmy
|
||||
NVPUxkzS3ZhqSmoW2G8hSvXpMjt/w5fhC0FJhFb/uM/7LqJWSl5aAkUHupN3t/nG
|
||||
9o7chBQzzXwbpiS6DQuIt6ouyPpwNjeELFPNBjZDf0auVWj6ZZ5+B/bmyHXQ4wFS
|
||||
bYKZdO9VeXhBxRUMfGpIKvIw/Lnc+1Toan1RTWoUoRH+HuK2D/LAuvsam36gtLrQ
|
||||
XV3//7Mh4oQ0Zg4REG3SEfG9i3rav8wMtRmaJaSbwmMVpC6mT/uiEzCSgQhO/0c=
|
||||
=HIM9
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -1,29 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# For audible sudo prompts:
|
||||
unset sudoFlags
|
||||
if [[ -x /etc/audibleprompt.sh ]]; then
|
||||
export SUDO_ASKPASS=/etc/audibleprompt.sh
|
||||
export sudoFlags=("-A")
|
||||
fi
|
||||
|
||||
trap cleanup EXIT
|
||||
cleanup() {
|
||||
popd &> /dev/null
|
||||
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
|
||||
echo "Thank you for choosing Stormux!"
|
||||
}
|
||||
|
||||
|
||||
if [[ -x /opt/configure-stormux/configure-stormux.sh ]]; then
|
||||
pushd /opt/configure-stormux
|
||||
./configure-stormux.sh
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Volume calibration is now handled in stormux_first_boot.sh
|
||||
|
||||
export DIALOGOPTS='--insecure --no-lines --visit-items'
|
||||
@@ -36,7 +17,7 @@ set_timezone() {
|
||||
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)
|
||||
@@ -45,13 +26,13 @@ set_timezone() {
|
||||
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.
|
||||
@@ -65,29 +46,6 @@ if [[ "${continue,}" == "y" ]];then
|
||||
systemctl restart fenrirscreenreader.service
|
||||
fi
|
||||
|
||||
# Check for possible resize
|
||||
diskSource="$(df --output='source' / | tail -1)"
|
||||
diskSize="$(df -h --output='size' / | tail -1 | tr -cd '[:digit:].')"
|
||||
diskSize=${diskSize%.*}
|
||||
if [[ $diskSize -le 7 ]]; then
|
||||
echo "$diskSource is only $diskSize gigs, which means it probably needs to be resized. Would you like to do this now?"
|
||||
echo "Press y for yes or n for no followed by enter."
|
||||
read -r continue
|
||||
continue="${continue::1}"
|
||||
if [[ "${continue,}" == "y" ]];then
|
||||
# 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
|
||||
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."
|
||||
read -r continue
|
||||
@@ -103,39 +61,20 @@ if ping -qc1 -W 1 stormux.org &> /dev/null; then
|
||||
echo "Current date and time: $date_time"
|
||||
# set date and time
|
||||
date -s "$date_time"
|
||||
echo "If your Pi does not have a CMOS battery and is powered off regularly, it may take a while for the time to be set correctly after boot."
|
||||
echo "Stormux provides a service to sync the time after internet connection is established."
|
||||
read -rp "Would you like the time to sync as soon as the Pi connects to the internet? " answer
|
||||
answer="${answer:0:1}"
|
||||
if [[ "${answer,,}" == "y" ]]; then
|
||||
systemctl enable time_sync_at_boot.service
|
||||
else
|
||||
echo "Time sync at boot skipped."
|
||||
echo "If you change your mind later, simply type:"
|
||||
echo "sudo systemctl enable time_sync_at_boot.service"
|
||||
fi
|
||||
set_timezone
|
||||
else
|
||||
echo "Please connect to the internet and run ${0##*/} again."
|
||||
exit 1
|
||||
fi
|
||||
echo "Installing configure-stormux..."
|
||||
git -C /opt clone -q https://git.stormux.org/storm/configure-stormux || exit 1
|
||||
|
||||
echo
|
||||
echo "Initial setup is complete."
|
||||
echo
|
||||
echo "The default passwords are stormux for the stormux user"
|
||||
echo "and root for the root user. It is highly recommended to change them."
|
||||
echo "To change the password for stormux, run:"
|
||||
echo "passwd"
|
||||
echo "To change the password for root, run:"
|
||||
echo "sudo passwd"
|
||||
echo
|
||||
echo "For more configuration options, run configure-stormux,"
|
||||
echo "or you may configure your system manually."
|
||||
echo
|
||||
echo "Thank you for choosing Stormux."
|
||||
|
||||
|
||||
exit 0
|
||||
read -rp "Would you like to run the Stormux installer? [y/N]: " answer
|
||||
answer="${answer:0:1}"
|
||||
if [[ "${answer,,}" == "y" ]]; then
|
||||
install-stormux
|
||||
else
|
||||
echo
|
||||
echo "To run the Stormux installer, type install-stormux"
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
|
||||
+2249
File diff suppressed because it is too large
Load Diff
+1
@@ -0,0 +1 @@
|
||||
sas.sh
|
||||
@@ -0,0 +1,582 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
import signal
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import pwd
|
||||
import threading
|
||||
|
||||
|
||||
stormuxAdmin = ("storm",)
|
||||
ircServer = "irc.stormux.org"
|
||||
ircPort = 6667
|
||||
ircChannel = "#stormux"
|
||||
remoteHost = "billysballoons.com"
|
||||
sasUser = "sas"
|
||||
pingIntervalSeconds = 180
|
||||
pingCount = 5
|
||||
maxWormholeFailures = 3
|
||||
|
||||
sudoKeepaliveThread = None
|
||||
sudoKeepaliveStop = threading.Event()
|
||||
|
||||
|
||||
def speak_message(message):
|
||||
try:
|
||||
subprocess.run(["spd-say", message], check=False)
|
||||
except FileNotFoundError:
|
||||
print(message, flush=True)
|
||||
|
||||
|
||||
def say_or_print(message, useSpeech):
|
||||
if useSpeech:
|
||||
speak_message(message)
|
||||
else:
|
||||
print(message, flush=True)
|
||||
|
||||
|
||||
def run_command(command, inputText=None, check=False, env=None):
|
||||
return subprocess.run(
|
||||
command,
|
||||
input=inputText,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=check,
|
||||
env=env,
|
||||
)
|
||||
|
||||
|
||||
def ensure_sudo(useSpeech):
|
||||
if os.geteuid() == 0:
|
||||
return True
|
||||
if useSpeech:
|
||||
speak_message("Sudo password required. Please enter your password now.")
|
||||
result = run_command(["sudo", "-v"])
|
||||
if result.returncode == 0:
|
||||
start_sudo_keepalive()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def start_sudo_keepalive():
|
||||
global sudoKeepaliveThread
|
||||
if sudoKeepaliveThread and sudoKeepaliveThread.is_alive():
|
||||
return
|
||||
|
||||
def keepalive_loop():
|
||||
while not sudoKeepaliveStop.wait(240):
|
||||
run_command(["sudo", "-n", "-v"])
|
||||
|
||||
sudoKeepaliveThread = threading.Thread(target=keepalive_loop, daemon=True)
|
||||
sudoKeepaliveThread.start()
|
||||
|
||||
|
||||
def run_privileged(command, useSpeech, inputText=None, check=True):
|
||||
if os.geteuid() == 0:
|
||||
fullCommand = command
|
||||
else:
|
||||
if not ensure_sudo(useSpeech):
|
||||
raise RuntimeError("sudo authentication failed")
|
||||
fullCommand = ["sudo"] + command
|
||||
return run_command(fullCommand, inputText=inputText, check=check)
|
||||
|
||||
|
||||
def run_as_user(userName, command, useSpeech, check=True):
|
||||
if os.geteuid() == 0:
|
||||
fullCommand = ["sudo", "-u", userName, "-H"] + command
|
||||
else:
|
||||
if not ensure_sudo(useSpeech):
|
||||
raise RuntimeError("sudo authentication failed")
|
||||
fullCommand = ["sudo", "-u", userName, "-H"] + command
|
||||
return run_command(fullCommand, check=check)
|
||||
|
||||
|
||||
def user_exists(userName):
|
||||
result = run_command(["getent", "passwd", userName])
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def ensure_wheel(userName, useSpeech):
|
||||
result = run_command(["id", "-nG", userName])
|
||||
groups = result.stdout.strip().split()
|
||||
if "wheel" not in groups:
|
||||
run_privileged(["usermod", "-a", "-G", "wheel", userName], useSpeech)
|
||||
|
||||
|
||||
def generate_password():
|
||||
allowedChars = string.ascii_letters + string.digits
|
||||
length = secrets.randbelow(5) + 6
|
||||
return "".join(secrets.choice(allowedChars) for _ in range(length))
|
||||
|
||||
|
||||
def get_user_home(userName):
|
||||
return pwd.getpwnam(userName).pw_dir
|
||||
|
||||
|
||||
def set_password(userName, password, useSpeech):
|
||||
run_privileged(["chpasswd"], useSpeech, inputText=f"{userName}:{password}\n")
|
||||
|
||||
|
||||
def generate_ssh_key(userName, useSpeech):
|
||||
userHome = get_user_home(userName)
|
||||
sshDir = os.path.join(userHome, ".ssh")
|
||||
privateKeyPath = os.path.join(sshDir, "id_ed25519")
|
||||
publicKeyPath = f"{privateKeyPath}.pub"
|
||||
|
||||
run_privileged(["mkdir", "-p", sshDir], useSpeech)
|
||||
run_privileged(["chown", f"{userName}:{userName}", sshDir], useSpeech)
|
||||
run_privileged(["chmod", "700", sshDir], useSpeech)
|
||||
for entry in list_subdirs(sshDir):
|
||||
if os.path.isfile(entry) or os.path.islink(entry):
|
||||
run_privileged(["rm", "-f", entry], useSpeech, check=False)
|
||||
|
||||
run_as_user(
|
||||
userName,
|
||||
["ssh-keygen", "-t", "ed25519", "-N", "", "-f", privateKeyPath],
|
||||
useSpeech,
|
||||
)
|
||||
|
||||
run_privileged(["chmod", "600", privateKeyPath], useSpeech)
|
||||
run_privileged(["chmod", "644", publicKeyPath], useSpeech)
|
||||
run_privileged(["chown", f"{userName}:{userName}", privateKeyPath, publicKeyPath], useSpeech)
|
||||
|
||||
return privateKeyPath, publicKeyPath
|
||||
|
||||
|
||||
def path_exists_for_user(path, userName, useSpeech):
|
||||
result = run_as_user(userName, ["stat", path], useSpeech, check=False)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def extract_message_text(line):
|
||||
if "> " in line:
|
||||
return line.split("> ", 1)[1].strip()
|
||||
if ": " in line:
|
||||
return line.split(": ", 1)[1].strip()
|
||||
return line.strip()
|
||||
|
||||
|
||||
def parse_sender(line):
|
||||
match = re.search(r"<([^>]+)>", line)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def find_wormhole_code(message):
|
||||
lowered = message.strip().lower()
|
||||
if lowered in ("yes", "accept"):
|
||||
return None
|
||||
match = re.search(r"\b\d+-[A-Za-z0-9-]+\b", message)
|
||||
if match:
|
||||
return match.group(0)
|
||||
return None
|
||||
|
||||
|
||||
class IrcSession:
|
||||
def __init__(self, server, port, nick, channel, baseDir):
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.nick = nick
|
||||
self.channel = channel
|
||||
self.baseDir = baseDir
|
||||
self.serverDir = None
|
||||
self.serverInPath = None
|
||||
self.channelInPath = None
|
||||
self.iiProcess = None
|
||||
self.pmOffsets = {}
|
||||
|
||||
def start(self):
|
||||
if not shutil_which("ii"):
|
||||
raise RuntimeError("ii is not installed")
|
||||
supportsI = ii_supports_i()
|
||||
processEnv = os.environ.copy()
|
||||
iiCommand = ["ii", "-s", self.server, "-p", str(self.port), "-n", self.nick]
|
||||
if supportsI:
|
||||
iiCommand += ["-i", self.baseDir]
|
||||
else:
|
||||
processEnv["HOME"] = self.baseDir
|
||||
self.iiProcess = subprocess.Popen(
|
||||
iiCommand,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
env=processEnv,
|
||||
)
|
||||
self.serverDir = self.wait_for_server_dir()
|
||||
self.serverInPath = os.path.join(self.serverDir, "in")
|
||||
|
||||
def stop(self):
|
||||
if self.channelInPath and os.path.exists(self.channelInPath):
|
||||
try:
|
||||
self.write_line(self.channelInPath, f"/part {self.channel}")
|
||||
except OSError:
|
||||
pass
|
||||
if self.serverInPath and os.path.exists(self.serverInPath):
|
||||
try:
|
||||
self.write_line(self.serverInPath, "/quit")
|
||||
except OSError:
|
||||
pass
|
||||
if self.iiProcess and self.iiProcess.poll() is None:
|
||||
self.iiProcess.terminate()
|
||||
try:
|
||||
self.iiProcess.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.iiProcess.kill()
|
||||
|
||||
def join_channel(self):
|
||||
joinMessage = f"/join {self.channel}"
|
||||
channelDir = os.path.join(self.serverDir, self.channel)
|
||||
channelAltDir = os.path.join(self.serverDir, self.channel.lstrip("#"))
|
||||
startTime = time.monotonic()
|
||||
nextJoinTime = startTime
|
||||
while time.monotonic() - startTime < 60:
|
||||
if time.monotonic() >= nextJoinTime:
|
||||
self.write_line(self.serverInPath, joinMessage)
|
||||
nextJoinTime = time.monotonic() + 5
|
||||
for candidate in (channelDir, channelAltDir):
|
||||
inPath = os.path.join(candidate, "in")
|
||||
if os.path.exists(inPath):
|
||||
self.channelInPath = inPath
|
||||
return
|
||||
time.sleep(0.5)
|
||||
self.channelInPath = None
|
||||
|
||||
def send_channel_message(self, message):
|
||||
if not self.channelInPath:
|
||||
self.refresh_channel_in_path()
|
||||
if self.channelInPath and os.path.exists(self.channelInPath):
|
||||
self.write_line(self.channelInPath, message)
|
||||
else:
|
||||
self.write_line(self.serverInPath, f"/msg {self.channel} {message}")
|
||||
|
||||
def send_private_message(self, nick, message):
|
||||
nickDir = os.path.join(self.serverDir, nick)
|
||||
inPath = os.path.join(nickDir, "in")
|
||||
if os.path.exists(inPath):
|
||||
self.write_line(inPath, message)
|
||||
else:
|
||||
self.write_line(self.serverInPath, f"/msg {nick} {message}")
|
||||
|
||||
def get_private_messages(self, allowedUsers):
|
||||
messages = []
|
||||
for nick in allowedUsers:
|
||||
nickDir = os.path.join(self.serverDir, nick)
|
||||
outPath = os.path.join(nickDir, "out")
|
||||
if not os.path.exists(outPath):
|
||||
continue
|
||||
lastPos = self.pmOffsets.get(outPath, 0)
|
||||
with open(outPath, "r", encoding="utf-8", errors="ignore") as fileHandle:
|
||||
fileHandle.seek(lastPos)
|
||||
for line in fileHandle:
|
||||
sender = parse_sender(line)
|
||||
if sender and sender == self.nick:
|
||||
continue
|
||||
if sender and sender != nick:
|
||||
continue
|
||||
messageText = extract_message_text(line)
|
||||
if messageText:
|
||||
messages.append((nick, messageText))
|
||||
self.pmOffsets[outPath] = fileHandle.tell()
|
||||
return messages
|
||||
|
||||
def refresh_channel_in_path(self):
|
||||
channelDir = os.path.join(self.serverDir, self.channel)
|
||||
channelAltDir = os.path.join(self.serverDir, self.channel.lstrip("#"))
|
||||
for candidate in (channelDir, channelAltDir):
|
||||
inPath = os.path.join(candidate, "in")
|
||||
if os.path.exists(inPath):
|
||||
self.channelInPath = inPath
|
||||
return
|
||||
|
||||
def wait_for_server_dir(self):
|
||||
for _ in range(120):
|
||||
for rootDir in [self.baseDir] + list_subdirs(self.baseDir):
|
||||
if not os.path.isdir(rootDir):
|
||||
continue
|
||||
for entry in os.listdir(rootDir):
|
||||
path = os.path.join(rootDir, entry)
|
||||
if os.path.isdir(path) and self.server in entry:
|
||||
inPath = os.path.join(path, "in")
|
||||
if os.path.exists(inPath):
|
||||
return path
|
||||
time.sleep(0.5)
|
||||
raise RuntimeError("ii server directory not found")
|
||||
|
||||
@staticmethod
|
||||
def write_line(path, message):
|
||||
with open(path, "w", encoding="utf-8", errors="ignore") as fileHandle:
|
||||
fileHandle.write(message + "\n")
|
||||
fileHandle.flush()
|
||||
|
||||
|
||||
def ii_supports_i():
|
||||
result = run_command(["ii", "-h"])
|
||||
output = (result.stdout or "") + (result.stderr or "")
|
||||
return "-i" in output
|
||||
|
||||
|
||||
def list_subdirs(path):
|
||||
try:
|
||||
return [os.path.join(path, entry) for entry in os.listdir(path)]
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
|
||||
def shutil_which(command):
|
||||
for path in os.environ.get("PATH", "").split(os.pathsep):
|
||||
candidate = os.path.join(path, command)
|
||||
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def build_nick():
|
||||
baseUser = os.environ.get("SUDO_USER") or os.environ.get("USER") or "sas"
|
||||
return f"{baseUser}-{int(time.time())}"
|
||||
|
||||
|
||||
def main():
|
||||
say_or_print("Checking accessibility. Is your screen reader working? (y/n)", True)
|
||||
answer = input().strip().lower()
|
||||
useSpeech = answer in ("n", "no")
|
||||
|
||||
shouldRemoveUser = False
|
||||
cleanupDone = False
|
||||
tempDir = tempfile.mkdtemp(prefix="sas-ii-")
|
||||
ircSession = None
|
||||
sshProcess = None
|
||||
|
||||
def cleanup(exitMessage=None):
|
||||
nonlocal cleanupDone
|
||||
if cleanupDone:
|
||||
return
|
||||
cleanupDone = True
|
||||
nonlocal sshProcess
|
||||
if exitMessage:
|
||||
say_or_print(exitMessage, useSpeech)
|
||||
|
||||
if sshProcess and sshProcess.poll() is None:
|
||||
sshProcess.terminate()
|
||||
try:
|
||||
sshProcess.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
sshProcess.kill()
|
||||
|
||||
if ircSession:
|
||||
ircSession.stop()
|
||||
|
||||
if shouldRemoveUser:
|
||||
try:
|
||||
run_privileged(["pkill", "-u", sasUser], useSpeech, check=False)
|
||||
time.sleep(1)
|
||||
result = run_privileged(["userdel", "-r", sasUser], useSpeech, check=False)
|
||||
run_privileged(["rm", "-rf", f"/home/{sasUser}"], useSpeech, check=False)
|
||||
if result.returncode != 0 and user_exists(sasUser):
|
||||
say_or_print(
|
||||
"Cleanup warning: failed to remove sas user. Please remove it manually.",
|
||||
useSpeech,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sudoKeepaliveStop.set()
|
||||
if sudoKeepaliveThread:
|
||||
sudoKeepaliveThread.join(timeout=2)
|
||||
|
||||
try:
|
||||
remove_tree(tempDir)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def handle_signal(signum, frame):
|
||||
cleanup("Interrupted. Cleaning up.")
|
||||
sys.exit(1)
|
||||
|
||||
signal.signal(signal.SIGINT, handle_signal)
|
||||
signal.signal(signal.SIGTERM, handle_signal)
|
||||
|
||||
try:
|
||||
if not user_exists(sasUser):
|
||||
run_privileged(
|
||||
["useradd", "-m", "-d", f"/home/{sasUser}", "-s", "/bin/bash", "-G", "wheel", sasUser],
|
||||
useSpeech,
|
||||
)
|
||||
shouldRemoveUser = True
|
||||
else:
|
||||
say_or_print(
|
||||
"User 'sas' exists. Remove and recreate it? This will delete /home/sas. (y/n)",
|
||||
useSpeech,
|
||||
)
|
||||
response = input().strip().lower()
|
||||
if response not in ("y", "yes"):
|
||||
cleanup("The sas user is unavailable. Remove it manually and try again.")
|
||||
return 1
|
||||
run_privileged(["pkill", "-u", sasUser], useSpeech, check=False)
|
||||
run_privileged(["userdel", "-r", sasUser], useSpeech, check=False)
|
||||
run_privileged(["rm", "-rf", f"/home/{sasUser}"], useSpeech, check=False)
|
||||
run_privileged(
|
||||
["useradd", "-m", "-d", f"/home/{sasUser}", "-s", "/bin/bash", "-G", "wheel", sasUser],
|
||||
useSpeech,
|
||||
)
|
||||
shouldRemoveUser = True
|
||||
|
||||
ensure_wheel(sasUser, useSpeech)
|
||||
|
||||
password = generate_password()
|
||||
set_password(sasUser, password, useSpeech)
|
||||
|
||||
privateKeyPath, publicKeyPath = generate_ssh_key(sasUser, useSpeech)
|
||||
sasHome = get_user_home(sasUser)
|
||||
knownHostsPath = os.path.join(sasHome, ".ssh", "known_hosts_sas")
|
||||
run_privileged(["touch", knownHostsPath], useSpeech)
|
||||
run_privileged(["chmod", "600", knownHostsPath], useSpeech)
|
||||
run_privileged(["chown", f"{sasUser}:{sasUser}", knownHostsPath], useSpeech)
|
||||
|
||||
nick = build_nick()
|
||||
ircSession = IrcSession(ircServer, ircPort, nick, ircChannel, tempDir)
|
||||
ircSession.start()
|
||||
ircSession.join_channel()
|
||||
|
||||
say_or_print("Waiting for assistance on IRC.", useSpeech)
|
||||
startTime = time.monotonic()
|
||||
nextPingTime = startTime
|
||||
pingsSent = 0
|
||||
confirmedAdmin = None
|
||||
|
||||
while time.monotonic() - startTime < pingIntervalSeconds * pingCount:
|
||||
now = time.monotonic()
|
||||
if pingsSent < pingCount and now >= nextPingTime:
|
||||
ircSession.send_channel_message(f"{nick} is requesting assistance.")
|
||||
pingsSent += 1
|
||||
nextPingTime = startTime + (pingsSent * pingIntervalSeconds)
|
||||
|
||||
for adminNick, messageText in ircSession.get_private_messages(stormuxAdmin):
|
||||
if messageText.strip().lower() in ("yes", "accept"):
|
||||
confirmedAdmin = adminNick
|
||||
break
|
||||
if confirmedAdmin:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
if not confirmedAdmin:
|
||||
cleanup("No one was available to help, please try again later.")
|
||||
return 1
|
||||
|
||||
ircSession.send_private_message(
|
||||
confirmedAdmin,
|
||||
f'password: "{password}" please send wormhole ssh invite code',
|
||||
)
|
||||
|
||||
failures = 0
|
||||
while failures < maxWormholeFailures:
|
||||
inviteCode = None
|
||||
while inviteCode is None:
|
||||
for adminNick, messageText in ircSession.get_private_messages(stormuxAdmin):
|
||||
inviteCode = find_wormhole_code(messageText)
|
||||
if inviteCode:
|
||||
break
|
||||
if inviteCode:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
if not path_exists_for_user(publicKeyPath, sasUser, useSpeech):
|
||||
raise RuntimeError(f"Public key missing: {publicKeyPath}")
|
||||
|
||||
wormholeCommand = [
|
||||
"wormhole",
|
||||
"ssh",
|
||||
"accept",
|
||||
"--yes",
|
||||
inviteCode,
|
||||
]
|
||||
result = run_as_user(sasUser, wormholeCommand, useSpeech, check=False)
|
||||
if result.returncode == 0:
|
||||
say_or_print("Wormhole key transfer succeeded.", useSpeech)
|
||||
break
|
||||
|
||||
failures += 1
|
||||
errorTextFull = (result.stderr or result.stdout or "").strip()
|
||||
if errorTextFull and not useSpeech:
|
||||
print("Wormhole ssh accept error:", flush=True)
|
||||
print(errorTextFull, flush=True)
|
||||
errorText = errorTextFull
|
||||
if errorText:
|
||||
errorText = " ".join(errorText.split())
|
||||
if len(errorText) > 400:
|
||||
errorText = errorText[:400] + "..."
|
||||
ircSession.send_private_message(
|
||||
confirmedAdmin,
|
||||
f"Wormhole ssh accept failed: {errorText}",
|
||||
)
|
||||
ircSession.send_private_message(
|
||||
confirmedAdmin,
|
||||
"Wormhole ssh accept failed. Please send a new invite code.",
|
||||
)
|
||||
|
||||
if failures >= maxWormholeFailures:
|
||||
cleanup("Wormhole failed too many times. Exiting.")
|
||||
return 1
|
||||
|
||||
say_or_print("Starting reverse SSH tunnel. Press Ctrl+C to stop.", useSpeech)
|
||||
sshCommand = [
|
||||
"ssh",
|
||||
"-N",
|
||||
"-R",
|
||||
"localhost:2232:localhost:22",
|
||||
"-o",
|
||||
"ExitOnForwardFailure=yes",
|
||||
"-o",
|
||||
"BatchMode=yes",
|
||||
"-o",
|
||||
"ServerAliveInterval=30",
|
||||
"-o",
|
||||
"ServerAliveCountMax=3",
|
||||
"-o",
|
||||
"StrictHostKeyChecking=accept-new",
|
||||
"-o",
|
||||
f"UserKnownHostsFile={knownHostsPath}",
|
||||
"-i",
|
||||
privateKeyPath,
|
||||
f"{sasUser}@{remoteHost}",
|
||||
]
|
||||
sshCommand = ["sudo", "-u", sasUser, "-H"] + sshCommand
|
||||
sshProcess = subprocess.Popen(sshCommand)
|
||||
sshProcess.wait()
|
||||
|
||||
except Exception as exc:
|
||||
cleanup(f"Error: {exc}")
|
||||
return 1
|
||||
finally:
|
||||
cleanup()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def remove_tree(path):
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
for rootDir, dirNames, fileNames in os.walk(path, topdown=False):
|
||||
for fileName in fileNames:
|
||||
try:
|
||||
os.unlink(os.path.join(rootDir, fileName))
|
||||
except OSError:
|
||||
pass
|
||||
for dirName in dirNames:
|
||||
try:
|
||||
os.rmdir(os.path.join(rootDir, dirName))
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
os.rmdir(path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -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-deamon.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
|
||||
+43
-1
@@ -54,6 +54,9 @@ if [ "$(whoami)" != "root" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Capture the calling user for chown later
|
||||
callingUser="${SUDO_USER:-$USER}"
|
||||
|
||||
# Check for required tools
|
||||
for tool in mkarchiso pacman; do
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
@@ -64,7 +67,6 @@ done
|
||||
|
||||
# Get the directory where this script is located
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
pi4_files_dir="$script_dir/../pi4/files"
|
||||
|
||||
echo "Building Stormux x86_64 ISO..."
|
||||
echo "Profile directory: $script_dir"
|
||||
@@ -105,6 +107,46 @@ fi
|
||||
# Build the ISO
|
||||
mkarchiso -v -w "$work_dir" -o "$output_dir" "$script_dir"
|
||||
|
||||
# Rename ISO to stormux-x86_64-YYYY-MM-DD.iso
|
||||
dateStamp="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y-%m-%d)"
|
||||
pushd "$output_dir" > /dev/null
|
||||
shopt -s nullglob
|
||||
isoFiles=( *.iso )
|
||||
shopt -u nullglob
|
||||
|
||||
if [[ ${#isoFiles[@]} -eq 1 && -f "${isoFiles[0]}" ]]; then
|
||||
desiredIso="stormux-x86_64-${dateStamp}.iso"
|
||||
if [[ "${isoFiles[0]}" != "$desiredIso" ]]; then
|
||||
if [[ -e "$desiredIso" ]]; then
|
||||
echo "Warning: $desiredIso already exists; skipping rename."
|
||||
else
|
||||
mv "${isoFiles[0]}" "$desiredIso"
|
||||
echo "Renamed ISO to: $desiredIso"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Warning: expected one ISO in output, found ${#isoFiles[@]}; skipping rename."
|
||||
fi
|
||||
popd > /dev/null
|
||||
|
||||
# Generate sha1sum for the ISO
|
||||
echo
|
||||
echo "Generating sha1sum..."
|
||||
pushd "$output_dir" > /dev/null
|
||||
for isoFile in *.iso; do
|
||||
if [ -f "$isoFile" ]; then
|
||||
sha1sum "$isoFile" > "${isoFile}.sha1sum"
|
||||
echo "Created sha1sum: ${isoFile}.sha1sum"
|
||||
fi
|
||||
done
|
||||
popd > /dev/null
|
||||
|
||||
# Change ownership of output directory to calling user
|
||||
if [ -n "$callingUser" ] && [ "$callingUser" != "root" ]; then
|
||||
echo "Changing ownership of $output_dir to $callingUser..."
|
||||
chown -R "$callingUser:$callingUser" "$output_dir"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Build complete!"
|
||||
echo "ISO file is in: $output_dir"
|
||||
|
||||
@@ -2,6 +2,8 @@ alsa-firmware
|
||||
alsa-utils
|
||||
amd-ucode
|
||||
arch-install-scripts
|
||||
archinstall
|
||||
autossh
|
||||
base
|
||||
base-devel
|
||||
bash-completion
|
||||
@@ -19,6 +21,7 @@ cryptsetup
|
||||
darkhttpd
|
||||
ddrescue
|
||||
dhcpcd
|
||||
dialog
|
||||
diffutils
|
||||
dmidecode
|
||||
dmraid
|
||||
@@ -42,6 +45,7 @@ gptfdisk
|
||||
grub
|
||||
hdparm
|
||||
hyperv
|
||||
ii
|
||||
intel-ucode
|
||||
irssi
|
||||
iw
|
||||
@@ -134,7 +138,7 @@ udftools
|
||||
usb_modeswitch
|
||||
usbmuxd
|
||||
usbutils
|
||||
vim
|
||||
vi
|
||||
virtualbox-guest-utils-nox
|
||||
vpnc
|
||||
w3m-git
|
||||
|
||||
+2
-1
@@ -25,7 +25,8 @@ Architecture = auto
|
||||
#IgnorePkg =
|
||||
#IgnoreGroup =
|
||||
|
||||
#NoUpgrade =
|
||||
# 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
|
||||
|
||||
@@ -19,13 +19,22 @@ file_permissions=(
|
||||
["/etc/shadow"]="0:0:400"
|
||||
["/root"]="0:0:750"
|
||||
["/root/.automated_script.sh"]="0:0:755"
|
||||
["/usr/local/bin/sas"]="0:0:755"
|
||||
["/root/customize_airootfs.sh"]="0:0:755"
|
||||
["/root/.gnupg"]="0:0:700"
|
||||
["/root/.config"]="0:0:700"
|
||||
["/usr/local/bin/choose-mirror"]="0:0:755"
|
||||
["/usr/local/bin/Installation_guide"]="0:0:755"
|
||||
["/usr/local/bin/livecd-sound"]="0:0:755"
|
||||
["/usr/local/bin/configure-stormux"]="0:0:755"
|
||||
["/usr/local/bin/install-stormux"]="0:0:755"
|
||||
["/usr/local/bin/stormux-setup.sh"]="0:0:755"
|
||||
["/usr/local/bin/init-pipewire-sound.sh"]="0:0:755"
|
||||
["/usr/local/bin/sas.sh"]="0:0:755"
|
||||
["/usr/local/bin/sas"]="0:0:755"
|
||||
["/usr/share/fenrirscreenreader/scripts/ssh-login-monitor.sh"]="0:0:755"
|
||||
["/etc/stormux-assist"]="0:0:755"
|
||||
["/etc/stormux-assist/client.conf"]="0:0:644"
|
||||
["/etc/udev/rules.d/99-brltty.rules"]="0:0:644"
|
||||
["/home/stormux"]="1000:1000:755"
|
||||
)
|
||||
|
||||
+87
-27
@@ -21,10 +21,34 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Parse command line arguments
|
||||
bootInstalled=false
|
||||
while getopts "ih" opt; do
|
||||
case "$opt" in
|
||||
i) bootInstalled=true ;;
|
||||
h)
|
||||
echo "Usage: $0 [options]"
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " -i Boot from installed system (virtual disk) instead of ISO"
|
||||
echo " -h Show this help"
|
||||
echo
|
||||
echo "Without -i flag, boots from ISO with virtual disk attached for installation."
|
||||
echo "With -i flag, boots only from virtual disk (installed system)."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Use -h for help"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Get the directory where this script is located
|
||||
scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
outDir="${scriptDir}/out"
|
||||
logDir="${scriptDir}/logs"
|
||||
diskFile="${scriptDir}/stormux-test-disk.qcow2"
|
||||
|
||||
# Create logs directory if it doesn't exist
|
||||
mkdir -p "$logDir"
|
||||
@@ -34,31 +58,54 @@ timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
qemuLog="${logDir}/qemu-boot_${timestamp}.log"
|
||||
serialLog="${logDir}/serial-console_${timestamp}.log"
|
||||
|
||||
# Find the most recent ISO in the out directory
|
||||
if [[ ! -d "$outDir" ]]; then
|
||||
echo "Error: Output directory not found: $outDir"
|
||||
echo "Please build an ISO first using: sudo ./build.sh"
|
||||
exit 1
|
||||
# Create virtual disk if it doesn't exist
|
||||
if [[ ! -f "$diskFile" ]]; then
|
||||
echo "Creating virtual disk: $diskFile (10GB)"
|
||||
qemu-img create -f qcow2 "$diskFile" 10G
|
||||
echo "Virtual disk created successfully"
|
||||
echo
|
||||
fi
|
||||
|
||||
# Find the most recent ISO file
|
||||
isoFile=$(find "$outDir" -name "*.iso" -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
|
||||
# Find ISO file (only needed if not booting installed system)
|
||||
isoFile=""
|
||||
if [[ "$bootInstalled" == false ]]; then
|
||||
# Find the most recent ISO in the out directory
|
||||
if [[ ! -d "$outDir" ]]; then
|
||||
echo "Error: Output directory not found: $outDir"
|
||||
echo "Please build an ISO first using: sudo ./build.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$isoFile" ]]; then
|
||||
echo "Error: No ISO file found in $outDir"
|
||||
echo "Please build an ISO first using: sudo ./build.sh"
|
||||
exit 1
|
||||
# Find the most recent ISO file
|
||||
isoFile=$(find "$outDir" -name "*.iso" -type f -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
|
||||
|
||||
if [[ -z "$isoFile" ]]; then
|
||||
echo "Error: No ISO file found in $outDir"
|
||||
echo "Please build an ISO first using: sudo ./build.sh"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Found ISO: $isoFile"
|
||||
echo "Booting Stormux in QEMU with audio support..."
|
||||
if [[ "$bootInstalled" == true ]]; then
|
||||
echo "Booting installed system from: $diskFile"
|
||||
else
|
||||
echo "Found ISO: $isoFile"
|
||||
echo "Booting Stormux ISO in QEMU with virtual disk attached..."
|
||||
echo "Virtual disk: $diskFile"
|
||||
fi
|
||||
echo
|
||||
echo "Logging to:"
|
||||
echo " QEMU log: $qemuLog"
|
||||
echo " Serial console: $serialLog"
|
||||
echo
|
||||
echo "Note: After boot, press Ctrl+Alt+F2 to switch to tty2"
|
||||
echo " where the stormux user will be logged in with Fenrir screen reader."
|
||||
if [[ "$bootInstalled" == false ]]; then
|
||||
echo "Note: After boot, press Ctrl+Alt+F2 to switch to tty2"
|
||||
echo " where the stormux user will be logged in with Fenrir screen reader."
|
||||
echo " Run 'sudo install-stormux' to install to the virtual disk."
|
||||
echo " After installation, use './qemu-boot.sh -i' to boot the installed system."
|
||||
else
|
||||
echo "Note: Booting from installed system on virtual disk."
|
||||
fi
|
||||
echo " You can monitor the serial console log in real-time with:"
|
||||
echo " tail -f $serialLog"
|
||||
echo
|
||||
@@ -66,6 +113,30 @@ echo
|
||||
# Export DISPLAY for console usage
|
||||
export DISPLAY="${DISPLAY:-:0}"
|
||||
|
||||
# Build QEMU command based on boot mode
|
||||
# Common options for both modes
|
||||
# shellcheck disable=SC2054
|
||||
qemuCmd=(
|
||||
qemu-system-x86_64
|
||||
-enable-kvm
|
||||
-m 2048
|
||||
-drive file="$diskFile",format=qcow2
|
||||
-audiodev pa,id=snd0
|
||||
-device intel-hda
|
||||
-device hda-duplex,audiodev=snd0
|
||||
-serial file:"$serialLog"
|
||||
-D "$qemuLog"
|
||||
-d guest_errors,cpu_reset
|
||||
-display gtk
|
||||
)
|
||||
|
||||
# Add ISO and boot order if booting from ISO
|
||||
if [[ "$bootInstalled" == false ]]; then
|
||||
qemuCmd+=(-cdrom "$isoFile" -boot order=dc)
|
||||
else
|
||||
qemuCmd+=(-boot c)
|
||||
fi
|
||||
|
||||
# Boot QEMU with audio support and logging
|
||||
# -D: QEMU debug log (shows QEMU errors, device issues)
|
||||
# -d: Debug categories (guest_errors, cpu_reset)
|
||||
@@ -73,15 +144,4 @@ export DISPLAY="${DISPLAY:-:0}"
|
||||
# -audiodev: PulseAudio backend for audio
|
||||
# -device intel-hda: Intel HD Audio controller
|
||||
# -device hda-duplex: HD Audio codec with input/output, connected to audiodev
|
||||
exec qemu-system-x86_64 \
|
||||
-enable-kvm \
|
||||
-m 2048 \
|
||||
-cdrom "$isoFile" \
|
||||
-boot d \
|
||||
-audiodev pa,id=snd0 \
|
||||
-device intel-hda \
|
||||
-device hda-duplex,audiodev=snd0 \
|
||||
-serial file:"$serialLog" \
|
||||
-D "$qemuLog" \
|
||||
-d guest_errors,cpu_reset \
|
||||
-display gtk
|
||||
exec "${qemuCmd[@]}"
|
||||
|
||||
Reference in New Issue
Block a user