Compare commits

...
4 Commits
23 changed files with 843 additions and 2 deletions
+8
View File
@@ -0,0 +1,8 @@
/output/
/tmp/
/terraform/.terraform/
/terraform/terraform.tfstate
/terraform/terraform.tfstate.*
/terraform/*.tfvars
/terraform/*.tfvars.json
/terraform/crash.log
+122
View File
@@ -0,0 +1,122 @@
SHELL := /bin/bash
.DEFAULT_GOAL := help
ROOT_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
OUTPUT_DIR := $(ROOT_DIR)/output
TMP_DIR := $(ROOT_DIR)/tmp
VERSION ?=
LIBVIRT_URI ?= qemu:///system
LIBVIRT_POOL ?= default
LIBVIRT_NETWORK ?= default
TERRAFORM_DIR := $(ROOT_DIR)/terraform
TERRAFORM_LOCK_TIMEOUT ?= 60s
VM_NAME ?= parch-cloud
SSH_KEY ?= $(HOME)/.ssh/id_ed25519
TERRAFORM_VARS = -var="libvirt_uri=$(LIBVIRT_URI)" -var="libvirt_pool=$(LIBVIRT_POOL)" -var="libvirt_network=$(LIBVIRT_NETWORK)" \
-var="vm_name=$(VM_NAME)" -var="ssh_public_key_path=$(SSH_KEY).pub"
.PHONY: help check-deps install-deps install-qemu-deps build build-cloud list verify info clean clean-output clean-tmp \
qemu-check qemu-init qemu-plan qemu-up qemu-down qemu-ip qemu-ssh
help:
@awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z0-9_-]+:.*##/ {printf " %-16s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
check-deps:
@missing=0; \
for command in pacstrap arch-chroot pacman gpgconf losetup mount mountpoint umount \
truncate sgdisk mkfs.fat mkfs.btrfs udevadm blockdev fstrim qemu-img sha256sum; do \
if ! command -v "$$command" >/dev/null 2>&1; then \
echo "Missing command: $$command"; \
missing=1; \
fi; \
done; \
if (( missing )); then \
echo "Install the build dependencies with: make install-deps"; \
exit 1; \
fi; \
echo "Build commands are available."
install-deps:
@sudo pacman -S --needed --noconfirm arch-install-scripts btrfs-progs dosfstools gptfdisk gnupg qemu-img systemd util-linux
install-qemu-deps:
@sudo pacman -S --needed --noconfirm qemu-full libvirt terraform
build: check-deps
@if [[ $$(id -u) -eq 0 ]]; then \
cd "$(ROOT_DIR)" && ./build.sh "$(VERSION)"; \
else \
cd "$(ROOT_DIR)" && sudo ./build.sh "$(VERSION)"; \
fi
build-cloud: check-deps
@if [[ $$(id -u) -eq 0 ]]; then \
cd "$(ROOT_DIR)" && ./build.sh "$(VERSION)" cloud-image; \
else \
cd "$(ROOT_DIR)" && sudo ./build.sh "$(VERSION)" cloud-image; \
fi
list:
@if [[ -d "$(OUTPUT_DIR)" ]] && compgen -G "$(OUTPUT_DIR)/*.qcow2" >/dev/null; then \
find "$(OUTPUT_DIR)" -maxdepth 1 -type f -name '*.qcow2' -printf '%f\t%s bytes\n'; \
else \
echo "No QCOW2 images found. Run 'make build' first."; \
fi
verify:
@if [[ ! -d "$(OUTPUT_DIR)" ]] || ! compgen -G "$(OUTPUT_DIR)/*.SHA256" >/dev/null; then \
echo "No checksum files found in $(OUTPUT_DIR)."; \
exit 1; \
fi; \
cd "$(OUTPUT_DIR)"; \
for checksum in *.SHA256; do sha256sum --check "$$checksum"; done
info: check-deps
@if [[ ! -d "$(OUTPUT_DIR)" ]] || ! compgen -G "$(OUTPUT_DIR)/*.qcow2" >/dev/null; then \
echo "No QCOW2 images found in $(OUTPUT_DIR)."; \
exit 1; \
fi; \
for image in "$(OUTPUT_DIR)"/*.qcow2; do qemu-img info "$$image"; done
clean-output:
@rm -rf -- "$(OUTPUT_DIR)"
@echo "Removed $(OUTPUT_DIR)."
clean-tmp:
@rm -rf -- "$(TMP_DIR)"
@echo "Removed $(TMP_DIR)."
clean: clean-output clean-tmp
qemu-check:
@command -v terraform >/dev/null || { echo "Missing command: terraform"; exit 1; }
@command -v virsh >/dev/null || { echo "Missing command: virsh (install libvirt)"; exit 1; }
@virsh -c "$(LIBVIRT_URI)" pool-info "$(LIBVIRT_POOL)" >/dev/null || { echo "Cannot access libvirt pool '$(LIBVIRT_POOL)' through $(LIBVIRT_URI)."; exit 1; }
@virsh -c "$(LIBVIRT_URI)" net-info "$(LIBVIRT_NETWORK)" >/dev/null || { echo "Libvirt network '$(LIBVIRT_NETWORK)' is unavailable."; exit 1; }
qemu-init:
@terraform -chdir="$(TERRAFORM_DIR)" init -input=false
qemu-plan: qemu-check
@terraform -chdir="$(TERRAFORM_DIR)" plan -input=false -lock-timeout=$(TERRAFORM_LOCK_TIMEOUT) $(TERRAFORM_VARS)
qemu-up: qemu-check
@if ! compgen -G "$(OUTPUT_DIR)/Parch-Linux-x86_64-cloudimg-*.qcow2" >/dev/null; then $(MAKE) build-cloud; fi
@terraform -chdir="$(TERRAFORM_DIR)" init -input=false
@terraform -chdir="$(TERRAFORM_DIR)" apply -auto-approve -input=false -lock-timeout=$(TERRAFORM_LOCK_TIMEOUT) $(TERRAFORM_VARS)
qemu-down: qemu-check
@terraform -chdir="$(TERRAFORM_DIR)" destroy -auto-approve -input=false -lock-timeout=$(TERRAFORM_LOCK_TIMEOUT) $(TERRAFORM_VARS)
qemu-ip: qemu-check
@leases="$$(virsh -c "$(LIBVIRT_URI)" net-dhcp-leases "$(LIBVIRT_NETWORK)")"; \
if printf '%s\n' "$$leases" | grep -Eq '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'; then \
printf '%s\n' "$$leases"; \
else \
printf '%s\n' "No DHCP lease yet. Start the VM with 'make qemu-up', then run 'make qemu-ip' again."; \
fi
qemu-ssh: qemu-check
@ip="$$(virsh -c "$(LIBVIRT_URI)" domifaddr "$(VM_NAME)" --source lease | awk '/ipv4/ {sub(/\/.*/, "", $$4); print $$4; exit}')"; \
if [[ -z "$$ip" ]]; then echo "No IP for $(VM_NAME) yet. Run 'make qemu-up', wait for boot, then retry."; exit 1; fi; \
exec ssh -o IdentitiesOnly=yes -i "$(SSH_KEY)" arch@"$$ip"
+76 -2
View File
@@ -1,3 +1,77 @@
# parchlinux-cloud # Parch Linux Cloud Images
Parch Linux Cloud & QCOW2 appliance images Build and run Parch Linux virtual machine images for x86_64 QEMU/KVM.
## Images
| Image | Description |
| --- | --- |
| Cloud | Cloud-init enabled image for automated setup. |
| Basic | Image for interactive use. |
| Libvirt executor | Image with GitLab Runner and libvirt tools. |
Generated images and SHA-256 checksums are saved in `output/`
## Requirements
- Arch Linux x86_64 to build images
- QEMU/KVM and libvirt to run virtual machines
- Terraform to provision a VM with the included configuration
Install the build dependencies with:
```sh
make install-deps
```
## Build
Build all image variants:
```sh
make build
```
Build only the cloud image:
```sh
make build-cloud
```
Set a version explicitly if needed:
```sh
make build-cloud VERSION=20261018.1
```
## Run
Create an SSH key if needed, then build and start the cloud VM:
```sh
ssh-keygen -t ed25519
make build-cloud
make qemu-up
```
Connect to the VM:
```sh
make qemu-ip
make qemu-ssh
```
The VM is configured with Terraform in `terraform/`. Adjust its settings there as needed.
## Useful commands
```sh
make help # list available commands
make qemu-plan # preview VM changes
make qemu-down # remove the VM
make clean # remove generated files
```
## Contributors
- Core Developer: Amir Husayn Panahifar <ahp@panahifar.ir>
+1
View File
@@ -0,0 +1 @@
Parchlinux \r (\l)
+1
View File
@@ -0,0 +1 @@
LANG=C.UTF-8
+3
View File
@@ -0,0 +1,3 @@
DISTRIB_ID="Parch"
DISTRIB_RELEASE="rolling"
DISTRIB_DESCRIPTION="Parch Linux"
+12
View File
@@ -0,0 +1,12 @@
NAME="Parch Linux"
PRETTY_NAME="Parch Linux"
ID=parch
BUILD_ID=rolling
HOME_URL="https://parchlinux.com"
DOCUMENTATION_URL="https://wiki.parchlinux.com/en/"
SUPPORT_URL="https://forum.parchlinux.com"
BUG_REPORT_URL="https://forum.parchlinux.com"
LOGO=parch-logo
VARIANT="Virtual Machine"
VARIANT_ID=vm
ANSI_COLOR="0;35"
+22
View File
@@ -0,0 +1,22 @@
[options]
HoldPkg = pacman glibc
Architecture = auto
Color
CheckSpace
ParallelDownloads = 5
SigLevel = Required DatabaseOptional
LocalFileSigLevel = Optional
[world]
SigLevel = Never
Include = /etc/pacman.d/parch-mirrors
[core]
Include = /etc/pacman.d/mirrorlist
[extra]
Include = /etc/pacman.d/mirrorlist
#[multilib]
#Include = /etc/pacman.d/mirrorlist
@@ -0,0 +1,12 @@
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = parch-branding
[Action]
Description = Applying Parch Linux virtual machine branding...
When = PostTransaction
Depends = parch-branding
Depends = coreutils
Exec = /bin/sh -c "install -m644 /usr/local/share/parch-branding/os-release /usr/lib/os-release && install -m644 /usr/local/share/parch-branding/issue /etc/issue && install -m644 /usr/local/share/parch-branding/lsb-release /etc/lsb-release && install -m644 /usr/local/share/parch-branding/pixmaps/parch-logo.svg /usr/share/pixmaps/parch-logo.svg && install -m644 /usr/local/share/parch-branding/pixmaps/parch-logo-dark.svg /usr/share/pixmaps/parch-logo-dark.svg"
+1
View File
@@ -0,0 +1 @@
Server = https://github.com/parchlinux/world/raw/refs/heads/main/x86_64/
+12
View File
@@ -0,0 +1,12 @@
NAME="Parch Linux"
PRETTY_NAME="Parch Linux"
ID=parch
BUILD_ID=rolling
HOME_URL="https://parchlinux.com"
DOCUMENTATION_URL="https://wiki.parchlinux.com/en/"
SUPPORT_URL="https://forum.parchlinux.com"
BUG_REPORT_URL="https://forum.parchlinux.com"
LOGO=parch-logo
VARIANT="Virtual Machine"
VARIANT_ID=vm
ANSI_COLOR="0;35"
@@ -0,0 +1,7 @@
<svg width="438" height="438" viewBox="0 0 438 438" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M338.619 271.955L264.458 281.519L135.949 355.713C146.568 370.223 159.065 383.307 173.298 394.467C189.967 406.346 210.214 415.006 234.278 415.908C302.603 418.502 340.706 366.003 342.443 307.222C342.802 295.085 341.344 283.399 338.619 271.955Z" fill="#8500B2"/>
<path d="M149.525 281.519C137.049 295.076 124.523 311.185 118.189 326.753C123.323 336.903 129.271 346.586 135.951 355.714L264.46 281.519H149.525ZM140.935 362.214C141.689 363.161 142.451 364.1 143.222 365.034C142.451 364.1 141.688 363.161 140.935 362.214ZM148.736 371.466C149.286 372.078 149.857 372.669 150.415 373.274C149.858 372.669 149.285 372.078 148.736 371.466ZM155.661 378.749C156.233 379.322 156.795 379.903 157.374 380.47C156.796 379.903 156.232 379.323 155.661 378.749ZM164.138 386.78C164.681 387.263 165.231 387.737 165.779 388.214C165.231 387.737 164.68 387.263 164.138 386.78Z" fill="#4B0080"/>
<path d="M286.55 167.911L184.511 248.473C184.511 248.473 167.063 262.454 149.517 281.519H264.452L265.25 281.058L327.203 239.084C316.694 215.579 301.908 192.409 286.55 167.911ZM149.517 281.519C145.177 286.235 140.861 291.248 136.772 296.457C140.857 291.26 145.182 286.229 149.517 281.519ZM135.688 297.82C134.838 298.923 134.027 300.039 133.203 301.156C134.027 300.041 134.838 298.921 135.688 297.82ZM130.9 304.328C130.207 305.312 129.543 306.297 128.879 307.287C129.543 306.298 130.208 305.31 130.9 304.328ZM125.569 312.385C125.44 312.596 125.318 312.807 125.191 313.018C125.318 312.807 125.44 312.596 125.569 312.385Z" fill="#A300B2"/>
<path d="M128.924 112.324C108.513 144.226 96.6191 181.816 95.1426 220.98C94.2364 246.129 97.668 270.818 104.892 294.001L184.515 248.473L272.795 178.774L185.535 137.248L128.924 112.324ZM307.241 201.429C307.66 202.136 308.094 202.847 308.509 203.552C308.094 202.847 307.66 202.135 307.241 201.429ZM314.52 214.058C315.085 215.072 315.627 216.085 316.181 217.098C315.628 216.086 315.085 215.071 314.52 214.058ZM320.433 225.116C320.98 226.177 321.53 227.24 322.062 228.302C321.53 227.24 320.979 226.177 320.433 225.116Z" fill="#7600B2"/>
<path d="M285.873 22.0212C238.195 22.7546 192.238 42.4028 157.025 77.3641C152.519 81.8382 148.247 86.5302 144.189 91.3983L185.537 137.248L272.797 178.774L286.556 167.911C281.07 159.161 275.527 150.259 270.058 141.087C246.092 100.887 258.906 36.8152 304.196 22.683C301.278 22.4267 298.355 22.2417 295.429 22.1281C292.238 22.0078 289.051 21.9724 285.873 22.0212ZM299.967 189.414C300.632 190.494 301.297 191.573 301.954 192.649C301.3 191.578 300.629 190.489 299.967 189.414Z" fill="#B000CC"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,7 @@
<svg width="438" height="438" viewBox="0 0 438 438" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M339.114 272.077L264.782 281.663L135.977 356.028C146.62 370.571 159.146 383.685 173.412 394.871C190.119 406.777 210.413 415.457 234.533 416.361C303.015 418.961 341.206 366.341 342.947 307.425C343.306 295.26 341.845 283.547 339.114 272.077Z" fill="#00FF80"/>
<path d="M149.582 281.663C137.077 295.251 124.522 311.397 118.174 327.001C123.319 337.175 129.281 346.879 135.976 356.028L264.782 281.663H149.582ZM140.971 362.543C141.727 363.493 142.491 364.434 143.264 365.37C142.492 364.434 141.727 363.493 140.971 362.543ZM148.79 371.817C149.342 372.43 149.915 373.023 150.474 373.629C149.915 373.023 149.341 372.43 148.79 371.817ZM155.732 379.116C156.305 379.691 156.869 380.274 157.449 380.842C156.869 380.273 156.304 379.692 155.732 379.116ZM164.228 387.166C164.772 387.65 165.324 388.125 165.873 388.603C165.324 388.125 164.772 387.65 164.228 387.166Z" fill="#0080FF"/>
<path d="M286.931 167.793L184.657 248.541C184.657 248.541 167.168 262.554 149.583 281.663H264.782L265.582 281.201L327.679 239.13C317.145 215.571 302.325 192.348 286.931 167.793ZM149.583 281.663C145.233 286.389 140.907 291.414 136.808 296.636C140.902 291.427 145.238 286.384 149.583 281.663ZM135.722 298.002C134.869 299.107 134.056 300.225 133.231 301.345C134.056 300.228 134.869 299.105 135.722 298.002ZM130.922 304.524C130.228 305.51 129.562 306.498 128.896 307.491C129.562 306.499 130.228 305.508 130.922 304.524ZM125.579 312.6C125.45 312.812 125.328 313.023 125.2 313.235C125.328 313.023 125.45 312.811 125.579 312.6Z" fill="#00EAFF"/>
<path d="M128.938 112.079C108.479 144.053 96.5584 181.73 95.0785 220.985C94.1701 246.191 97.6097 270.937 104.85 294.174L184.657 248.541L273.141 178.681L185.68 137.06L128.938 112.079ZM307.666 201.389C308.086 202.097 308.521 202.81 308.938 203.517C308.522 202.81 308.086 202.097 307.666 201.389ZM314.962 214.046C315.529 215.063 316.072 216.078 316.627 217.094C316.073 216.079 315.528 215.062 314.962 214.046ZM320.889 225.13C321.437 226.194 321.988 227.258 322.521 228.323C321.989 227.259 321.436 226.193 320.889 225.13Z" fill="#00A9FF"/>
<path d="M286.247 21.5678C238.459 22.3028 192.396 41.9963 157.102 77.0381C152.585 81.5225 148.303 86.2253 144.236 91.1046L185.68 137.06L273.14 178.681L286.931 167.793C281.433 159.023 275.877 150.101 270.396 140.908C246.374 100.615 259.218 36.3958 304.612 22.2311C301.688 21.9741 298.758 21.7888 295.825 21.6749C292.626 21.5543 289.432 21.5188 286.247 21.5678ZM300.374 189.346C301.04 190.428 301.706 191.51 302.365 192.589C301.709 191.515 301.037 190.423 300.374 189.346Z" fill="#00FF95"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Executable
+191
View File
@@ -0,0 +1,191 @@
#!/bin/bash
set -o nounset -o errexit
readonly DEFAULT_DISK_SIZE="2G"
readonly IMAGE="image.img"
readonly MIRROR='https://fastly.mirror.pkgbuild.com/$repo/os/$arch'
function init() {
readonly ORIG_PWD="${PWD}"
readonly OUTPUT="${PWD}/output"
mkdir -p "${OUTPUT}" "${PWD}/tmp"
local tmpdir
tmpdir="$(mktemp --directory --tmpdir="${PWD}/tmp")"
readonly TMPDIR="${tmpdir}"
if [ -n "${SUDO_UID:-}" ] && [[ -n "${SUDO_GID:-}" ]]; then
chown "${SUDO_UID}:${SUDO_GID}" "${OUTPUT}" "${TMPDIR}"
fi
cd "${TMPDIR}"
readonly MOUNT="${PWD}/mount"
mkdir "${MOUNT}"
}
function cleanup() {
set +o errexit
if [ -n "${MOUNT:-}" ] && mountpoint -q "${MOUNT}"; then
umount --recursive "${MOUNT}" || exit 1
fi
if [ -n "${LOOPDEV:-}" ]; then
losetup -d "${LOOPDEV}"
fi
if [ -n "${TMPDIR:-}" ]; then
rm -rf "${TMPDIR}"
fi
}
trap cleanup EXIT
function setup_disk() {
truncate -s "${DEFAULT_DISK_SIZE}" "${IMAGE}"
sgdisk --align-end \
--clear \
--new 0:0:+1M --typecode=0:ef02 --change-name=0:'BIOS boot partition' \
--new 0:0:+300M --typecode=0:ef00 --change-name=0:'EFI system partition' \
--new 0:0:0 --typecode=0:8304 --change-name=0:'Parch Linux root' \
"${IMAGE}"
LOOPDEV=$(losetup --find --partscan --show "${IMAGE}")
wait_until_settled "${LOOPDEV}"
mkfs.fat -F 32 -S 4096 "${LOOPDEV}p2"
mkfs.btrfs "${LOOPDEV}p3"
mount -o compress=zstd:1 "${LOOPDEV}p3" "${MOUNT}"
mount --mkdir "${LOOPDEV}p2" "${MOUNT}/efi"
}
function bootstrap() {
cat <<EOF >pacman.conf
[options]
Architecture = auto
SigLevel = DatabaseOptional
[world]
SigLevel = Never
Server = https://github.com/parchlinux/world/raw/refs/heads/main/\$arch/
[core]
Include = mirrorlist
[extra]
Include = mirrorlist
EOF
echo "Server = ${MIRROR}" >mirrorlist
pacstrap -c -C pacman.conf -M "${MOUNT}" base linux grub openssh sudo btrfs-progs dosfstools efibootmgr qemu-guest-agent parchlinux-keyring parch-branding lsb-release
gpgconf --homedir "${MOUNT}/etc/pacman.d/gnupg" --kill gpg-agent
cp -a --no-preserve=ownership "${ORIG_PWD}/branding/." "${MOUNT}/"
install -Dm 644 "${ORIG_PWD}/branding/usr/lib/os-release_parch" "${MOUNT}/usr/local/share/parch-branding/os-release"
install -Dm 644 "${ORIG_PWD}/branding/etc/issue_parch" "${MOUNT}/usr/local/share/parch-branding/issue"
install -Dm 644 "${ORIG_PWD}/branding/etc/lsb-release_parch" "${MOUNT}/usr/local/share/parch-branding/lsb-release"
install -Dm 644 "${ORIG_PWD}/branding/usr/share/pixmaps/parch-logo.svg" "${MOUNT}/usr/local/share/parch-branding/pixmaps/parch-logo.svg"
install -Dm 644 "${ORIG_PWD}/branding/usr/share/pixmaps/parch-logo-dark.svg" "${MOUNT}/usr/local/share/parch-branding/pixmaps/parch-logo-dark.svg"
}
function image_cleanup() {
rm -rf "${MOUNT}/etc/pacman.d/gnupg/"
printf '\nMODULES=(virtio_pci virtio_blk virtio_scsi btrfs sr_mod)\n' >>"${MOUNT}/etc/mkinitcpio.conf"
arch-chroot "${MOUNT}" /usr/bin/mkinitcpio -P
sync -f "${MOUNT}/etc/os-release"
fstrim --verbose "${MOUNT}"
fstrim --verbose "${MOUNT}/efi"
}
function wait_until_settled() {
udevadm settle
blockdev --flushbufs --rereadpt "${1}"
until test -e "${1}p3"; do
echo "${1}p3 doesn't exist yet..."
sleep 1
done
}
function mount_image() {
LOOPDEV=$(losetup --find --partscan --show "${1:-${IMAGE}}")
wait_until_settled "${LOOPDEV}"
mount -o compress=zstd:1 "${LOOPDEV}p3" "${MOUNT}"
mount --bind "/var/cache/pacman/pkg" "${MOUNT}/var/cache/pacman/pkg"
}
function unmount_image() {
umount --recursive "${MOUNT}"
losetup -d "${LOOPDEV}"
LOOPDEV=""
}
function mv_to_output() {
sha256sum "${1}" >"${1}.SHA256"
if [ -n "${SUDO_UID:-}" ]; then
chown "${SUDO_UID}:${SUDO_GID}" "${1}"{,.SHA256}
fi
mv "${1}"{,.SHA256} "${OUTPUT}/"
}
function create_image() {
local tmp_image
tmp_image="$(basename "$(mktemp -u)")"
cp -a "${IMAGE}" "${tmp_image}"
if [ -n "${DISK_SIZE}" ]; then
truncate -s "${DISK_SIZE}" "${tmp_image}"
sgdisk --align-end --delete 3 "${tmp_image}"
sgdisk --align-end --move-second-header \
--new 0:0:0 --typecode=0:8304 --change-name=0:'Parch Linux root' \
"${tmp_image}"
fi
mount_image "${tmp_image}"
if [ -n "${DISK_SIZE}" ]; then
btrfs filesystem resize max "${MOUNT}"
fi
if [ 0 -lt "${#PACKAGES[@]}" ]; then
arch-chroot "${MOUNT}" /usr/bin/pacman -S --noconfirm "${PACKAGES[@]}"
fi
if [ 0 -lt "${#SERVICES[@]}" ]; then
arch-chroot "${MOUNT}" /usr/bin/systemctl enable "${SERVICES[@]}"
fi
"${2}"
image_cleanup
unmount_image
"${3}" "${tmp_image}" "${1}"
mv_to_output "${1}"
}
function main() {
if [ "$(id -u)" -ne 0 ]; then
echo "root is required"
exit 1
fi
init
if [ -n "${2:-}" ] && [ ! -f "${ORIG_PWD}/images/${2}.sh" ]; then
echo "Unknown image variant '${2}'. Available variants are: $(find "${ORIG_PWD}/images" -maxdepth 1 -type f -name '*.sh' ! -name base.sh -printf '%f\n' | sed 's/\.sh$//' | tr '\n' ' ')"
exit 1
fi
setup_disk
bootstrap
source "${ORIG_PWD}/images/base.sh"
pre
unmount_image
local build_version
if [ -z "${1:-}" ]; then
build_version="$(date +%Y%m%d).0"
echo "WARNING: BUILD_VERSION wasn't set!"
echo "Falling back to $build_version"
else
build_version="${1}"
fi
for image in "${ORIG_PWD}/images/"*.sh; do
if [ "$(basename "${image}")" = "base.sh" ]; then
continue
fi
if [ -n "${2:-}" ] && [ "$(basename "${image}" .sh)" != "${2}" ]; then
continue
fi
source "${image}"
create_image "${IMAGE_NAME}" pre post
done
}
main "$@"
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash
function pre() {
rm "${MOUNT}/etc/machine-id"
arch-chroot "${MOUNT}" /usr/bin/btrfs subvolume create /swap
chattr +C "${MOUNT}/swap"
chmod 0700 "${MOUNT}/swap"
arch-chroot "${MOUNT}" /usr/bin/btrfs filesystem mkswapfile --size 512m --uuid clear /swap/swapfile
echo -e "/swap/swapfile none swap defaults 0 0" >>"${MOUNT}/etc/fstab"
arch-chroot "${MOUNT}" /usr/bin/systemd-firstboot --locale=C.UTF-8 --timezone=UTC --hostname=parch --keymap=us
ln -sf /run/systemd/resolve/stub-resolv.conf "${MOUNT}/etc/resolv.conf"
install -m 644 "${MOUNT}/usr/local/share/parch-branding/os-release" "${MOUNT}/usr/lib/os-release"
install -m 644 "${MOUNT}/usr/local/share/parch-branding/issue" "${MOUNT}/etc/issue"
install -m 644 "${MOUNT}/usr/local/share/parch-branding/lsb-release" "${MOUNT}/etc/lsb-release"
install -m 644 "${MOUNT}/usr/local/share/parch-branding/pixmaps/parch-logo.svg" "${MOUNT}/usr/share/pixmaps/parch-logo.svg"
install -m 644 "${MOUNT}/usr/local/share/parch-branding/pixmaps/parch-logo-dark.svg" "${MOUNT}/usr/share/pixmaps/parch-logo-dark.svg"
cat <<EOF >"${MOUNT}/etc/systemd/system/pacman-init.service"
[Unit]
Description=Initializes Pacman keyring
Before=sshd.service cloud-final.service archlinux-keyring-wkd-sync.service
After=time-sync.target
ConditionFirstBoot=yes
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/pacman-key --init
ExecStart=/usr/bin/pacman-key --populate archlinux
ExecStart=-/usr/bin/pacman-key --populate parchlinux
ExecStart=/usr/bin/gpg --homedir /etc/pacman.d/gnupg --batch --import-ownertrust /usr/share/pacman/keyrings/parchlinux-trusted
[Install]
WantedBy=multi-user.target
EOF
cat <<'EOF' >"${MOUNT}/etc/pacman.d/mirrorlist"
Server = https://fastly.mirror.pkgbuild.com/$repo/os/$arch
Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch
EOF
arch-chroot "${MOUNT}" /bin/bash -e <<EOF
source /etc/profile
systemctl enable sshd
systemctl enable systemd-networkd
systemctl enable systemd-resolved
systemctl enable systemd-timesyncd
systemctl enable systemd-time-wait-sync
systemctl enable pacman-init.service
EOF
arch-chroot "${MOUNT}" /usr/bin/grub-install --target=i386-pc "${LOOPDEV}"
arch-chroot "${MOUNT}" /usr/bin/grub-install --target=x86_64-efi --efi-directory=/efi --removable
sed -i 's/^GRUB_TIMEOUT=.*$/GRUB_TIMEOUT=1/' "${MOUNT}/etc/default/grub"
sed -i 's/^GRUB_CMDLINE_LINUX=.*$/GRUB_CMDLINE_LINUX="net.ifnames=0"/' "${MOUNT}/etc/default/grub"
sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT=\"rootflags=compress=zstd:1\"/' "${MOUNT}/etc/default/grub"
arch-chroot "${MOUNT}" /usr/bin/grub-mkconfig -o /boot/grub/grub.cfg
}
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
IMAGE_NAME="Parch-Linux-x86_64-basic-${build_version}.qcow2"
DISK_SIZE="40G"
PACKAGES=()
SERVICES=()
function pre() {
local NEWUSER="arch"
arch-chroot "${MOUNT}" /usr/bin/useradd -m -U "${NEWUSER}"
echo -e "${NEWUSER}\n${NEWUSER}" | arch-chroot "${MOUNT}" /usr/bin/passwd "${NEWUSER}"
echo "${NEWUSER} ALL=(ALL) NOPASSWD: ALL" >"${MOUNT}/etc/sudoers.d/${NEWUSER}"
cat <<EOF >"${MOUNT}/etc/systemd/network/80-dhcp.network"
[Match]
Name=en*
Name=eth*
[Link]
RequiredForOnline=routable
[Network]
DHCP=yes
EOF
}
function post() {
qemu-img convert -c -f raw -O qcow2 "${1}" "${2}"
rm "${1}"
}
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
IMAGE_NAME="Parch-Linux-x86_64-cloudimg-${build_version}.qcow2"
DISK_SIZE=""
PACKAGES=(cloud-init cloud-guest-utils gptfdisk)
SERVICES=(cloud-init-main.service cloud-init-local.service cloud-init-network.service cloud-config.service cloud-final.service qemu-guest-agent.service)
function pre() {
cat <<'EOF' >"${MOUNT}/etc/systemd/network/80-dhcp.network"
[Match]
Name=en* eth*
[Network]
DHCP=yes
EOF
sed -Ei 's/^(GRUB_CMDLINE_LINUX_DEFAULT=.*)"$/\1 console=tty0 console=ttyS0,115200"/' "${MOUNT}/etc/default/grub"
echo 'GRUB_TERMINAL="serial console"' >>"${MOUNT}/etc/default/grub"
echo 'GRUB_SERIAL_COMMAND="serial --speed=115200"' >>"${MOUNT}/etc/default/grub"
arch-chroot "${MOUNT}" /usr/bin/grub-mkconfig -o /boot/grub/grub.cfg
}
function post() {
qemu-img convert -c -f raw -O qcow2 "${1}" "${2}"
rm "${1}"
}
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
IMAGE_NAME="Parch-Linux-x86_64-libvirt-executor-${build_version}.qcow2"
DISK_SIZE="40G"
PACKAGES=(git git-lfs gitlab-runner)
SERVICES=()
function pre() {
arch-chroot "${MOUNT}" /usr/bin/systemctl disable systemd-time-wait-sync
sed -E 's/^#(IgnorePkg *=)/\1 linux/' -i "${MOUNT}/etc/pacman.conf"
sed 's/^\(GRUB_CMDLINE_LINUX=".*\)"$/\1 lockdown=confidentiality"/' -i "${MOUNT}/etc/default/grub"
arch-chroot "${MOUNT}" /usr/bin/grub-mkconfig -o /boot/grub/grub.cfg
rm -f "${MOUNT}/etc/hostname"
cat <<EOF >"${MOUNT}/etc/systemd/network/80-dhcp.network"
[Match]
Name=en*
Name=eth*
[Network]
DHCP=yes
EOF
}
function post() {
qemu-img convert -c -f raw -O qcow2 "${1}" "${2}"
rm "${1}"
}
+23
View File
@@ -0,0 +1,23 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/dmacvicar/libvirt" {
version = "0.9.9"
constraints = "~> 0.9.9"
hashes = [
"h1:ljmMoTuHWXJGf28PY5E0fX9BDQWNZfs8zX+xY80YBok=",
"zh:130c9df49bf2f69deb2af1ebbd25deb09b4ebda1022fcda980c197ffda800c1d",
"zh:38216229290a3450d53a8a2799175fd8de5c26581913e20c8848b75890343ea3",
"zh:45ad900f9c1866e24643b03ec4b0b716e012bd25d0c92bb4d0d47ca7531abec8",
"zh:6e77ca4b5fad91c30b7123fd1b40024dabf188e33509ed219904f7ff84c8fb1b",
"zh:70a9d5af99a60179c7d38032662f443c3d2ad8bddab11d535c545c5c44273b2d",
"zh:7de6b5272a89710f2490d3e7fdf7c495f7acaddfb127df8ebd86ef9a70508e3a",
"zh:8932f2eb1013b84f01008d59bb8a16b982ac9ac72f7de41a0f88b7fe41c352df",
"zh:8b6044884696c671610dfe752bec5507131f21757e848872d8624cfba7e9f6d6",
"zh:d891ec2c1a0ae55d44b158c7e629316caf02ab8a5c4018523865fdc2ae3299a3",
"zh:dd9bc604e2ac389da3bae463a3da2a1e06915b7952892c274ea0b54f2eb41300",
"zh:e18ca294c7ca2d35aa866d37c64d01f62bda2b2c261d8280dba0ab272517e47f",
"zh:ef96bb292d6df57c4975f9d94c09a2efd7d6aed470a0f23f1c56b69dc1ec0b14",
"zh:f7337de4d59dc06d578ab3393506fedfdc962b01cc24c78a3ff46b1311e5fdb7",
]
}
+13
View File
@@ -0,0 +1,13 @@
#cloud-config
hostname: ${hostname}
manage_etc_hosts: true
disable_root: true
ssh_pwauth: false
users:
- name: arch
groups: [wheel]
sudo: ["ALL=(ALL) NOPASSWD: ALL"]
shell: /bin/bash
lock_passwd: true
ssh_authorized_keys:
- ${ssh_public_key}
+129
View File
@@ -0,0 +1,129 @@
locals {
output_dir = abspath("${path.module}/../output")
cloud_images = try(sort(fileset(local.output_dir, "Parch-Linux-x86_64-cloudimg-*.qcow2")), [])
selected_image = var.image_path != "" ? pathexpand(var.image_path) : (
length(local.cloud_images) > 0 ? "${local.output_dir}/${local.cloud_images[length(local.cloud_images) - 1]}" : ""
)
ssh_public_key = trimspace(file(pathexpand(var.ssh_public_key_path)))
}
resource "libvirt_volume" "root" {
name = "${var.vm_name}.qcow2"
pool = var.libvirt_pool
target = { format = { type = "qcow2" } }
create = {
content = {
url = local.selected_image
}
}
lifecycle {
precondition {
condition = local.selected_image != "" && fileexists(local.selected_image)
error_message = "No Parch cloudimg QCOW2 found. Run 'make build' first or set -var image_path=/absolute/path/to/cloudimg.qcow2."
}
}
}
resource "libvirt_cloudinit_disk" "seed" {
name = "${var.vm_name}-cloudinit"
user_data = templatefile("${path.module}/cloud-init/user-data.yaml.tftpl", {
hostname = var.vm_name
ssh_public_key = jsonencode(local.ssh_public_key)
})
meta_data = yamlencode({
instance-id = var.vm_name
local-hostname = var.vm_name
})
}
resource "libvirt_volume" "cloudinit" {
name = "${var.vm_name}-cloudinit.iso"
pool = var.libvirt_pool
target = { format = { type = "iso" } }
create = {
content = {
url = libvirt_cloudinit_disk.seed.path
}
}
}
resource "libvirt_domain" "vm" {
name = var.vm_name
type = "kvm"
memory = var.memory_mib
memory_unit = "MiB"
vcpu = var.vcpu
features = {
acpi = true
apic = {}
}
cpu = {
mode = "host-passthrough"
}
os = {
type = "hvm"
type_arch = "x86_64"
type_machine = "q35"
boot_devices = [{ dev = "hd" }]
}
devices = {
disks = [
{
driver = { name = "qemu", type = "qcow2" }
source = { volume = { pool = libvirt_volume.root.pool, volume = libvirt_volume.root.name } }
target = { dev = "vda", bus = "virtio" }
},
{
device = "cdrom"
source = { volume = { pool = libvirt_volume.cloudinit.pool, volume = libvirt_volume.cloudinit.name } }
target = { dev = "sda", bus = "sata" }
}
]
interfaces = [
{
type = "network"
model = { type = "virtio" }
source = { network = { network = var.libvirt_network } }
}
]
serials = [{
type = "pty"
target = { type = "isa-serial", port = 0 }
}]
consoles = [{
type = "pty"
target = { type = "serial", port = 0 }
}]
channels = [{
source = { unix = {} }
target = { virt_io = { name = "org.qemu.guest_agent.0" } }
}]
rngs = [{
model = "virtio"
backend = { random = "/dev/urandom" }
}]
}
running = true
lifecycle {
replace_triggered_by = [libvirt_volume.root, libvirt_volume.cloudinit]
}
}
output "vm_name" {
value = libvirt_domain.vm.name
}
output "ssh_command" {
value = "make qemu-ssh (or: ssh -o IdentitiesOnly=yes -i ${trimsuffix(pathexpand(var.ssh_public_key_path), ".pub")} arch@<VM-IP>)"
}
+47
View File
@@ -0,0 +1,47 @@
variable "libvirt_uri" {
description = "Libvirt connection URI. Use qemu:///system for the system daemon."
type = string
default = "qemu:///system"
}
variable "libvirt_pool" {
description = "Existing libvirt storage pool for VM disks and cloud-init seed."
type = string
default = "default"
}
variable "libvirt_network" {
description = "Existing libvirt network that provides DHCP."
type = string
default = "default"
}
variable "vm_name" {
description = "Name for the Parch Linux VM."
type = string
default = "parch-cloud"
}
variable "image_path" {
description = "Optional path to a cloudimg QCOW2. Empty selects the newest output/Parch-Linux-x86_64-cloudimg-*.qcow2."
type = string
default = ""
}
variable "ssh_public_key_path" {
description = "Path to the public SSH key injected by cloud-init."
type = string
default = "~/.ssh/id_ed25519.pub"
}
variable "memory_mib" {
description = "VM memory in MiB."
type = number
default = 2048
}
variable "vcpu" {
description = "Number of virtual CPUs."
type = number
default = 2
}
+14
View File
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.5.0"
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "~> 0.9.9"
}
}
}
provider "libvirt" {
uri = var.libvirt_uri
}