Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
uses: actions/cache@v4
with:
path: ${{ env.PACMAN_CACHE }}
key: archlinux-pacman-${{ hashFiles('packages.x86_64', 'bootstrap_packages.x86_64') }}
key: archlinux-pacman-${{ hashFiles('pacman.conf', 'packages.x86_64', 'bootstrap_packages.x86_64') }}
restore-keys: |
archlinux-pacman-

Expand All @@ -56,9 +56,9 @@ jobs:

- name: Build ISO
run: |
# Run the ISO build with privileged mode to allow loop device mounting
# Run the ISO build with required capabilities instead of full privileged mode
# The 'build' command is passed to the entrypoint script
docker run --rm --privileged \
docker run --rm --cap-add=SYS_ADMIN --cap-add=MKNOD \
-v ${{ github.workspace }}:${{ env.WORKSPACE }} \
-v ${{ env.PACMAN_CACHE }}:/var/cache/pacman/pkg \
arch-iso-builder build out work || {
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/gui-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ jobs:
run: sudo docker exec arch-iso-gui-test test -f /workdir/out/Arch.iso

- name: Test ISO download endpoint
run: curl -f -I http://localhost:8080/download | grep -q 'Content-Disposition: attachment; filename=Arch.iso'
run: |
curl -f -I http://localhost:8080/download | grep -q 'Content-Disposition: attachment; filename=Arch.iso'

- name: Stop the container
if: always()
Expand Down
2 changes: 1 addition & 1 deletion airootfs/etc/systemd/system/no-beep.service
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Conflicts=shutdown.target
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/bash -c "rmmod pcspkr snd_pcsp 2>/dev/null || true"
ExecStart=/bin/bash -c "if [ -f /sys/module/i8042/parameters/nopnp ]; then echo 1 > /sys/module/i8042/parameters/nopnp; fi"
ExecStart=/bin/bash -c "if [ -w /sys/module/i8042/parameters/nopnp ]; then echo 1 > /sys/module/i8042/parameters/nopnp; fi"

[Install]
WantedBy=sysinit.target
100 changes: 81 additions & 19 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,107 @@
import os
import subprocess
import threading
from queue import Queue, Empty
from flask import Flask, render_template, Response, send_from_directory
from werkzeug.utils import secure_filename

app = Flask(__name__)
ISO_DIR = "/workdir/out"
ISO_NAME = "Arch.iso"
ISO_PATH = os.path.join(ISO_DIR, ISO_NAME)

@app.route('/')
def index():
return render_template('index.html')

@app.route('/build')
def build():
def generate():
# Ensure the output directory exists
os.makedirs(ISO_DIR, exist_ok=True)
# --- Build Process Management ---
build_process = None
build_lock = threading.Lock()

# Command to execute the build script
def run_build_in_thread(queue):
"""
Runs the build script in a separate thread and puts its output into a queue.
This prevents the Flask endpoint from blocking.
"""
global build_process
try:
command = ["/entrypoint.sh", "build", "out", "work"]
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
universal_newlines=True,
errors='replace' # Avoids crashing on weird characters
)
# Set the global process object so we can check its status
with build_lock:
build_process = process

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True)

for line in process.stdout:
yield f"data: {line}\n\n"
# Stream output to the queue
for line in iter(process.stdout.readline, ''):
queue.put(line)

process.stdout.close()
process.wait()

# Signal completion status via the queue
if process.returncode == 0:
yield "data: BUILD_SUCCESS\n\n"
queue.put("BUILD_SUCCESS")
else:
yield "data: BUILD_FAILED\n\n"
queue.put("BUILD_FAILED")

except Exception as e:
queue.put(f"BUILD_ERROR: {str(e)}")
finally:
# Clear the global process variable once done
with build_lock:
build_process = None

@app.route('/')
def index():
return render_template('index.html')

@app.route('/build')
def build():
"""
Starts a new build if one is not already running.
Streams build logs back to the client using Server-Sent Events.
"""
with build_lock:
if build_process and build_process.poll() is None:
return Response("data: BUILD_IN_PROGRESS\n\n", mimetype='text/event-stream')

log_queue = Queue()
thread = threading.Thread(target=run_build_in_thread, args=(log_queue,))
thread.daemon = True
thread.start()

def generate():
while thread.is_alive() or not log_queue.empty():
try:
line = log_queue.get(timeout=1)
yield f"data: {line}\n\n"
except Empty:
# If the queue is empty, the build may still be running.
# The loop will continue until the thread is finished.
pass

return Response(generate(), mimetype='text/event-stream')

@app.route('/download')
def download():
if os.path.exists(ISO_PATH):
return send_from_directory(directory=ISO_DIR, path=ISO_NAME, as_attachment=True)
"""
Provides the built ISO for download.
Includes filename sanitization as a security best practice.
"""
safe_filename = secure_filename(ISO_NAME)
if safe_filename != ISO_NAME:
# This case should not be reachable with a hardcoded ISO_NAME,
# but serves as a defense-in-depth security measure.
return "Invalid filename provided.", 400

if os.path.exists(os.path.join(ISO_DIR, safe_filename)):
return send_from_directory(directory=ISO_DIR, path=safe_filename, as_attachment=True)
else:
return "ISO not found.", 404

if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
app.run(host='0.0.0.0', port=8080)
26 changes: 17 additions & 9 deletions profiledef.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,30 @@ iso_application="Arch Linux No Beep Live/Rescue DVD"
iso_version="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)"
install_dir="arch"
buildmodes=('iso')
# Use simplified, general bootmodes
bootmodes=('bios.syslinux' 'uefi.systemd-boot')
arch="x86_64"
pacman_conf="pacman.conf"
airootfs_image_type="squashfs"
bootstrap_tarball_compression=('zstd' '-c' '-T0' '--auto-threads=logical' '--long' '-19')

# Base compression options for mksquashfs
airootfs_image_tool_options=(
'-comp' 'xz'
'-Xbcj' 'x86'
'-b' '1M'
'-Xdict-size' '1M'
)

# Use better compression options correctly formatted for mksquashfs with XZ
# XZ compressor supports these options: -Xbcj and -Xdict-size
if [ "$(nproc)" -gt 2 ]; then
# For multi-core systems: use XZ with bcj x86 filter for better compression
airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-b' '1M' '-Xdict-size' '1M')
# Determine the number of processors to use for compression
if [ "$(nproc)" -ge 4 ]; then
# Use all available cores for systems with 4 or more cores
airootfs_image_tool_options+=('-processors' "$(nproc)")
else
# For single/dual-core systems: use safe fixed dictionary size
airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-b' '1M' '-Xdict-size' '512K')
# Use a single thread for systems with fewer than 4 cores
airootfs_image_tool_options+=('-processors' '1')
fi

bootstrap_tarball_compression=('zstd' '-c' '-T0' '--auto-threads=logical' '--long' '-19')
file_permissions=(
["/etc/shadow"]="0:0:400"
["/root"]="0:0:750"
Expand All @@ -32,4 +40,4 @@ file_permissions=(
["/usr/local/bin/choose-mirror"]="0:0:755"
["/usr/local/bin/Installation_guide"]="0:0:755"
["/usr/local/bin/livecd-sound"]="0:0:755"
)
)
Loading
Loading