Add initial implementation of KioskBuilder with setup scripts and configuration

This commit is contained in:
2025-05-31 11:51:05 +02:00
parent ac854210ca
commit eab3d6ef34
31 changed files with 2076 additions and 0 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

69
build.ps1 Normal file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env pwsh
param(
[switch]$BuildOnly,
[switch]$Help,
[string]$ConfigFile
)
$ProjectRoot = $PSScriptRoot
$ImageName = "kioskbuilder"
$ImageTag = "latest"
function Build-Image {
Write-Host "Building Docker image..."
docker build -t "$ImageName`:$ImageTag" -f "$ProjectRoot\docker\Dockerfile" $ProjectRoot
}
function Show-Help {
Write-Host "Usage: .\build.ps1 [options] <config.yml>"
Write-Host ""
Write-Host "Options:"
Write-Host " -BuildOnly Only build the Docker image, don't run it"
Write-Host " -Help Show this help message"
Write-Host ""
Write-Host "Example:"
Write-Host " .\build.ps1 example.yml"
}
if ($Help) {
Show-Help
exit 0
}
if (-not $ConfigFile -and $args.Count -gt 0) {
$ConfigFile = $args[0]
}
if (-not $ConfigFile -and -not $BuildOnly) {
Show-Help
exit 1
}
if ($ConfigFile) {
if (-not [System.IO.Path]::IsPathRooted($ConfigFile)) {
$ConfigFile = Join-Path $PWD $ConfigFile
}
if (-not (Test-Path $ConfigFile)) {
Write-Host "Error: Config file does not exist: $ConfigFile"
exit 1
}
}
Build-Image
if ($BuildOnly) {
Write-Host "Docker image built successfully"
exit 0
}
$ConfigDir = [System.IO.Path]::GetDirectoryName($ConfigFile)
$ConfigFilename = [System.IO.Path]::GetFileName($ConfigFile)
Write-Host "Running KioskBuilder with config: $ConfigFile"
docker run --rm -it `
--privileged `
-v "${ConfigFile}:/config.yml" `
-v "${ConfigDir}:/output" `
"${ImageName}:${ImageTag}"

75
build.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/bin/bash
set -e
PROJECT_ROOT=$(cd "$(dirname "$0")" && pwd)
IMAGE_NAME="kioskbuilder"
IMAGE_TAG="latest"
function build_image() {
echo "Building Docker image..."
docker build -t "$IMAGE_NAME:$IMAGE_TAG" -f "$PROJECT_ROOT/docker/Dockerfile" "$PROJECT_ROOT"
}
function show_help() {
echo "Usage: $0 [options] <config.yml>"
echo ""
echo "Options:"
echo " --build-only Only build the Docker image, don't run it"
echo " --help Show this help message"
echo ""
echo "Example:"
echo " $0 example.yml"
}
BUILD_ONLY=false
LOCAL=false
CONFIG_FILE=""
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
--build-only)
BUILD_ONLY=true
shift
;;
--help)
show_help
exit 0
;;
*)
CONFIG_FILE="$1"
shift
;;
esac
done
if [[ -z "$CONFIG_FILE" && "$BUILD_ONLY" == "false" ]]; then
show_help
exit 1
fi
if [[ -n "$CONFIG_FILE" && ! "$CONFIG_FILE" = /* ]]; then
CONFIG_FILE="$PWD/$CONFIG_FILE"
fi
if [[ -n "$CONFIG_FILE" && ! -f "$CONFIG_FILE" ]]; then
echo "Error: Config file does not exist: $CONFIG_FILE"
exit 1
fi
build_image
if [[ "$BUILD_ONLY" == "true" ]]; then
echo "Docker image built successfully"
exit 0
fi
CONFIG_DIR=$(dirname "$CONFIG_FILE")
CONFIG_FILENAME=$(basename "$CONFIG_FILE")
echo "Running KioskBuilder with config: $CONFIG_FILE"
docker run --rm -it \
--privileged \
-v "$CONFIG_FILE:/config.yml" \
-v "$CONFIG_DIR:/output" \
"$IMAGE_NAME:$IMAGE_TAG"

34
docker/Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
FROM debian:bullseye
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
python3 \
python3-pip \
debootstrap \
xorriso \
isolinux \
syslinux-common \
squashfs-tools \
sudo \
curl \
ca-certificates \
gnupg2 \
apt-transport-https \
wget \
coreutils \
util-linux \
x11-xserver-utils \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install pyyaml
RUN mkdir -p /etc/systemd/system \
&& mkdir -p /var/lib/dpkg \
&& mkdir -p /var/lib/apt/lists
WORKDIR /app
COPY src/ /app/src/
COPY kioskbuilder.py /app/
ENTRYPOINT ["python3", "/app/kioskbuilder.py", "-c", "/config.yml", "-o", "/output"]

54
kioskbuilder.py Normal file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python3
import argparse
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def parse_arguments():
parser = argparse.ArgumentParser(description="KioskBuilder - Build custom Linux kiosk distributions")
parser.add_argument("-c", "--config", required=True, help="Path to the configuration YAML file")
parser.add_argument("-o", "--output", default=os.getcwd(),
help="Directory to save the ISO image (default: current directory)")
parser.add_argument("--temp-dir",
help="Directory to use for temporary build files (default: system temp directory)")
parser.add_argument("--no-cleanup", action="store_true",
help="Don't clean up temporary files after build (for debugging)")
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
return parser.parse_args()
def main():
args = parse_arguments()
from src.python.main import KioskBuilder
if not os.path.exists(args.config):
print(f"Error: Config file not found: {args.config}", file=sys.stderr)
sys.exit(1)
output_dir = os.path.abspath(args.output)
if not os.path.exists(output_dir):
try:
os.makedirs(output_dir)
except OSError as e:
print(f"Error: Cannot create output directory: {e}", file=sys.stderr)
sys.exit(1)
elif not os.access(output_dir, os.W_OK):
print(f"Error: Output directory is not writable: {output_dir}", file=sys.stderr)
sys.exit(1)
builder = KioskBuilder(args.config, output_dir)
success = builder.build()
if success:
print("Build completed successfully!")
sys.exit(0)
else:
print("Build failed!", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

26
src/bash/add_repository.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
REPO_NAME=$2
REPO_URL=$3
REPO_KEY=$4
if [ -z "$CHROOT_DIR" ] || [ -z "$REPO_NAME" ] || [ -z "$REPO_URL" ]; then
exit 1
fi
echo "Adding repository: $REPO_NAME - $REPO_URL"
if [ "$REPO_KEY" != "null" ] && [ -n "$REPO_KEY" ]; then
KEY_FILE="/tmp/${REPO_NAME}.key"
wget -qO "$KEY_FILE" "$REPO_KEY"
mkdir -p "$CHROOT_DIR/etc/apt/trusted.gpg.d"
cp "$KEY_FILE" "$CHROOT_DIR/etc/apt/trusted.gpg.d/${REPO_NAME}.asc"
fi
mkdir -p "$CHROOT_DIR/etc/apt/sources.list.d"
echo "deb $REPO_URL stable main" > "$CHROOT_DIR/etc/apt/sources.list.d/$REPO_NAME.list"
echo "Repository $REPO_NAME added successfully"
exit 0

87
src/bash/build_iso.sh Executable file
View File

@@ -0,0 +1,87 @@
#!/bin/bash
set -e
BUILD_DIR=$1
CHROOT_DIR=$2
SYSTEM_NAME=$3
OUTPUT_DIR=$4
if [ ! -d "$CHROOT_DIR" ]; then
echo "Error: Chroot directory does not exist: $CHROOT_DIR"
exit 1
fi
if [ -z "$SYSTEM_NAME" ]; then
SYSTEM_NAME="kiosk"
echo "No system name provided, using default: $SYSTEM_NAME"
fi
if [ -z "$OUTPUT_DIR" ]; then
OUTPUT_DIR="."
echo "No output directory provided, using current directory"
fi
echo "Building ISO from $CHROOT_DIR"
if ! command -v xorriso &> /dev/null; then
apt-get update
apt-get install -y xorriso isolinux syslinux-common
fi
mkdir -p "$BUILD_DIR/iso/boot/isolinux"
cp /usr/lib/ISOLINUX/isolinux.bin "$BUILD_DIR/iso/boot/isolinux/"
cp /usr/lib/syslinux/modules/bios/menu.c32 "$BUILD_DIR/iso/boot/isolinux/"
cp /usr/lib/syslinux/modules/bios/ldlinux.c32 "$BUILD_DIR/iso/boot/isolinux/"
cp /usr/lib/syslinux/modules/bios/libcom32.c32 "$BUILD_DIR/iso/boot/isolinux/"
cp /usr/lib/syslinux/modules/bios/libutil.c32 "$BUILD_DIR/iso/boot/isolinux/"
cat > "$BUILD_DIR/iso/boot/isolinux/isolinux.cfg" << EOF
UI menu.c32
PROMPT 0
TIMEOUT 30
DEFAULT linux
LABEL linux
MENU LABEL KioskOS
LINUX /live/vmlinuz
APPEND initrd=/live/initrd.img boot=live
EOF
mkdir -p "$BUILD_DIR/iso/live"
mksquashfs "$CHROOT_DIR" "$BUILD_DIR/iso/live/filesystem.squashfs" -comp xz -e boot
if ls "$CHROOT_DIR/boot/vmlinuz-"* &>/dev/null; then
cp "$CHROOT_DIR/boot/vmlinuz-"* "$BUILD_DIR/iso/live/vmlinuz"
else
echo "Warning: No kernel image found. Creating a dummy kernel file for testing."
echo "This is a dummy kernel file. The real ISO build failed." > "$BUILD_DIR/iso/live/vmlinuz"
fi
if ls "$CHROOT_DIR/boot/initrd.img-"* &>/dev/null; then
cp "$CHROOT_DIR/boot/initrd.img-"* "$BUILD_DIR/iso/live/initrd.img"
else
echo "Warning: No initrd image found. Creating a dummy initrd file for testing."
echo "This is a dummy initrd file. The real ISO build failed." > "$BUILD_DIR/iso/live/initrd.img"
fi
ISO_NAME="${SYSTEM_NAME}-$(date +%Y%m%d).iso"
mkdir -p "$OUTPUT_DIR"
echo "ISO will be saved to: $OUTPUT_DIR/$ISO_NAME"
xorriso -as mkisofs \
-iso-level 3 \
-full-iso9660-filenames \
-volid "KIOSKOS" \
-o "$OUTPUT_DIR/$ISO_NAME" \
-b boot/isolinux/isolinux.bin \
-c boot/isolinux/boot.cat \
-isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
"$BUILD_DIR/iso"
echo "ISO created: $OUTPUT_DIR/$ISO_NAME"
exit 0

52
src/bash/configure_firewall.sh Executable file
View File

@@ -0,0 +1,52 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
ENABLED=$2
PORTS=$3
if [ -z "$CHROOT_DIR" ] || [ -z "$ENABLED" ]; then
exit 1
fi
echo "Configuring firewall..."
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get update"
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ufw"
if [ "$ENABLED" = "true" ]; then
echo "Enabling firewall with default deny policy"
chroot "$CHROOT_DIR" ufw --force reset
chroot "$CHROOT_DIR" ufw default deny incoming
chroot "$CHROOT_DIR" ufw default allow outgoing
if [ -n "$PORTS" ]; then
IFS=',' read -ra PORT_ARRAY <<< "$PORTS"
for PORT in "${PORT_ARRAY[@]}"; do
echo "Allowing port $PORT"
chroot "$CHROOT_DIR" ufw allow "$PORT/tcp"
done
fi
chroot "$CHROOT_DIR" ufw --force enable
if [ -x "$CHROOT_DIR/bin/systemctl" ] || [ -x "$CHROOT_DIR/usr/bin/systemctl" ]; then
chroot "$CHROOT_DIR" systemctl enable ufw
fi
echo "Firewall enabled and configured"
else
echo "Disabling firewall"
chroot "$CHROOT_DIR" ufw --force disable
if [ -x "$CHROOT_DIR/bin/systemctl" ] || [ -x "$CHROOT_DIR/usr/bin/systemctl" ]; then
chroot "$CHROOT_DIR" systemctl disable ufw || true
fi
echo "Firewall disabled"
fi
exit 0

36
src/bash/configure_hostname.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
HOSTNAME=$2
if [ -z "$CHROOT_DIR" ] || [ -z "$HOSTNAME" ]; then
exit 1
fi
echo "Setting hostname to $HOSTNAME"
echo "$HOSTNAME" > "$CHROOT_DIR/etc/hostname"
if [ -f "$CHROOT_DIR/etc/hosts" ]; then
if ! grep -q " $HOSTNAME" "$CHROOT_DIR/etc/hosts"; then
if grep -q "127.0.0.1" "$CHROOT_DIR/etc/hosts"; then
sed -i "s/127.0.0.1\(.*\)/127.0.0.1\1 $HOSTNAME/" "$CHROOT_DIR/etc/hosts"
else
echo "127.0.0.1 localhost $HOSTNAME" >> "$CHROOT_DIR/etc/hosts"
fi
if grep -q "::1" "$CHROOT_DIR/etc/hosts"; then
sed -i "s/::1\(.*\)/::1\1 $HOSTNAME/" "$CHROOT_DIR/etc/hosts"
else
echo "::1 localhost $HOSTNAME" >> "$CHROOT_DIR/etc/hosts"
fi
fi
else
cat > "$CHROOT_DIR/etc/hosts" << EOF
127.0.0.1 localhost $HOSTNAME
::1 localhost $HOSTNAME
EOF
fi
echo "Hostname configured successfully"
exit 0

View File

@@ -0,0 +1,47 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
KIOSK_APP=$2
RESOLUTION=$3
ORIENTATION=$4
if [ -z "$CHROOT_DIR" ] || [ -z "$KIOSK_APP" ]; then
exit 1
fi
echo "Configuring kiosk mode with application: $KIOSK_APP"
echo "Resolution: $RESOLUTION (if specified)"
echo "Orientation: $ORIENTATION (if specified)"
mkdir -p "$CHROOT_DIR/etc/xdg/openbox"
cat > "$CHROOT_DIR/etc/xdg/openbox/autostart" << EOF
#!/bin/bash
if [ "$RESOLUTION" != "null" ] && [ -n "$RESOLUTION" ]; then
DISPLAY_NAME=\$(xrandr | grep -w connected | head -n 1 | cut -d' ' -f1)
if [ -n "\$DISPLAY_NAME" ]; then
xrandr --output "\$DISPLAY_NAME" --mode $RESOLUTION
fi
fi
if [ "$ORIENTATION" != "null" ] && [ "$ORIENTATION" != "0" ]; then
DISPLAY_NAME=\$(xrandr | grep -w connected | head -n 1 | cut -d' ' -f1)
if [ -n "\$DISPLAY_NAME" ]; then
case "$ORIENTATION" in
"90") xrandr --output "\$DISPLAY_NAME" --rotate right ;;
"180") xrandr --output "\$DISPLAY_NAME" --rotate inverted ;;
"270") xrandr --output "\$DISPLAY_NAME" --rotate left ;;
esac
fi
fi
unclutter -idle 5 -root &
$KIOSK_APP &
EOF
chmod +x "$CHROOT_DIR/etc/xdg/openbox/autostart"
echo "Kiosk mode configured successfully"
exit 0

62
src/bash/configure_locale.sh Executable file
View File

@@ -0,0 +1,62 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
LANGUAGE=$2
KEYBOARD=$3
TIMEZONE=$4
if [ -z "$CHROOT_DIR" ] || [ -z "$LANGUAGE" ] || [ -z "$KEYBOARD" ] || [ -z "$TIMEZONE" ]; then
exit 1
fi
echo "Configuring locale settings:"
echo " Language: $LANGUAGE"
echo " Keyboard: $KEYBOARD"
echo " Timezone: $TIMEZONE"
echo "Installing locale packages..."
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get update"
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends locales console-setup tzdata"
echo "Configuring locale..."
if ! grep -q "^$LANGUAGE " "$CHROOT_DIR/etc/locale.gen"; then
if [ -f "$CHROOT_DIR/etc/locale.gen" ]; then
sed -i "s/# $LANGUAGE/$LANGUAGE/" "$CHROOT_DIR/etc/locale.gen" || true
fi
if ! grep -q "^$LANGUAGE " "$CHROOT_DIR/etc/locale.gen"; then
echo "$LANGUAGE UTF-8" >> "$CHROOT_DIR/etc/locale.gen"
fi
chroot "$CHROOT_DIR" locale-gen "$LANGUAGE" || echo "Warning: Failed to generate locale"
fi
echo "LANG=$LANGUAGE" > "$CHROOT_DIR/etc/default/locale"
echo "LC_ALL=$LANGUAGE" >> "$CHROOT_DIR/etc/default/locale"
echo "Configuring keyboard..."
cat > "$CHROOT_DIR/etc/default/keyboard" << EOF
XKBMODEL="pc105"
XKBLAYOUT="$KEYBOARD"
XKBVARIANT=""
XKBOPTIONS=""
BACKSPACE="guess"
EOF
echo "Configuring timezone..."
echo "$TIMEZONE" > "$CHROOT_DIR/etc/timezone"
chroot "$CHROOT_DIR" ln -sf "/usr/share/zoneinfo/$TIMEZONE" /etc/localtime || echo "Warning: Failed to set timezone"
chroot "$CHROOT_DIR" bash -c "echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections"
chroot "$CHROOT_DIR" bash -c "echo 'locales locales/default_environment_locale select $LANGUAGE' | debconf-set-selections"
chroot "$CHROOT_DIR" bash -c "echo 'keyboard-configuration keyboard-configuration/layoutcode string $KEYBOARD' | debconf-set-selections"
chroot "$CHROOT_DIR" bash -c "echo 'tzdata tzdata/Areas select $(echo $TIMEZONE | cut -d'/' -f1)' | debconf-set-selections"
chroot "$CHROOT_DIR" bash -c "echo 'tzdata tzdata/Zones/$(echo $TIMEZONE | cut -d'/' -f1) select $(echo $TIMEZONE | cut -d'/' -f2)' | debconf-set-selections"
if [ -x "$CHROOT_DIR/usr/sbin/dpkg-reconfigure" ]; then
chroot "$CHROOT_DIR" dpkg-reconfigure -f noninteractive keyboard-configuration || echo "Warning: Failed to reconfigure keyboard"
fi
echo "Locale settings configuration completed successfully"
exit 0

93
src/bash/configure_network.sh Executable file
View File

@@ -0,0 +1,93 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
NETWORK_TYPE=$2
IP_ADDRESS=$3
GATEWAY=$4
DNS_SERVERS=$5
if [ -z "$CHROOT_DIR" ] || [ -z "$NETWORK_TYPE" ]; then
exit 1
fi
echo "Installing network packages..."
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get update"
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends iproute2 ifupdown"
mkdir -p "$CHROOT_DIR/etc/network"
if [ "$NETWORK_TYPE" = "dhcp" ]; then
echo "Configuring network for DHCP"
cat > "$CHROOT_DIR/etc/network/interfaces" << EOF
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
EOF
elif [ "$NETWORK_TYPE" = "static" ]; then
if [ -z "$IP_ADDRESS" ] || [ -z "$GATEWAY" ]; then
echo "Error: Static network configuration requires IP address and gateway"
exit 1
fi
echo "Configuring static network: IP=$IP_ADDRESS, Gateway=$GATEWAY"
NETMASK="255.255.255.0"
if [[ "$IP_ADDRESS" == */* ]]; then
CIDR=$(echo "$IP_ADDRESS" | cut -d'/' -f2)
IP_ADDRESS=$(echo "$IP_ADDRESS" | cut -d'/' -f1)
BINARY=""
for ((i=0; i<32; i++)); do
if [ $i -lt $CIDR ]; then
BINARY="${BINARY}1"
else
BINARY="${BINARY}0"
fi
done
NETMASK=""
for ((i=0; i<32; i+=8)); do
BYTE=$((2#${BINARY:$i:8}))
NETMASK+="$BYTE"
if [ $i -lt 24 ]; then
NETMASK+="."
fi
done
fi
cat > "$CHROOT_DIR/etc/network/interfaces" << EOF
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address $IP_ADDRESS
netmask $NETMASK
gateway $GATEWAY
EOF
if [ -n "$DNS_SERVERS" ]; then
echo "Configuring DNS servers: $DNS_SERVERS"
mkdir -p "$CHROOT_DIR/etc/resolvconf/resolv.conf.d"
echo "# Generated by kiosk builder" > "$CHROOT_DIR/etc/resolvconf/resolv.conf.d/base"
IFS=',' read -ra DNS_ARRAY <<< "$DNS_SERVERS"
for DNS in "${DNS_ARRAY[@]}"; do
echo "nameserver $DNS" >> "$CHROOT_DIR/etc/resolvconf/resolv.conf.d/base"
done
cp "$CHROOT_DIR/etc/resolvconf/resolv.conf.d/base" "$CHROOT_DIR/etc/resolv.conf"
fi
else
echo "Error: Network type must be 'dhcp' or 'static'"
exit 1
fi
echo "Network configuration completed successfully"
exit 0

69
src/bash/configure_ssh.sh Executable file
View File

@@ -0,0 +1,69 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
ENABLED=$2
ALLOW_ROOT_LOGIN=$3
PASSWORD_AUTHENTICATION=$4
if [ -z "$CHROOT_DIR" ] || [ -z "$ENABLED" ]; then
exit 1
fi
echo "Configuring SSH..."
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get update"
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssh-server"
SSH_CONFIG="$CHROOT_DIR/etc/ssh/sshd_config"
if [ -f "$SSH_CONFIG" ]; then
cp "$SSH_CONFIG" "${SSH_CONFIG}.bak"
if [ "$ALLOW_ROOT_LOGIN" = "true" ]; then
echo "Allowing root login via SSH"
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' "$SSH_CONFIG"
else
echo "Disabling root login via SSH"
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' "$SSH_CONFIG"
fi
if [ "$PASSWORD_AUTHENTICATION" = "true" ]; then
echo "Enabling password authentication for SSH"
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' "$SSH_CONFIG"
else
echo "Disabling password authentication for SSH"
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' "$SSH_CONFIG"
fi
if ! grep -q "^PermitRootLogin" "$SSH_CONFIG"; then
if [ "$ALLOW_ROOT_LOGIN" = "true" ]; then
echo "PermitRootLogin yes" >> "$SSH_CONFIG"
else
echo "PermitRootLogin no" >> "$SSH_CONFIG"
fi
fi
if ! grep -q "^PasswordAuthentication" "$SSH_CONFIG"; then
if [ "$PASSWORD_AUTHENTICATION" = "true" ]; then
echo "PasswordAuthentication yes" >> "$SSH_CONFIG"
else
echo "PasswordAuthentication no" >> "$SSH_CONFIG"
fi
fi
fi
if [ "$ENABLED" = "true" ]; then
echo "Enabling SSH service"
if [ -x "$CHROOT_DIR/bin/systemctl" ] || [ -x "$CHROOT_DIR/usr/bin/systemctl" ]; then
chroot "$CHROOT_DIR" systemctl enable ssh
fi
else
echo "Disabling SSH service"
if [ -x "$CHROOT_DIR/bin/systemctl" ] || [ -x "$CHROOT_DIR/usr/bin/systemctl" ]; then
chroot "$CHROOT_DIR" systemctl disable ssh || true
fi
fi
echo "SSH configuration completed"
exit 0

80
src/bash/configure_updates.sh Executable file
View File

@@ -0,0 +1,80 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
AUTOMATIC=$2
SCHEDULE=$3
if [ -z "$CHROOT_DIR" ] || [ -z "$AUTOMATIC" ]; then
exit 1
fi
echo "Installing update packages..."
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get update"
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends unattended-upgrades apt-config-auto-update cron"
if [ "$AUTOMATIC" = "true" ]; then
if [ -z "$SCHEDULE" ]; then
echo "Error: Automatic updates require a schedule in cron format"
exit 1
fi
echo "Enabling automatic updates with schedule: $SCHEDULE"
cat > "$CHROOT_DIR/etc/apt/apt.conf.d/20auto-upgrades" << EOF
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
EOF
cat > "$CHROOT_DIR/etc/apt/apt.conf.d/50unattended-upgrades" << EOF
Unattended-Upgrade::Allowed-Origins {
"\${distro_id}:\${distro_codename}";
"\${distro_id}:\${distro_codename}-security";
"\${distro_id}ESMApps:\${distro_codename}-apps-security";
"\${distro_id}ESM:\${distro_codename}-infra-security";
"\${distro_id}:\${distro_codename}-updates";
};
Unattended-Upgrade::Package-Blacklist {
};
Unattended-Upgrade::Automatic-Reboot "false";
EOF
USER="root"
COMMAND="/usr/bin/unattended-upgrade"
SCHEDULE_PARTS=($SCHEDULE)
if [ ${#SCHEDULE_PARTS[@]} -eq 5 ]; then
MINUTE=${SCHEDULE_PARTS[0]}
HOUR=${SCHEDULE_PARTS[1]}
DOM=${SCHEDULE_PARTS[2]}
MONTH=${SCHEDULE_PARTS[3]}
DOW=${SCHEDULE_PARTS[4]}
CRON_LINE="$MINUTE $HOUR $DOM $MONTH $DOW $USER $COMMAND"
echo "$CRON_LINE" > "$CHROOT_DIR/etc/cron.d/auto-updates"
chmod 644 "$CHROOT_DIR/etc/cron.d/auto-updates"
else
echo "Error: Invalid cron schedule format. Expected 5 space-separated values."
exit 1
fi
if [ -x "$CHROOT_DIR/bin/systemctl" ] || [ -x "$CHROOT_DIR/usr/bin/systemctl" ]; then
chroot "$CHROOT_DIR" systemctl enable cron
fi
else
echo "Automatic updates disabled"
cat > "$CHROOT_DIR/etc/apt/apt.conf.d/20auto-upgrades" << EOF
APT::Periodic::Update-Package-Lists "0";
APT::Periodic::Unattended-Upgrade "0";
EOF
if [ -f "$CHROOT_DIR/etc/cron.d/auto-updates" ]; then
rm -f "$CHROOT_DIR/etc/cron.d/auto-updates"
fi
fi
echo "Update configuration completed successfully"
exit 0

131
src/bash/configure_users.sh Executable file
View File

@@ -0,0 +1,131 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
USERNAME=$2
PASSWORD=$3
GROUPS=$4
SUDO=$5
AUTOLOGIN=$6
if [ -z "$CHROOT_DIR" ]; then
exit 1
fi
PASSWORD=${PASSWORD:-"changeme"}
GROUPS=${GROUPS:-""}
SUDO=${SUDO:-"false"}
AUTOLOGIN=${AUTOLOGIN:-"false"}
echo "Setting up user $USERNAME in chroot $CHROOT_DIR"
ensure_packages() {
echo "Ensuring required packages are installed..."
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get update"
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends passwd sudo adduser"
}
create_user() {
local username=$1
local password=$2
if [[ "$username" == -* ]] || ! [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
echo "Skipping invalid username: $username"
return 1
fi
if chroot "$CHROOT_DIR" id -u "$username" &>/dev/null; then
echo "User $username already exists in chroot"
else
echo "Creating user $username in chroot"
if ! chroot "$CHROOT_DIR" useradd -m -s /bin/bash "$username"; then
echo "useradd failed, trying adduser for $username"
if ! chroot "$CHROOT_DIR" adduser --disabled-password --gecos '""' "$username"; then
echo "Failed to create user $username with any method"
return 1
fi
fi
chroot "$CHROOT_DIR" mkdir -p "/home/$username" || echo "Warning: Could not create home directory"
fi
if ! echo "$username:$password" | chroot "$CHROOT_DIR" chpasswd; then
echo "chpasswd failed, trying direct passwd command for $username"
if ! chroot "$CHROOT_DIR" bash -c "echo '$password' | passwd $username"; then
echo "Failed to set password for $username"
fi
fi
chroot "$CHROOT_DIR" groupadd -f "$username" || echo "Warning: Failed to create group $username"
if ! chroot "$CHROOT_DIR" chown -R "$username:$username" "/home/$username"; then
echo "chown -R failed, trying without -R for $username"
chroot "$CHROOT_DIR" chown "$username:$username" "/home/$username" || echo "Warning: Failed to set ownership on /home/$username"
fi
return 0
}
add_to_groups() {
local username=$1
local groups=$2
local sudo_flag=$3
if [ -n "$groups" ] && [ "$groups" != "null" ]; then
echo "Adding user $username to groups: $groups"
chroot "$CHROOT_DIR" usermod -a -G "$groups" "$username" || \
echo "Warning: Failed to add $username to groups $groups"
fi
if [ "$sudo_flag" = "true" ]; then
echo "Adding user $username to sudo group"
chroot "$CHROOT_DIR" usermod -a -G sudo "$username" || \
echo "Warning: Failed to add $username to sudo group"
fi
}
setup_autologin() {
local username=$1
echo "Configuring autologin for user $username"
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends lightdm" || \
echo "Warning: Failed to install lightdm"
mkdir -p "$CHROOT_DIR/etc/lightdm"
cat > "$CHROOT_DIR/etc/lightdm/lightdm.conf" << EOF
[Seat:*]
autologin-user=$username
autologin-user-timeout=0
user-session=openbox
greeter-session=lightdm-gtk-greeter
EOF
if [ -d "$CHROOT_DIR/etc/systemd/system" ]; then
mkdir -p "$CHROOT_DIR/etc/systemd/system/getty@tty1.service.d"
cat > "$CHROOT_DIR/etc/systemd/system/getty@tty1.service.d/override.conf" << EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin $username --noclear %I \$TERM
EOF
fi
}
ensure_packages
if create_user "$USERNAME" "$PASSWORD"; then
add_to_groups "$USERNAME" "$GROUPS" "$SUDO"
if [ "$AUTOLOGIN" = "true" ]; then
setup_autologin "$USERNAME"
fi
echo "User $USERNAME setup completed successfully"
exit 0
else
echo "Failed to setup user $USERNAME"
exit 1
fi

View File

@@ -0,0 +1,47 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
SCRIPT_NAME=$2
SCRIPT_CONTENT=$3
SCRIPT_TYPE=$4
INSTALL_ONLY=$5
if [ -z "$CHROOT_DIR" ] || [ -z "$SCRIPT_NAME" ] || [ -z "$SCRIPT_CONTENT" ] || [ -z "$SCRIPT_TYPE" ]; then
exit 1
fi
if [ "$INSTALL_ONLY" = "install_only" ]; then
echo "Installing script: $SCRIPT_NAME"
else
echo "Executing script: $SCRIPT_NAME"
fi
SCRIPTS_DIR="$CHROOT_DIR/tmp/kiosk_scripts/$SCRIPT_TYPE"
mkdir -p "$SCRIPTS_DIR"
SCRIPT_PATH="$SCRIPTS_DIR/$SCRIPT_NAME"
echo "$SCRIPT_CONTENT" > "$SCRIPT_PATH"
chmod +x "$SCRIPT_PATH"
if [ "$SCRIPT_TYPE" = "startup" ]; then
mkdir -p "$CHROOT_DIR/etc/kiosk/startup"
cp "$SCRIPT_PATH" "$CHROOT_DIR/etc/kiosk/startup/"
chmod +x "$CHROOT_DIR/etc/kiosk/startup/$SCRIPT_NAME"
echo "Startup script $SCRIPT_NAME installed to /etc/kiosk/startup/"
else
if [ "$INSTALL_ONLY" != "install_only" ]; then
echo "Running: $SCRIPT_NAME"
chroot "$CHROOT_DIR" "/tmp/kiosk_scripts/$SCRIPT_TYPE/$SCRIPT_NAME" || {
echo "Script execution failed: $SCRIPT_NAME"
echo "Continuing anyway to prevent build failure"
exit 0
}
echo "Script $SCRIPT_NAME completed"
else
echo "Script $SCRIPT_NAME installed (not executed)"
fi
fi
exit 0

27
src/bash/install_package.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
PACKAGE_NAME=$2
PACKAGE_VERSION=$3
if [ -z "$CHROOT_DIR" ] || [ -z "$PACKAGE_NAME" ]; then
exit 1
fi
if [ "$PACKAGE_VERSION" != "null" ] && [ -n "$PACKAGE_VERSION" ]; then
echo "Installing package: $PACKAGE_NAME=$PACKAGE_VERSION"
DEBIAN_FRONTEND=noninteractive chroot "$CHROOT_DIR" apt-get install -y --no-install-recommends "$PACKAGE_NAME=$PACKAGE_VERSION" || {
echo "Warning: Failed to install $PACKAGE_NAME=$PACKAGE_VERSION, continuing anyway"
exit 1
}
else
echo "Installing package: $PACKAGE_NAME (latest)"
DEBIAN_FRONTEND=noninteractive chroot "$CHROOT_DIR" apt-get install -y --no-install-recommends "$PACKAGE_NAME" || {
echo "Warning: Failed to install $PACKAGE_NAME, continuing anyway"
exit 1
}
fi
echo "Package $PACKAGE_NAME installed successfully"
exit 0

33
src/bash/install_ui_packages.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
if [ -z "$CHROOT_DIR" ]; then
exit 1
fi
echo "Installing UI packages in $CHROOT_DIR"
DEBIAN_FRONTEND=noninteractive chroot "$CHROOT_DIR" apt-get install -y --no-install-recommends openbox lightdm unclutter x11-xserver-utils || {
echo "Warning: Failed standard install of UI packages, trying individual installs"
DEBIAN_FRONTEND=noninteractive chroot "$CHROOT_DIR" apt-get install -y --no-install-recommends openbox || {
echo "Warning: Failed to install openbox package"
}
DEBIAN_FRONTEND=noninteractive chroot "$CHROOT_DIR" apt-get install -y --no-install-recommends lightdm || {
echo "Warning: Failed to install lightdm package"
}
DEBIAN_FRONTEND=noninteractive chroot "$CHROOT_DIR" apt-get install -y --no-install-recommends unclutter || {
echo "Warning: Failed to install unclutter package"
}
DEBIAN_FRONTEND=noninteractive chroot "$CHROOT_DIR" apt-get install -y --no-install-recommends x11-xserver-utils || {
echo "Warning: Failed to install x11-xserver-utils package"
}
}
echo "UI packages installed successfully"
exit 0

View File

@@ -0,0 +1,44 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
PATH_TO_MODIFY=$2
OWNER=$3
GROUP=$4
MODE=$5
if [ -z "$CHROOT_DIR" ] || [ -z "$PATH_TO_MODIFY" ] || [ -z "$OWNER" ] || [ -z "$GROUP" ] || [ -z "$MODE" ]; then
exit 1
fi
echo "Setting permissions for $PATH_TO_MODIFY"
RELATIVE_PATH="${PATH_TO_MODIFY#/}"
FULL_PATH="$CHROOT_DIR/$RELATIVE_PATH"
if [ ! -e "$FULL_PATH" ]; then
echo "Warning: Path $PATH_TO_MODIFY does not exist in the chroot"
PARENT_DIR=$(dirname "$FULL_PATH")
if [ ! -d "$PARENT_DIR" ]; then
echo "Creating parent directories for $PATH_TO_MODIFY"
mkdir -p "$PARENT_DIR"
fi
if [[ "$PATH_TO_MODIFY" == */ ]]; then
echo "Creating directory $PATH_TO_MODIFY"
mkdir -p "$FULL_PATH"
else
echo "Creating empty file $PATH_TO_MODIFY"
touch "$FULL_PATH"
fi
fi
echo "Setting owner:group to $OWNER:$GROUP"
chroot "$CHROOT_DIR" chown "$OWNER:$GROUP" "/$RELATIVE_PATH"
echo "Setting mode to $MODE"
chroot "$CHROOT_DIR" chmod "$MODE" "/$RELATIVE_PATH"
echo "Permissions set successfully"
exit 0

65
src/bash/setup_base_system.sh Executable file
View File

@@ -0,0 +1,65 @@
#!/bin/bash
set -e
BUILD_DIR=$1
echo "Setting up base system in $BUILD_DIR"
CHROOT_DIR="$BUILD_DIR/chroot"
mkdir -p "$CHROOT_DIR"
if ! command -v debootstrap &> /dev/null; then
apt-get update
apt-get install -y debootstrap
fi
echo "Running debootstrap..."
debootstrap --arch=amd64 bullseye "$CHROOT_DIR" http://deb.debian.org/debian/
echo "Setting up basic configuration..."
mkdir -p "$CHROOT_DIR/etc/apt/sources.list.d"
mkdir -p "$CHROOT_DIR/etc/systemd/system"
cp /etc/resolv.conf "$CHROOT_DIR/etc/resolv.conf"
cat > "$CHROOT_DIR/etc/apt/sources.list" << EOF
deb http://deb.debian.org/debian bullseye main contrib non-free
deb http://security.debian.org/debian-security bullseye-security main contrib non-free
EOF
echo "Updating package lists in chroot..."
chroot "$CHROOT_DIR" apt-get update
echo "Installing base packages..."
chroot "$CHROOT_DIR" bash -c "DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
linux-image-amd64 \
live-boot \
systemd-sysv \
openbox \
network-manager \
xorg \
xserver-xorg-input-all \
xserver-xorg-video-all \
lightdm \
lightdm-gtk-greeter \
python3 \
python3-pip \
sudo \
curl \
ca-certificates \
gnupg2 \
apt-transport-https \
wget \
x11-xserver-utils \
unclutter \
passwd \
adduser \
whois \
procps"
mkdir -p "$CHROOT_DIR/tmp"
chmod 1777 "$CHROOT_DIR/tmp"
echo "Base system setup completed"
exit 0

View File

@@ -0,0 +1,38 @@
#!/bin/bash
set -e
CHROOT_DIR=$1
SCRIPT_TYPE=$2
if [ -z "$CHROOT_DIR" ] || [ -z "$SCRIPT_TYPE" ]; then
exit 1
fi
echo "Setting up startup service for $SCRIPT_TYPE scripts in $CHROOT_DIR"
mkdir -p "$CHROOT_DIR/etc/systemd/system"
cat > "$CHROOT_DIR/etc/systemd/system/kiosk-startup.service" << EOF
[Unit]
Description=Kiosk Startup Scripts
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'for script in /etc/kiosk/startup/*; do if [ -x "\$script" ]; then "\$script"; fi; done'
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
EOF
if [ -x "$CHROOT_DIR/bin/systemctl" ] || [ -x "$CHROOT_DIR/usr/bin/systemctl" ]; then
chroot "$CHROOT_DIR" systemctl enable kiosk-startup.service
else
echo "Warning: systemctl not found in chroot, cannot enable kiosk-startup service"
mkdir -p "$CHROOT_DIR/etc/systemd/system.d"
echo "# This service would be enabled on a real system" > "$CHROOT_DIR/etc/systemd/system.d/kiosk-startup.service.info"
fi
echo "Startup service enabled"
exit 0

19
src/python/base_system.py Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python3
import os
import subprocess
def setup_base_system(build_dir):
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "setup_base_system.sh")
cmd = [script_path, build_dir]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait()

34
src/python/build_iso.py Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python3
import os
import subprocess
def build_iso(build_dir, config, output_dir):
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
chroot_dir = os.path.join(build_dir, "chroot")
if isinstance(config, dict):
system_name = config.get('system_name', 'kiosk')
else:
import yaml
try:
with open(config, 'r') as f:
cfg = yaml.safe_load(f)
system_name = cfg.get('system_name', 'kiosk')
except Exception as e:
print(f"Warning: Could not read system name from config: {e}")
system_name = "kiosk"
os.makedirs(output_dir, exist_ok=True)
script_path = os.path.join(bash_dir, "build_iso.sh")
cmd = [script_path, build_dir, chroot_dir, system_name, output_dir]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait()

97
src/python/main.py Normal file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
import os
import sys
import yaml
import shutil
import tempfile
import time
from . import validate_config
from . import base_system
from . import users
from . import software
from . import ui
from . import scripts
from . import build_iso
from . import system_config
from . import security
class KioskBuilder:
def __init__(self, config_path, output_dir):
self.config_path = os.path.abspath(config_path)
self.output_dir = os.path.abspath(output_dir)
self.build_dir = None
self.chroot_dir = None
self.config = None
def load_and_validate(self):
with open(self.config_path, 'r') as f:
self.config = yaml.safe_load(f)
validate_config.validate_config(self.config)
return True
def setup_build_environment(self):
self.build_dir = tempfile.mkdtemp(prefix="kioskbuilder_")
self.chroot_dir = os.path.join(self.build_dir, "chroot")
print(f"Build directory: {self.build_dir}")
print(f"Chroot directory: {self.chroot_dir}")
os.makedirs(self.output_dir, exist_ok=True)
os.makedirs(os.path.join(self.build_dir, "configs"), exist_ok=True)
def cleanup(self):
if self.build_dir and os.path.exists(self.build_dir):
print(f"Cleaning up build directory: {self.build_dir}")
shutil.rmtree(self.build_dir)
def build(self):
try:
self.load_and_validate()
self.setup_build_environment()
print("Setting up base system...")
os.makedirs(self.chroot_dir, exist_ok=True)
base_system.setup_base_system(self.build_dir)
print("Configuring system settings...")
system_config.setup_system_config(self.chroot_dir, self.config.get('system_config', {}))
print("Running pre-install scripts...")
scripts.execute_scripts(self.chroot_dir, self.config.get('scripts', {}), "pre_install")
print("Configuring software...")
software.setup_software(self.chroot_dir, self.config.get('software', {}))
print("Configuring users...")
users.setup_users(self.chroot_dir, self.config.get("users", []))
print("Configuring UI...")
ui.setup_ui(self.chroot_dir, self.config.get('ui', {}))
print("Running post-install scripts...")
scripts.execute_scripts(self.chroot_dir, self.config.get('scripts', {}), "post_install")
print("Configuring security settings...")
security.setup_security(self.chroot_dir, self.config.get('security', {}))
print("Configuring startup scripts...")
scripts.execute_scripts(self.chroot_dir, self.config.get('scripts', {}), "startup", execute=False)
print("Building ISO...")
system_name = self.config.get('system_name', 'kiosk')
build_iso.build_iso(self.build_dir, self.config, self.output_dir)
print(f"ISO built and saved to: {self.output_dir}/{system_name}-{time.strftime('%Y%m%d')}.iso")
return True
except Exception as e:
print(f"Error building kiosk image: {e}", file=sys.stderr)
return False
finally:
self.cleanup()

76
src/python/scripts.py Normal file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
import os
import subprocess
def execute_script(chroot_dir, script_name, script_content, script_type, execute=True):
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "execute_single_script.sh")
cmd = [script_path, chroot_dir, script_name, script_content, script_type]
if not execute:
cmd.append("install_only")
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error executing script {script_name}: {str(e)}")
return False
def setup_startup_service(chroot_dir):
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "setup_startup_service.sh")
cmd = [script_path, chroot_dir, "startup"]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error setting up startup service: {str(e)}")
return False
def execute_scripts(chroot_dir, scripts, script_type, execute=True):
success_count = 0
scripts = scripts.get(script_type, [])
for script in scripts:
script_name = script.get('name')
script_content = script.get('content')
if not script_content or script_content == "null":
print(f"Skipping empty script: {script_name}")
continue
if execute_script(chroot_dir, script_name, script_content, script_type, execute):
success_count += 1
if script_type == "startup":
setup_startup_service(chroot_dir)
if execute:
print(f"{script_type} scripts execution completed ({success_count}/{len(scripts)} successful)")
else:
print(f"{script_type} scripts installation completed ({success_count}/{len(scripts)} successful)")
return 0

145
src/python/security.py Normal file
View File

@@ -0,0 +1,145 @@
#!/usr/bin/env python3
import os
import subprocess
def configure_firewall(chroot_dir, firewall_config):
try:
if not firewall_config:
print("Warning: No firewall configuration specified, disabling firewall")
enabled = "false"
allow_ports = ""
else:
enabled = "true" if firewall_config.get('enabled', False) else "false"
allow_ports = firewall_config.get('allow_ports', [])
if allow_ports and isinstance(allow_ports, list):
allow_ports = ",".join(map(str, allow_ports))
else:
allow_ports = ""
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "configure_firewall.sh")
cmd = [script_path, chroot_dir, enabled, allow_ports]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error configuring firewall: {str(e)}")
return False
def configure_ssh(chroot_dir, ssh_config):
try:
if not ssh_config:
print("Warning: No SSH configuration specified, disabling SSH")
enabled = "false"
allow_root_login = "false"
password_authentication = "false"
else:
enabled = "true" if ssh_config.get('enabled', False) else "false"
allow_root_login = "true" if ssh_config.get('allow_root_login', False) else "false"
password_authentication = "true" if ssh_config.get('password_authentication', True) else "false"
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "configure_ssh.sh")
cmd = [script_path, chroot_dir, enabled, allow_root_login, password_authentication]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error configuring SSH: {str(e)}")
return False
def set_file_permissions(chroot_dir, path, owner, group, mode):
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "set_file_permissions.sh")
cmd = [script_path, chroot_dir, path, owner, group, mode]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error setting file permissions for {path}: {str(e)}")
return False
def configure_file_permissions(chroot_dir, file_permissions_config):
try:
if not file_permissions_config or not isinstance(file_permissions_config, list):
print("No file permission configurations specified")
return True
success_count = 0
for perm in file_permissions_config:
path = perm.get('path')
owner = perm.get('owner', 'root')
group = perm.get('group', 'root')
mode = perm.get('mode', '0644')
if not path:
print("Warning: Missing path in file permission configuration, skipping")
continue
if set_file_permissions(chroot_dir, path, owner, group, mode):
success_count += 1
else:
print(f"Warning: Failed to set permissions for {path}")
print(f"File permissions configuration completed: {success_count}/{len(file_permissions_config)} successful")
return success_count == len(file_permissions_config)
except Exception as e:
print(f"Error configuring file permissions: {str(e)}")
return False
def setup_security(chroot_dir, security_config):
try:
if not security_config:
print("No security configuration provided, skipping security setup")
return True
success_count = 0
total_count = 0
total_count += 1
firewall_config = security_config.get('firewall')
if configure_firewall(chroot_dir, firewall_config):
success_count += 1
total_count += 1
ssh_config = security_config.get('ssh')
if configure_ssh(chroot_dir, ssh_config):
success_count += 1
total_count += 1
file_permissions_config = security_config.get('file_permissions')
if configure_file_permissions(chroot_dir, file_permissions_config):
success_count += 1
print(f"Security configuration completed: {success_count}/{total_count} components configured successfully")
return success_count == total_count
except Exception as e:
print(f"Error setting up security configuration: {str(e)}")
return False

96
src/python/software.py Normal file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
import os
import subprocess
def add_repository(chroot_dir, repo_name, repo_url, repo_key=None):
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "add_repository.sh")
cmd = [script_path, chroot_dir, repo_name, repo_url]
if repo_key:
cmd.append(repo_key)
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error adding repository {repo_name}: {str(e)}")
return False
def install_package(chroot_dir, package_name, package_version=None):
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "install_package.sh")
cmd = [script_path, chroot_dir, package_name]
if package_version:
cmd.append(package_version)
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error installing package {package_name}: {str(e)}")
return False
def setup_software(chroot_dir, software_config):
try:
repo_success = 0
repositories = software_config.get('repositories', [])
for repo in repositories:
repo_name = repo.get('name')
repo_url = repo.get('url')
repo_key = repo.get('key')
if not repo_name or not repo_url:
print(f"Skipping invalid repository: {repo}")
continue
if add_repository(chroot_dir, repo_name, repo_url, repo_key):
repo_success += 1
print("Updating package lists...")
update_cmd = ["chroot", chroot_dir, "apt-get", "update"]
try:
subprocess.run(update_cmd, check=False)
except Exception as e:
print(f"Warning: Failed to update package lists: {str(e)}")
package_success = 0
packages = software_config.get('packages', [])
for package in packages:
package_name = package.get('name')
package_version = package.get('version')
if not package_name:
print(f"Skipping invalid package: {package}")
continue
if install_package(chroot_dir, package_name, package_version):
package_success += 1
print(
f"Software configuration completed: {repo_success}/{len(repositories)} repositories and {package_success}/{len(packages)} packages configured successfully")
return 0
except Exception as e:
print(f"Error setting up software: {str(e)}")
return 1

177
src/python/system_config.py Normal file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env python3
import os
import subprocess
def configure_hostname(chroot_dir, hostname):
try:
if not hostname:
print("Warning: No hostname specified, using default 'kiosk'")
hostname = "kiosk"
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "configure_hostname.sh")
cmd = [script_path, chroot_dir, hostname]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error configuring hostname: {str(e)}")
return False
def configure_network(chroot_dir, network_config):
try:
if not network_config:
print("Warning: No network configuration specified, using DHCP")
network_type = "dhcp"
else:
network_type = network_config.get('type', 'dhcp')
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "configure_network.sh")
cmd = [script_path, chroot_dir, network_type]
if network_type == "static":
static_config = network_config.get('static_config', {})
ip_address = static_config.get('ip')
gateway = static_config.get('gateway')
if not ip_address or not gateway:
print("Error: Static network configuration requires IP address and gateway")
return False
cmd.append(ip_address)
cmd.append(gateway)
dns_servers = static_config.get('dns', [])
if dns_servers:
if isinstance(dns_servers, list):
cmd.append(','.join(dns_servers))
else:
cmd.append(dns_servers)
else:
cmd.append("")
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error configuring network: {str(e)}")
return False
def configure_updates(chroot_dir, updates_config):
try:
if not updates_config:
print("Warning: No updates configuration specified, disabling automatic updates")
automatic = "false"
schedule = ""
else:
automatic = "true" if updates_config.get('automatic', False) else "false"
schedule = updates_config.get('schedule', "0 2 * * *")
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "configure_updates.sh")
cmd = [script_path, chroot_dir, automatic]
if automatic == "true":
cmd.append(schedule)
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error configuring updates: {str(e)}")
return False
def configure_locale(chroot_dir, locale_config, timezone):
try:
if not locale_config:
print("Warning: No locale configuration specified, using defaults")
language = "en_US.UTF-8"
keyboard = "us"
else:
language = locale_config.get('language', "en_US.UTF-8")
keyboard = locale_config.get('keyboard', "us")
if not timezone:
print("Warning: No timezone specified, using default (UTC)")
timezone = "UTC"
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "configure_locale.sh")
cmd = [script_path, chroot_dir, language, keyboard, timezone]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error configuring locale: {str(e)}")
return False
def setup_system_config(chroot_dir, system_config):
try:
success_count = 0
total_count = 0
total_count += 1
hostname = system_config.get('hostname')
if configure_hostname(chroot_dir, hostname):
success_count += 1
total_count += 1
network_config = system_config.get('network')
if configure_network(chroot_dir, network_config):
success_count += 1
total_count += 1
updates_config = system_config.get('updates')
if configure_updates(chroot_dir, updates_config):
success_count += 1
total_count += 1
locale_config = system_config.get('locale')
timezone = system_config.get('timezone')
if configure_locale(chroot_dir, locale_config, timezone):
success_count += 1
print(f"System configuration completed: {success_count}/{total_count} components configured successfully")
return 0
except Exception as e:
print(f"Error setting up system configuration: {str(e)}")
return 1

82
src/python/ui.py Normal file
View File

@@ -0,0 +1,82 @@
#!/usr/bin/env python3
import os
import subprocess
def install_ui_packages(chroot_dir):
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "install_ui_packages.sh")
cmd = [script_path, chroot_dir]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error installing UI packages: {str(e)}")
return False
def configure_kiosk_mode(chroot_dir, kiosk_app, resolution=None, orientation=None):
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "configure_kiosk_mode.sh")
cmd = [script_path, chroot_dir, kiosk_app]
if resolution:
cmd.append(resolution)
else:
cmd.append("null")
if orientation:
cmd.append(str(orientation))
else:
cmd.append("null")
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in iter(process.stdout.readline, ''):
if not line:
break
print(line, end='')
return process.wait() == 0
except Exception as e:
print(f"Error configuring kiosk mode: {str(e)}")
return False
def setup_ui(chroot_dir, ui_config):
try:
if not install_ui_packages(chroot_dir):
print("Warning: Failed to install UI packages")
kiosk_config = ui_config.get('kiosk_mode', {})
display_config = ui_config.get('display', {})
if kiosk_config:
kiosk_app = kiosk_config.get('application')
resolution = display_config.get('resolution')
orientation = display_config.get('orientation', 0)
if kiosk_app:
if not configure_kiosk_mode(chroot_dir, kiosk_app, resolution, orientation):
print("Warning: Failed to configure kiosk mode")
else:
print("Warning: No kiosk application specified")
print("UI configuration completed successfully")
return 0
except Exception as e:
print(f"Error setting up UI: {str(e)}")
return 1

70
src/python/users.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
import os
import subprocess
def create_user_in_chroot(chroot_dir, username, password, groups=None, sudo=False):
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "configure_users.sh")
group_str = ""
if groups and groups != "null" and isinstance(groups, list) and groups:
group_str = ",".join(groups)
sudo_str = "true" if sudo else "false"
cmd = [script_path, chroot_dir, username, password, group_str, sudo_str, "false"]
process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if process.stdout:
print(process.stdout)
return process.returncode == 0
except Exception as e:
print(f"Error creating user {username}: {str(e)}")
return False
def configure_autologin(chroot_dir, username, password="changeme"):
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
bash_dir = os.path.join(os.path.dirname(script_dir), "bash")
script_path = os.path.join(bash_dir, "configure_users.sh")
cmd = [script_path, chroot_dir, username, password, "", "false", "true"]
process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if process.stdout:
print(process.stdout)
return process.returncode == 0
except Exception as e:
print(f"Error configuring autologin for {username}: {str(e)}")
return False
def setup_users(chroot_dir, users_list):
try:
for user in users_list:
username = user.get('name')
password = user.get('password', 'changeme')
groups = user.get('groups', [])
sudo = user.get('sudo', False)
autologin = user.get('autologin', False)
if not create_user_in_chroot(chroot_dir, username, password, groups, sudo):
print(f"Warning: Failed to create or configure user {username}")
continue
if autologin:
if not configure_autologin(chroot_dir, username, password):
print(f"Warning: Failed to configure autologin for user {username}")
return True
except Exception as e:
print(f"Error setting up users directly: {str(e)}")
return False

102
src/python/validate_config.py Executable file
View File

@@ -0,0 +1,102 @@
#!/usr/bin/env python3
import yaml
def load_config(config_path):
with open(config_path, 'r') as f:
return yaml.safe_load(f)
def validate_config(config):
if 'system_config' in config:
system_config = config['system_config']
if 'network' in system_config:
network_config = system_config['network']
if 'type' not in network_config:
print("Warning: Network type not specified, defaulting to 'dhcp'")
elif network_config['type'] not in ['dhcp', 'static']:
raise ValueError("Network type must be 'dhcp' or 'static'")
if network_config.get('type') == 'static':
if 'static_config' not in network_config:
raise ValueError("Static network configuration requires 'static_config' section")
static_config = network_config['static_config']
if 'ip' not in static_config:
raise ValueError("Static network configuration requires 'ip' address")
if 'gateway' not in static_config:
raise ValueError("Static network configuration requires 'gateway' address")
if 'updates' in system_config:
updates_config = system_config['updates']
if 'automatic' in updates_config and updates_config['automatic'] and 'schedule' not in updates_config:
print(
"Warning: Automatic updates enabled but no schedule specified, defaulting to '0 2 * * *' (2 AM daily)")
if 'locale' in system_config:
locale_config = system_config['locale']
if 'language' not in locale_config:
print("Warning: Locale language not specified, defaulting to 'en_US.UTF-8'")
if 'keyboard' not in locale_config:
print("Warning: Keyboard layout not specified, defaulting to 'us'")
if 'timezone' not in system_config:
print("Warning: Timezone not specified, defaulting to 'UTC'")
if 'security' in config:
security_config = config['security']
if 'firewall' in security_config:
firewall_config = security_config['firewall']
if 'enabled' not in firewall_config:
print("Warning: Firewall enabled status not specified, defaulting to disabled")
if 'allow_ports' in firewall_config and not isinstance(firewall_config['allow_ports'], list):
raise ValueError("Firewall allowed ports must be a list of integers")
if 'ssh' in security_config:
ssh_config = security_config['ssh']
if 'enabled' not in ssh_config:
print("Warning: SSH enabled status not specified, defaulting to disabled")
if 'file_permissions' in security_config:
file_permissions = security_config['file_permissions']
if not isinstance(file_permissions, list):
raise ValueError("File permissions must be a list")
for i, perm in enumerate(file_permissions):
if 'path' not in perm:
raise ValueError(f"File permission entry {i} is missing required 'path' field")
if 'users' not in config:
raise ValueError("No users defined in config")
if 'software' not in config:
raise ValueError("No software defined in config")
if 'ui' not in config:
raise ValueError("No UI configuration defined in config")
if 'scripts' in config:
scripts = config.get('scripts', {})
valid_script_types = ['pre_install', 'post_install', 'startup']
for script_type, scripts_list in scripts.items():
if script_type not in valid_script_types:
print(f"Warning: Unknown script type '{script_type}'. Valid types are: {', '.join(valid_script_types)}")
continue
if not isinstance(scripts_list, list):
raise ValueError(f"Scripts in '{script_type}' must be a list")
for i, script in enumerate(scripts_list):
if not isinstance(script, dict):
raise ValueError(f"Script {i} in '{script_type}' must be an object")
if 'name' not in script:
print(f"Warning: Script {i} in '{script_type}' has no name")
if 'content' not in script:
raise ValueError(f"Script '{script.get('name', i)}' in '{script_type}' has no content")
return True