diff --git a/.github/workflows/test-container.yml b/.github/workflows/test-container.yml new file mode 100644 index 0000000..e153734 --- /dev/null +++ b/.github/workflows/test-container.yml @@ -0,0 +1,25 @@ +name: Test Distribution Containers + +on: + push: + branches: + - main + - master + - develop + - "*" + pull_request: + +jobs: + test-unit: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Show Docker version + run: docker version + + - name: Run container tests + run: make test-container diff --git a/.gitignore b/.gitignore index f469422..1bc514a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ venv/ # Build artifacts dist/ -build/ +build/* *.egg-info/ # Editor files @@ -31,4 +31,11 @@ Thumbs.db # Ignore logs *.log -package-manager-* \ No newline at end of file +package-manager-* + +# debian +debian/package-manager/ +debian/debhelper-build-stamp +debian/files +debian/.debhelper/ +debian/package-manager.substvars \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 98dc2b2..125f012 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,87 +4,6 @@ ARG BASE_IMAGE=archlinux:latest FROM ${BASE_IMAGE} -# ------------------------------------------------------------ -# System base + conditional package tool installation -# -# Important: -# - We do NOT install Nix directly here via curl. -# - Nix is installed/initialized by init-nix.sh, which is invoked -# from the system packaging hooks (Arch .install, Debian postinst, -# RPM %post). -# ------------------------------------------------------------ -RUN set -e; \ - if [ -f /etc/os-release ]; then . /etc/os-release; else echo "No /etc/os-release found" && exit 1; fi; \ - echo "Detected base image: ${ID:-unknown} (like: ${ID_LIKE:-})"; \ - \ - if [ "$ID" = "arch" ]; then \ - pacman -Syu --noconfirm && \ - pacman -S --noconfirm --needed \ - base-devel \ - git \ - rsync \ - curl \ - ca-certificates \ - xz && \ - pacman -Scc --noconfirm; \ - elif [ "$ID" = "debian" ]; then \ - apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - build-essential \ - debhelper \ - dpkg-dev \ - git \ - rsync \ - bash \ - curl \ - ca-certificates \ - xz-utils && \ - rm -rf /var/lib/apt/lists/*; \ - elif [ "$ID" = "ubuntu" ]; then \ - apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - build-essential \ - debhelper \ - dpkg-dev \ - git \ - tzdata \ - lsb-release \ - rsync \ - bash \ - curl \ - ca-certificates \ - xz-utils && \ - rm -rf /var/lib/apt/lists/*; \ - elif [ "$ID" = "fedora" ]; then \ - dnf -y update && \ - dnf -y install \ - git \ - rsync \ - rpm-build \ - make \ - gcc \ - bash \ - curl \ - ca-certificates \ - xz && \ - dnf clean all; \ - elif [ "$ID" = "centos" ]; then \ - dnf -y update && \ - dnf -y install \ - git \ - rsync \ - rpm-build \ - make \ - gcc \ - bash \ - curl-minimal \ - ca-certificates \ - xz && \ - dnf clean all; \ - else \ - echo "Unsupported base image: ${ID}" && exit 1; \ - fi - # ------------------------------------------------------------ # Nix environment defaults # @@ -96,94 +15,38 @@ ENV NIX_CONFIG="experimental-features = nix-command flakes" # ------------------------------------------------------------ # Unprivileged user for Arch package build (makepkg) # ------------------------------------------------------------ -RUN useradd -m builder || true +RUN useradd -m aur_builder || true + +# ------------------------------------------------------------ +# Copy scripts and install distro dependencies +# ------------------------------------------------------------ +WORKDIR /build + +# Copy only scripts first so dependency installation can run early +COPY scripts/ scripts/ +RUN find scripts -type f -name '*.sh' -exec chmod +x {} \; + +# Install distro-specific build dependencies (and AUR builder on Arch) +RUN scripts/installation/run-dependencies.sh + +# ------------------------------------------------------------ +# Select distro-specific Docker entrypoint +# ------------------------------------------------------------ +# Docker entrypoint (distro-agnostic, nutzt run-package.sh) +# ------------------------------------------------------------ +COPY scripts/docker/entry.sh /usr/local/bin/docker-entry.sh +RUN chmod +x /usr/local/bin/docker-entry.sh # ------------------------------------------------------------ # Build and install distro-native package-manager package -# -# - Arch: PKGBUILD -> pacman -U -# - Debian: debhelper -> dpkg-buildpackage -> apt install ./package-manager_*.deb -# - Ubuntu: same as Debian -# - Fedora: rpmbuild -> dnf/dnf5/yum install package-manager-*.rpm -# - CentOS: rpmbuild -> dnf/yum install package-manager-*.rpm -# -# Nix is NOT manually installed here; it is handled by init-nix.sh. +# via Makefile `install` target (calls scripts/installation/run-package.sh) # ------------------------------------------------------------ -WORKDIR /build COPY . . +RUN find scripts -type f -name '*.sh' -exec chmod +x {} \; RUN set -e; \ - . /etc/os-release; \ - if [ "$ID" = "arch" ]; then \ - echo 'Building Arch package (makepkg --nodeps)...'; \ - chown -R builder:builder /build; \ - su builder -c "cd /build && rm -f package-manager-*.pkg.tar.* && makepkg --noconfirm --clean --nodeps"; \ - \ - echo 'Installing generated Arch package...'; \ - pacman -U --noconfirm package-manager-*.pkg.tar.*; \ - elif [ "$ID" = "debian" ] || [ "$ID" = "ubuntu" ]; then \ - echo 'Building Debian/Ubuntu package...'; \ - dpkg-buildpackage -us -uc -b; \ - \ - echo 'Installing generated DEB package...'; \ - apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y ./../package-manager_*.deb && \ - rm -rf /var/lib/apt/lists/*; \ - elif [ "$ID" = "fedora" ] || [ "$ID" = "centos" ]; then \ - echo 'Setting up rpmbuild dirs...'; \ - mkdir -p /root/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}; \ - \ - echo "Extracting version from package-manager.spec..."; \ - version=$(grep -E '^Version:' /build/package-manager.spec | awk '{print $2}'); \ - if [ -z "$version" ]; then echo 'ERROR: Version missing!' && exit 1; fi; \ - srcdir="package-manager-${version}"; \ - \ - echo "Preparing source tree for RPM: $srcdir"; \ - rm -rf "/tmp/$srcdir"; \ - mkdir -p "/tmp/$srcdir"; \ - cp -a /build/. "/tmp/$srcdir/"; \ - \ - echo "Creating source tarball: /root/rpmbuild/SOURCES/$srcdir.tar.gz"; \ - tar czf "/root/rpmbuild/SOURCES/$srcdir.tar.gz" -C /tmp "$srcdir"; \ - \ - echo 'Copying SPEC...'; \ - cp /build/package-manager.spec /root/rpmbuild/SPECS/; \ - \ - echo 'Running rpmbuild...'; \ - cd /root/rpmbuild/SPECS && rpmbuild -bb package-manager.spec; \ - \ - echo 'Installing generated RPM (local, offline)...'; \ - rpm_path=$(find /root/rpmbuild/RPMS -name "package-manager-*.rpm" | head -n1); \ - if [ -z "$rpm_path" ]; then echo 'ERROR: RPM not found!' && exit 1; fi; \ - \ - if command -v dnf5 >/dev/null 2>&1; then \ - echo 'Using dnf5 to install local RPM (no remote repos)...'; \ - if ! dnf5 install -y --disablerepo='*' "$rpm_path"; then \ - echo 'dnf5 failed, falling back to rpm -i --nodeps'; \ - rpm -i --nodeps "$rpm_path"; \ - fi; \ - elif command -v dnf >/dev/null 2>&1; then \ - echo 'Using dnf to install local RPM (no remote repos)...'; \ - if ! dnf install -y --disablerepo='*' "$rpm_path"; then \ - echo 'dnf failed, falling back to rpm -i --nodeps'; \ - rpm -i --nodeps "$rpm_path"; \ - fi; \ - elif command -v yum >/dev/null 2>&1; then \ - echo 'Using yum to install local RPM (no remote repos)...'; \ - if ! yum localinstall -y --disablerepo='*' "$rpm_path"; then \ - echo 'yum failed, falling back to rpm -i --nodeps'; \ - rpm -i --nodeps "$rpm_path"; \ - fi; \ - else \ - echo 'No dnf/dnf5/yum found, falling back to rpm -i --nodeps...'; \ - rpm -i --nodeps "$rpm_path"; \ - fi; \ - \ - rm -rf "/tmp/$srcdir"; \ - else \ - echo "Unsupported distro: ${ID}"; \ - exit 1; \ - fi; \ + echo "Building and installing package-manager via make install..."; \ + make install; \ rm -rf /build # ------------------------------------------------------------ @@ -191,8 +54,5 @@ RUN set -e; \ # ------------------------------------------------------------ WORKDIR /src -COPY scripts/docker-entry-dev.sh /usr/local/bin/docker-entry-dev.sh -RUN chmod +x /usr/local/bin/docker-entry-dev.sh - -ENTRYPOINT ["/usr/local/bin/docker-entry-dev.sh"] -CMD ["--help"] +ENTRYPOINT ["/usr/local/bin/docker-entry.sh"] +CMD ["pkgmgr", "--help"] diff --git a/Makefile b/Makefile index 32c769a..d4f347e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ -.PHONY: install setup uninstall aur_builder_setup \ - test build build-no-cache test-unit test-e2e test-integration +.PHONY: install setup uninstall \ + test build build-no-cache test-unit test-e2e test-integration \ + test-container # ------------------------------------------------------------ # Local Nix cache directories in the repo @@ -9,267 +10,72 @@ NIX_CACHE_VOLUME := pkgmgr_nix_cache # ------------------------------------------------------------ # Distro list and base images +# (kept for documentation/reference; actual build logic is in scripts/build) # ------------------------------------------------------------ DISTROS := arch debian ubuntu fedora centos +BASE_IMAGE_ARCH := archlinux:latest +BASE_IMAGE_DEBIAN := debian:stable-slim +BASE_IMAGE_UBUNTU := ubuntu:latest +BASE_IMAGE_FEDORA := fedora:latest +BASE_IMAGE_CENTOS := quay.io/centos/centos:stream9 -BASE_IMAGE_arch := archlinux:latest -BASE_IMAGE_debian := debian:stable-slim -BASE_IMAGE_ubuntu := ubuntu:latest -BASE_IMAGE_fedora := fedora:latest -BASE_IMAGE_centos := quay.io/centos/centos:stream9 - -# Helper to echo which image is used for which distro (purely informational) -define echo_build_info - @echo "Building image for distro '$(1)' with base image '$(2)'..." -endef +# Make them available in scripts +export DISTROS +export BASE_IMAGE_ARCH +export BASE_IMAGE_DEBIAN +export BASE_IMAGE_UBUNTU +export BASE_IMAGE_FEDORA +export BASE_IMAGE_CENTOS # ------------------------------------------------------------ -# PKGMGR setup (wrapper) +# PKGMGR setup (developer wrapper -> scripts/installation/main.sh) # ------------------------------------------------------------ -setup: install - @echo "Running pkgmgr setup via main.py..." - @if [ -x "$$HOME/.venvs/pkgmgr/bin/python" ]; then \ - echo "Using virtualenv Python at $$HOME/.venvs/pkgmgr/bin/python"; \ - "$$HOME/.venvs/pkgmgr/bin/python" main.py install; \ - else \ - echo "Virtualenv not found, falling back to system python3"; \ - python3 main.py install; \ - fi +setup: + @bash scripts/installation/main.sh # ------------------------------------------------------------ -# Docker build targets: build all images +# Docker build targets (delegated to scripts/build) # ------------------------------------------------------------ build-no-cache: - @for distro in $(DISTROS); do \ - case "$$distro" in \ - arch) base_image="$(BASE_IMAGE_arch)" ;; \ - debian) base_image="$(BASE_IMAGE_debian)" ;; \ - ubuntu) base_image="$(BASE_IMAGE_ubuntu)" ;; \ - fedora) base_image="$(BASE_IMAGE_fedora)" ;; \ - centos) base_image="$(BASE_IMAGE_centos)" ;; \ - *) echo "Unknown distro '$$distro'" >&2; exit 1 ;; \ - esac; \ - echo "Building test image 'package-manager-test-$$distro' with no cache (BASE_IMAGE=$$base_image)..."; \ - docker build --no-cache \ - --build-arg BASE_IMAGE="$$base_image" \ - -t "package-manager-test-$$distro" . || exit $$?; \ - done + @bash scripts/build/build-image-no-cache.sh build: - @for distro in $(DISTROS); do \ - case "$$distro" in \ - arch) base_image="$(BASE_IMAGE_arch)" ;; \ - debian) base_image="$(BASE_IMAGE_debian)" ;; \ - ubuntu) base_image="$(BASE_IMAGE_ubuntu)" ;; \ - fedora) base_image="$(BASE_IMAGE_fedora)" ;; \ - centos) base_image="$(BASE_IMAGE_centos)" ;; \ - *) echo "Unknown distro '$$distro'" >&2; exit 1 ;; \ - esac; \ - echo "Building test image 'package-manager-test-$$distro' (BASE_IMAGE=$$base_image)..."; \ - docker build \ - --build-arg BASE_IMAGE="$$base_image" \ - -t "package-manager-test-$$distro" . || exit $$?; \ - done - -build-arch: - @base_image="$(BASE_IMAGE_arch)"; \ - echo "Building test image 'package-manager-test-arch' (BASE_IMAGE=$$base_image)..."; \ - docker build \ - --build-arg BASE_IMAGE="$$base_image" \ - -t "package-manager-test-arch" . || exit $$?; + @bash scripts/build/build-image.sh # ------------------------------------------------------------ -# Test targets +# Test targets (delegated to scripts/test) # ------------------------------------------------------------ -# Unit tests: only in Arch container (fastest feedback), via Nix devShell -test-unit: build-arch - @echo "============================================================" - @echo ">>> Running UNIT tests in Arch container (via Nix devShell)" - @echo "============================================================" - docker run --rm \ - -v "$$(pwd):/src" \ - --workdir /src \ - --entrypoint bash \ - "package-manager-test-arch" \ - -c '\ - set -e; \ - if [ -f /etc/os-release ]; then . /etc/os-release; fi; \ - echo "Detected container distro: $${ID:-unknown} (like: $${ID_LIKE:-})"; \ - echo "Running Python unit tests (tests/unit) via nix develop..."; \ - git config --global --add safe.directory /src || true; \ - cd /src; \ - nix develop .#default --no-write-lock-file -c \ - python -m unittest discover \ - -s tests/unit \ - -t /src \ - -p "test_*.py"; \ - ' +test-unit: + @bash scripts/test/test-unit.sh -# Integration tests: also in Arch container, via Nix devShell -test-integration: build-arch - @echo "============================================================" - @echo ">>> Running INTEGRATION tests in Arch container (via Nix devShell)" - @echo "============================================================" - docker run --rm \ - -v "$$(pwd):/src" \ - --workdir /src \ - --entrypoint bash \ - "package-manager-test-arch" \ - -c '\ - set -e; \ - if [ -f /etc/os-release ]; then . /etc/os-release; fi; \ - echo "Detected container distro: $${ID:-unknown} (like: $${ID_LIKE:-})"; \ - echo "Running Python integration tests (tests/integration) via nix develop..."; \ - git config --global --add safe.directory /src || true; \ - cd /src; \ - nix develop .#default --no-write-lock-file -c \ - python -m unittest discover \ - -s tests/integration \ - -t /src \ - -p "test_*.py"; \ - ' +test-integration: + @bash scripts/test/test-integration.sh -# End-to-end tests: run in all distros via Nix devShell (tests/e2e) -test-e2e: build - @echo "Ensuring Docker Nix volumes exist (auto-created if missing)..." - @echo "Running E2E tests inside Nix devShell with cached store for all distros: $(DISTROS)" +test-e2e: + @bash scripts/test/test-e2e.sh - @for distro in $(DISTROS); do \ - echo "============================================================"; \ - echo ">>> Running E2E tests in container for distro: $$distro"; \ - echo "============================================================"; \ - # Only for Arch: mount /nix as volume, for others use image-installed Nix \ - if [ "$$distro" = "arch" ]; then \ - NIX_STORE_MOUNT="-v $(NIX_STORE_VOLUME):/nix"; \ - else \ - NIX_STORE_MOUNT=""; \ - fi; \ - docker run --rm \ - -v "$$(pwd):/src" \ - $$NIX_STORE_MOUNT \ - -v "$(NIX_CACHE_VOLUME):/root/.cache/nix" \ - --workdir /src \ - --entrypoint bash \ - "package-manager-test-$$distro" \ - -c '\ - set -e; \ - if [ -f /etc/os-release ]; then . /etc/os-release; fi; \ - echo "Detected container distro: $${ID:-unknown} (like: $${ID_LIKE:-})"; \ - echo "Preparing Nix environment..."; \ - if [ -f "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh" ]; then \ - . "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"; \ - fi; \ - if [ -f "$$HOME/.nix-profile/etc/profile.d/nix.sh" ]; then \ - . "$$HOME/.nix-profile/etc/profile.d/nix.sh"; \ - fi; \ - PATH="/nix/var/nix/profiles/default/bin:$$HOME/.nix-profile/bin:$$PATH"; \ - export PATH; \ - echo "PATH is now:"; \ - echo "$$PATH"; \ - NIX_CMD=""; \ - if command -v nix >/dev/null 2>&1; then \ - echo "Found nix on PATH:"; \ - command -v nix; \ - NIX_CMD="nix"; \ - else \ - echo "nix not found on PATH, scanning /nix/store for a nix binary..."; \ - for path in /nix/store/*-nix-*/bin/nix; do \ - if [ -x "$$path" ]; then \ - echo "Found nix binary at $$path"; \ - NIX_CMD="$$path"; \ - break; \ - fi; \ - done; \ - fi; \ - if [ -z "$$NIX_CMD" ]; then \ - echo "ERROR: nix binary not found anywhere – cannot run devShell"; \ - exit 1; \ - fi; \ - echo "Using Nix command: $$NIX_CMD"; \ - echo "Run E2E tests inside Nix devShell (tests/e2e)..."; \ - git config --global --add safe.directory /src || true; \ - cd /src; \ - "$$NIX_CMD" develop .#default --no-write-lock-file -c \ - python3 -m unittest discover \ - -s /src/tests/e2e \ - -p "test_*.py"; \ - ' || exit $$?; \ - done +test-container: + @bash scripts/test/test-container.sh + +# ------------------------------------------------------------ +# Build only missing container images +# ------------------------------------------------------------ +build-missing: + @bash scripts/build/build-image-missing.sh # Combined test target for local + CI (unit + e2e + integration) -test: build test-unit test-e2e test-integration +test: build-missing test-container test-unit test-e2e test-integration # ------------------------------------------------------------ -# Installer for host systems (original logic) +# System install (native packages, calls scripts/installation/run-package.sh) # ------------------------------------------------------------ install: - @if [ -n "$$IN_NIX_SHELL" ]; then \ - echo "Nix shell detected (IN_NIX_SHELL=1). Skipping venv/pip install – handled by Nix flake."; \ - else \ - echo "Making 'main.py' executable..."; \ - chmod +x main.py; \ - echo "Checking if global user virtual environment exists..."; \ - mkdir -p "$$HOME/.venvs"; \ - if [ ! -d "$$HOME/.venvs/pkgmgr" ]; then \ - echo "Creating global venv at $$HOME/.venvs/pkgmgr..."; \ - python3 -m venv "$$HOME/.venvs/pkgmgr"; \ - fi; \ - echo "Installing required Python packages into $$HOME/.venvs/pkgmgr..."; \ - "$$HOME/.venvs/pkgmgr/bin/python" -m ensurepip --upgrade; \ - "$$HOME/.venvs/pkgmgr/bin/pip" install --upgrade pip setuptools wheel; \ - echo "Looking for requirements.txt / _requirements.txt..."; \ - if [ -f requirements.txt ]; then \ - echo "Installing Python packages from requirements.txt..."; \ - "$$HOME/.venvs/pkgmgr/bin/pip" install -r requirements.txt; \ - elif [ -f _requirements.txt ]; then \ - echo "Installing Python packages from _requirements.txt..."; \ - "$$HOME/.venvs/pkgmgr/bin/pip" install -r _requirements.txt; \ - else \ - echo "No requirements.txt or _requirements.txt found, skipping dependency installation."; \ - fi; \ - echo "Ensuring $$HOME/.bashrc and $$HOME/.zshrc exist..."; \ - touch "$$HOME/.bashrc" "$$HOME/.zshrc"; \ - echo "Ensuring automatic activation of $$HOME/.venvs/pkgmgr for this user..."; \ - for rc in "$$HOME/.bashrc" "$$HOME/.zshrc"; do \ - rc_line='if [ -d "$${HOME}/.venvs/pkgmgr" ]; then . "$${HOME}/.venvs/pkgmgr/bin/activate"; if [ -n "$${PS1:-}" ]; then echo "Global Python virtual environment '\''~/.venvs/pkgmgr'\'' activated."; fi; fi'; \ - grep -qxF "$${rc_line}" "$$rc" || echo "$${rc_line}" >> "$$rc"; \ - done; \ - echo "Arch/Manjaro detection and optional AUR setup..."; \ - if command -v pacman >/dev/null 2>&1; then \ - $(MAKE) aur_builder_setup; \ - else \ - echo "Not Arch-based (no pacman). Skipping aur_builder/yay setup."; \ - fi; \ - echo "Installation complete. Please restart your shell (or 'exec bash' or 'exec zsh') for the changes to take effect."; \ - fi - -# ------------------------------------------------------------ -# AUR builder setup — only on Arch/Manjaro -# ------------------------------------------------------------ -aur_builder_setup: - @echo "Setting up aur_builder and yay (Arch/Manjaro)..." - @sudo pacman -Syu --noconfirm - @sudo pacman -S --needed --noconfirm base-devel git sudo - @if ! getent group aur_builder >/dev/null; then sudo groupadd -r aur_builder; fi - @if ! id -u aur_builder >/dev/null 2>&1; then sudo useradd -m -r -g aur_builder -s /bin/bash aur_builder; fi - @echo '%aur_builder ALL=(ALL) NOPASSWD: /usr/bin/pacman' | sudo tee /etc/sudoers.d/aur_builder >/dev/null - @sudo chmod 0440 /etc/sudoers.d/aur_builder - @if ! sudo -u aur_builder bash -lc 'command -v yay >/dev/null'; then \ - sudo -u aur_builder bash -lc 'cd ~ && rm -rf yay && git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si --noconfirm'; \ - else \ - echo "yay already installed."; \ - fi - @echo "aur_builder/yay setup complete." + @echo "Building and installing distro-native package-manager for this system..." + @bash scripts/installation/run-package.sh # ------------------------------------------------------------ # Uninstall target # ------------------------------------------------------------ uninstall: - @echo "Removing global user virtual environment if it exists..." - @rm -rf "$$HOME/.venvs/pkgmgr" - @echo "Cleaning up $$HOME/.bashrc and $$HOME/.zshrc entries..." - @for rc in "$$HOME/.bashrc" "$$HOME/.zshrc"; do \ - sed -i '/\.venvs\/pkgmgr\/bin\/activate"; if \[ -n "\$${PS1:-}" \]; then echo "Global Python virtual environment '\''~\/\.venvs\/pkgmgr'\'' activated."; fi; fi/d' "$$rc"; \ - done - @echo "Uninstallation complete. Please restart your shell (or 'exec bash' or 'exec zsh') for the changes to fully apply." + @bash scripts/uninstall.sh diff --git a/scripts/build/build-image-missing.sh b/scripts/build/build-image-missing.sh new file mode 100755 index 0000000..3194961 --- /dev/null +++ b/scripts/build/build-image-missing.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/resolve-base-image.sh" + +echo "============================================================" +echo ">>> Building ONLY missing container images" +echo "============================================================" + +for distro in $DISTROS; do + IMAGE="package-manager-test-$distro" + BASE_IMAGE="$(resolve_base_image "$distro")" + + if docker image inspect "$IMAGE" >/dev/null 2>&1; then + echo "[build-missing] Image already exists: $IMAGE (skipping)" + continue + fi + + echo + echo "------------------------------------------------------------" + echo "[build-missing] Building missing image: $IMAGE" + echo "BASE_IMAGE = $BASE_IMAGE" + echo "------------------------------------------------------------" + + docker build \ + --build-arg BASE_IMAGE="$BASE_IMAGE" \ + -t "$IMAGE" \ + . +done + +echo +echo "============================================================" +echo ">>> build-missing: Done" +echo "============================================================" diff --git a/scripts/build/build-image-no-cache.sh b/scripts/build/build-image-no-cache.sh new file mode 100755 index 0000000..66ed058 --- /dev/null +++ b/scripts/build/build-image-no-cache.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/resolve-base-image.sh" + +for distro in $DISTROS; do + base_image="$(resolve_base_image "$distro")" + + echo ">>> Building test image for distro '$distro' with NO CACHE (BASE_IMAGE=$base_image)..." + + docker build \ + --no-cache \ + --build-arg BASE_IMAGE="$base_image" \ + -t "package-manager-test-$distro" \ + . +done diff --git a/scripts/build/build-image.sh b/scripts/build/build-image.sh new file mode 100755 index 0000000..f3b373c --- /dev/null +++ b/scripts/build/build-image.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/resolve-base-image.sh" + +for distro in $DISTROS; do + base_image="$(resolve_base_image "$distro")" + + echo ">>> Building test image for distro '$distro' (BASE_IMAGE=$base_image)..." + + docker build \ + --build-arg BASE_IMAGE="$base_image" \ + -t "package-manager-test-$distro" \ + . +done diff --git a/scripts/build/resolve-base-image.sh b/scripts/build/resolve-base-image.sh new file mode 100755 index 0000000..e02075f --- /dev/null +++ b/scripts/build/resolve-base-image.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +resolve_base_image() { + local distro="$1" + + case "$distro" in + arch) echo "$BASE_IMAGE_ARCH" ;; + debian) echo "$BASE_IMAGE_DEBIAN" ;; + ubuntu) echo "$BASE_IMAGE_UBUNTU" ;; + fedora) echo "$BASE_IMAGE_FEDORA" ;; + centos) echo "$BASE_IMAGE_CENTOS" ;; + *) + echo "ERROR: Unknown distro '$distro'" >&2 + exit 1 + ;; + esac +} diff --git a/scripts/docker-entry-dev.sh b/scripts/docker-entry-dev.sh deleted file mode 100644 index e0c9dc0..0000000 --- a/scripts/docker-entry-dev.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "[entry] Using /src as working tree for package-manager..." -cd /src - -# Optional: altes Paket entfernen -echo "[entry] Removing existing 'package-manager' Arch package (if installed)..." -pacman -Rns --noconfirm package-manager || true - -# Build-Owner richtig setzen (falls /src vom Host kommt) -echo "[entry] Fixing ownership of /src for user 'builder'..." -chown -R builder:builder /src - -echo "[entry] Rebuilding Arch package from /src as user 'builder'..." -su builder -c "cd /src && makepkg -s --noconfirm --clean" - -echo "[entry] Installing freshly built package-manager-*.pkg.tar.*..." -pacman -U --noconfirm /src/package-manager-*.pkg.tar.* - -echo "[entry] Handing off to pkgmgr with args: $*" -exec pkgmgr "$@" diff --git a/scripts/docker/entry.sh b/scripts/docker/entry.sh new file mode 100755 index 0000000..c7c3c13 --- /dev/null +++ b/scripts/docker/entry.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "[docker] Starting package-manager container" + +# Distro-Info nur für Logging +if [[ -f /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release + echo "[docker] Detected distro: ${ID:-unknown} (like: ${ID_LIKE:-})" +fi + +# Wir arbeiten immer aus /src (vom Host gemountet) +echo "[docker] Using /src as working directory" +cd /src + +# ------------------------------------------------------------ +# DEV-Mode: aus dem aktuellen /src heraus Paket bauen/installieren +# ------------------------------------------------------------ +if [[ "${PKGMGR_DEV:-0}" == "1" ]]; then + echo "[docker] DEV mode enabled (PKGMGR_DEV=1)" + echo "[docker] Rebuilding package-manager from /src via scripts/installation/run-package.sh..." + + if [[ -x scripts/installation/run-package.sh ]]; then + bash scripts/installation/run-package.sh + else + echo "[docker] ERROR: scripts/installation/run-package.sh not found or not executable" + exit 1 + fi +fi + +# ------------------------------------------------------------ +# Hand-off zu pkgmgr / beliebigem Kommando +# ------------------------------------------------------------ +if [[ $# -eq 0 ]]; then + echo "[docker] No arguments provided. Showing pkgmgr help..." + exec pkgmgr --help +else + echo "[docker] Executing command: $*" + exec "$@" +fi diff --git a/scripts/init-nix.sh b/scripts/init-nix.sh old mode 100644 new mode 100755 index 4ad7086..c13f374 --- a/scripts/init-nix.sh +++ b/scripts/init-nix.sh @@ -1,44 +1,208 @@ #!/usr/bin/env bash set -euo pipefail -echo ">>> Initializing Nix environment for package-manager..." +echo "[init-nix] Starting Nix initialization..." -# 1. /nix store -if [ ! -d /nix ]; then - echo ">>> Creating /nix store directory" - mkdir -m 0755 /nix - chown root:root /nix +# --------------------------------------------------------------------------- +# Helper: detect whether we are inside a container (Docker/Podman/etc.) +# --------------------------------------------------------------------------- +is_container() { + # Docker / Podman markers + if [[ -f /.dockerenv ]] || [[ -f /run/.containerenv ]]; then + return 0 + fi + + # cgroup hints + if grep -qiE 'docker|container|podman|lxc' /proc/1/cgroup 2>/dev/null; then + return 0 + fi + + # Environment variable used by some runtimes + if [[ -n "${container:-}" ]]; then + return 0 + fi + + return 1 +} + +# --------------------------------------------------------------------------- +# Helper: ensure Nix binaries are on PATH (multi-user or single-user) +# --------------------------------------------------------------------------- +ensure_nix_on_path() { + # Multi-user profile (daemon install) + if [[ -x /nix/var/nix/profiles/default/bin/nix ]]; then + export PATH="/nix/var/nix/profiles/default/bin:${PATH}" + fi + + # Single-user profile (current user) + if [[ -x "${HOME}/.nix-profile/bin/nix" ]]; then + export PATH="${HOME}/.nix-profile/bin:${PATH}" + fi + + # Single-user profile for dedicated "nix" user (container case) + if [[ -x /home/nix/.nix-profile/bin/nix ]]; then + export PATH="/home/nix/.nix-profile/bin:${PATH}" + fi +} + +# --------------------------------------------------------------------------- +# Fast path: Nix already available +# --------------------------------------------------------------------------- +if command -v nix >/dev/null 2>&1; then + echo "[init-nix] Nix already available on PATH: $(command -v nix)" + exit 0 fi -# 2. Enable nix-daemon if available -if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files | grep -q nix-daemon.service; then - echo ">>> Enabling nix-daemon.service" - systemctl enable --now nix-daemon.service 2>/dev/null || true +ensure_nix_on_path + +if command -v nix >/dev/null 2>&1; then + echo "[init-nix] Nix found after adjusting PATH: $(command -v nix)" + exit 0 +fi + +echo "[init-nix] Nix not found, starting installation logic..." + +IN_CONTAINER=0 +if is_container; then + IN_CONTAINER=1 + echo "[init-nix] Detected container environment." else - echo ">>> Warning: nix-daemon.service not found or systemctl not available." + echo "[init-nix] No container detected." fi -# 3. Ensure nix-users group -if ! getent group nix-users >/dev/null 2>&1; then - echo ">>> Creating nix-users group" - groupadd -r nix-users 2>/dev/null || true -fi +# --------------------------------------------------------------------------- +# Container + root: install Nix as dedicated "nix" user (single-user) +# --------------------------------------------------------------------------- +if [[ "${IN_CONTAINER}" -eq 1 && "${EUID:-0}" -eq 0 ]]; then + echo "[init-nix] Running as root inside a container – using dedicated 'nix' user." -# 4. Add users to nix-users (best-effort) -if command -v loginctl >/dev/null 2>&1; then - for user in $(loginctl list-users | awk 'NR>1 {print $2}'); do - if id "$user" >/dev/null 2>&1; then - echo ">>> Adding user '$user' to nix-users" - usermod -aG nix-users "$user" 2>/dev/null || true + # Ensure nixbld group (required by Nix) + if ! getent group nixbld >/dev/null 2>&1; then + echo "[init-nix] Creating group 'nixbld'..." + groupadd -r nixbld + fi + + # Ensure Nix build users (nixbld1..nixbld10) as members of nixbld + for i in $(seq 1 10); do + if ! id "nixbld$i" >/dev/null 2>&1; then + echo "[init-nix] Creating build user nixbld$i..." + # -r: system account, -g: primary group, -G: supplementary (ensures membership is listed) + useradd -r -g nixbld -G nixbld -s /usr/sbin/nologin "nixbld$i" fi done -elif command -v logname >/dev/null 2>&1; then - USERNAME="$(logname 2>/dev/null || true)" - if [ -n "$USERNAME" ] && id "$USERNAME" >/dev/null 2>&1; then - echo ">>> Adding user '$USERNAME' to nix-users" - usermod -aG nix-users "$USERNAME" 2>/dev/null || true + + # Ensure "nix" user (home at /home/nix) + if ! id nix >/dev/null 2>&1; then + echo "[init-nix] Creating user 'nix'..." + useradd -m -r -g nixbld -s /usr/bin/bash nix + fi + + # Create /nix directory and hand it to nix user (prevents installer sudo prompt) + if [[ ! -d /nix ]]; then + echo "[init-nix] Creating /nix with owner nix:nixbld..." + mkdir -m 0755 /nix + chown nix:nixbld /nix + fi + + # Run Nix single-user installer as "nix" + echo "[init-nix] Installing Nix as user 'nix' (single-user, --no-daemon)..." + if command -v sudo >/dev/null 2>&1; then + sudo -u nix bash -lc 'sh <(curl -L https://nixos.org/nix/install) --no-daemon' + else + su - nix -c 'sh <(curl -L https://nixos.org/nix/install) --no-daemon' + fi + + # After installation, expose nix to root via PATH and symlink + ensure_nix_on_path + + if [[ -x /home/nix/.nix-profile/bin/nix ]]; then + if [[ ! -e /usr/local/bin/nix ]]; then + echo "[init-nix] Creating /usr/local/bin/nix symlink -> /home/nix/.nix-profile/bin/nix" + ln -s /home/nix/.nix-profile/bin/nix /usr/local/bin/nix + fi + fi + + ensure_nix_on_path + + if command -v nix >/dev/null 2>&1; then + echo "[init-nix] Nix successfully installed (container mode) at: $(command -v nix)" + else + echo "[init-nix] WARNING: Nix installation finished in container, but 'nix' is still not on PATH." + fi + + # Optionally add PATH hints to /etc/profile (best effort) + if [[ -w /etc/profile ]]; then + if ! grep -q 'Nix profiles' /etc/profile 2>/dev/null; then + cat <<'EOF' >> /etc/profile + +# Nix profiles (added by package-manager init-nix.sh) +if [ -d /nix/var/nix/profiles/default/bin ]; then + PATH="/nix/var/nix/profiles/default/bin:$PATH" +fi +if [ -d "$HOME/.nix-profile/bin" ]; then + PATH="$HOME/.nix-profile/bin:$PATH" +fi +EOF + echo "[init-nix] Appended Nix PATH setup to /etc/profile (container mode)." + fi + fi + + echo "[init-nix] Nix initialization complete (container root mode)." + exit 0 +fi + +# --------------------------------------------------------------------------- +# Non-container or non-root container: normal installer paths +# --------------------------------------------------------------------------- +if [[ "${IN_CONTAINER}" -eq 0 ]]; then + # Real host + if command -v systemctl >/dev/null 2>&1; then + echo "[init-nix] Host with systemd – using multi-user install (--daemon)." + sh <(curl -L https://nixos.org/nix/install) --daemon + else + if [[ "${EUID:-0}" -eq 0 ]]; then + echo "[init-nix] WARNING: Running as root without systemd on host." + echo "[init-nix] Falling back to single-user install (--no-daemon), but this is not recommended." + sh <(curl -L https://nixos.org/nix/install) --no-daemon + else + echo "[init-nix] Non-root host without systemd – using single-user install (--no-daemon)." + sh <(curl -L https://nixos.org/nix/install) --no-daemon + fi + fi +else + # Container, but not root (rare) + echo "[init-nix] Container as non-root user – using single-user install (--no-daemon)." + sh <(curl -L https://nixos.org/nix/install) --no-daemon +fi + +# --------------------------------------------------------------------------- +# After installation: fix PATH (runtime + shell profiles) +# --------------------------------------------------------------------------- +ensure_nix_on_path + +if ! command -v nix >/dev/null 2>&1; then + echo "[init-nix] WARNING: Nix installation finished, but 'nix' is still not on PATH." + echo "[init-nix] You may need to source your shell profile manually." + exit 0 +fi + +echo "[init-nix] Nix successfully installed at: $(command -v nix)" + +# Update global /etc/profile if writable (helps especially on minimal systems) +if [[ -w /etc/profile ]]; then + if ! grep -q 'Nix profiles' /etc/profile 2>/dev/null; then + cat <<'EOF' >> /etc/profile + +# Nix profiles (added by package-manager init-nix.sh) +if [ -d /nix/var/nix/profiles/default/bin ]; then + PATH="/nix/var/nix/profiles/default/bin:$PATH" +fi +if [ -d "$HOME/.nix-profile/bin" ]; then + PATH="$HOME/.nix-profile/bin:$PATH" +fi +EOF + echo "[init-nix] Appended Nix PATH setup to /etc/profile" fi fi -echo ">>> Nix initialization complete." -echo ">>> You may need to log out and log back in to activate group membership." +echo "[init-nix] Nix initialization complete." diff --git a/scripts/installation/arch/aur-builder-setup.sh b/scripts/installation/arch/aur-builder-setup.sh new file mode 100755 index 0000000..52c6b4a --- /dev/null +++ b/scripts/installation/arch/aur-builder-setup.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ------------------------------------------------------------ +# aur-builder-setup.sh +# +# Setup helper for an 'aur_builder' user and yay on Arch-based +# systems. Intended for host usage and can also be used in +# containers if desired. +# ------------------------------------------------------------ + +echo "[aur-builder-setup] Checking for pacman..." +if ! command -v pacman >/dev/null 2>&1; then + echo "[aur-builder-setup] pacman not found – this is not an Arch-based system. Skipping." + exit 0 +fi + +if [[ "${EUID:-0}" -ne 0 ]]; then + ROOT_CMD="sudo" +else + ROOT_CMD="" +fi + +echo "[aur-builder-setup] Installing base-devel, git, sudo..." +${ROOT_CMD} pacman -Syu --noconfirm +${ROOT_CMD} pacman -S --needed --noconfirm base-devel git sudo + +echo "[aur-builder-setup] Ensuring aur_builder group/user..." +if ! getent group aur_builder >/dev/null 2>&1; then + ${ROOT_CMD} groupadd -r aur_builder +fi + +if ! id -u aur_builder >/dev/null 2>&1; then + ${ROOT_CMD} useradd -m -r -g aur_builder -s /bin/bash aur_builder +fi + +echo "[aur-builder-setup] Configuring sudoers for aur_builder..." +${ROOT_CMD} bash -c "echo '%aur_builder ALL=(ALL) NOPASSWD: /usr/bin/pacman' > /etc/sudoers.d/aur_builder" +${ROOT_CMD} chmod 0440 /etc/sudoers.d/aur_builder + +if command -v sudo >/dev/null 2>&1; then + RUN_AS_AUR=(sudo -u aur_builder bash -lc) +else + RUN_AS_AUR=(su - aur_builder -c) +fi + +echo "[aur-builder-setup] Ensuring yay is installed for aur_builder..." +if ! "${RUN_AS_AUR[@]}" 'command -v yay >/dev/null 2>&1'; then + "${RUN_AS_AUR[@]}" 'cd ~ && rm -rf yay && git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si --noconfirm' +else + echo "[aur-builder-setup] yay already installed." +fi + +echo "[aur-builder-setup] Done." diff --git a/scripts/installation/arch/dependencies.sh b/scripts/installation/arch/dependencies.sh new file mode 100755 index 0000000..da9bd40 --- /dev/null +++ b/scripts/installation/arch/dependencies.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "[arch/dependencies] Installing Arch build dependencies..." + +pacman -Syu --noconfirm +pacman -S --noconfirm --needed \ + base-devel \ + git \ + rsync \ + curl \ + ca-certificates \ + xz + +pacman -Scc --noconfirm + +# Always run AUR builder setup for Arch +AUR_SETUP="${SCRIPT_DIR}/aur-builder-setup.sh" + +if [[ ! -x "${AUR_SETUP}" ]]; then + echo "[arch/dependencies] ERROR: AUR builder setup script not found or not executable: ${AUR_SETUP}" + exit 1 +fi + +echo "[arch/dependencies] Running AUR builder setup..." +bash "${AUR_SETUP}" + +echo "[arch/dependencies] Done." diff --git a/scripts/installation/arch/package.sh b/scripts/installation/arch/package.sh new file mode 100755 index 0000000..bf646f3 --- /dev/null +++ b/scripts/installation/arch/package.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[arch/package] Building Arch package (makepkg --nodeps)..." + +if id aur_builder >/dev/null 2>&1; then + echo "[arch/package] Using 'aur_builder' user for makepkg..." + chown -R aur_builder:aur_builder "$(pwd)" + su aur_builder -c "cd '$(pwd)' && rm -f package-manager-*.pkg.tar.* && makepkg --noconfirm --clean --nodeps" +else + echo "[arch/package] WARNING: user 'aur_builder' not found, running makepkg as current user..." + rm -f package-manager-*.pkg.tar.* + makepkg --noconfirm --clean --nodeps +fi + +echo "[arch/package] Installing generated Arch package..." +pacman -U --noconfirm package-manager-*.pkg.tar.* + +echo "[arch/package] Done." diff --git a/scripts/installation/centos/dependencies.sh b/scripts/installation/centos/dependencies.sh new file mode 100755 index 0000000..bc9aab1 --- /dev/null +++ b/scripts/installation/centos/dependencies.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[centos/dependencies] Installing CentOS build dependencies..." + +dnf -y update +dnf -y install \ + git \ + rsync \ + rpm-build \ + make \ + gcc \ + bash \ + curl-minimal \ + ca-certificates \ + xz + +dnf clean all + +echo "[centos/dependencies] Done." diff --git a/scripts/installation/centos/package.sh b/scripts/installation/centos/package.sh new file mode 100755 index 0000000..6447a7e --- /dev/null +++ b/scripts/installation/centos/package.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[centos/package] Setting up rpmbuild directories..." +mkdir -p /root/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + +echo "[centos/package] Extracting version from package-manager.spec..." +version="$(grep -E '^Version:' package-manager.spec | awk '{print $2}')" +if [[ -z "${version}" ]]; then + echo "ERROR: Version missing!" + exit 1 +fi + +srcdir="package-manager-${version}" +echo "[centos/package] Preparing source tree: ${srcdir}" +rm -rf "/tmp/${srcdir}" +mkdir -p "/tmp/${srcdir}" +cp -a . "/tmp/${srcdir}/" + +echo "[centos/package] Creating source tarball..." +tar czf "/root/rpmbuild/SOURCES/${srcdir}.tar.gz" -C /tmp "${srcdir}" + +echo "[centos/package] Copying SPEC..." +cp package-manager.spec /root/rpmbuild/SPECS/ + +echo "[centos/package] Running rpmbuild..." +cd /root/rpmbuild/SPECS +rpmbuild -bb package-manager.spec + +echo "[centos/package] Installing generated RPM (local, offline, forced reinstall)..." +rpm_path="$(find /root/rpmbuild/RPMS -name 'package-manager-*.rpm' | head -n1)" +if [[ -z "${rpm_path}" ]]; then + echo "ERROR: RPM not found!" + exit 1 +fi + +# ------------------------------------------------------------ +# Forced reinstall, always overwrite old version +# ------------------------------------------------------------ +echo "[centos/package] Forcing reinstall via rpm -Uvh --replacepkgs --force" +rpm -Uvh --replacepkgs --force "${rpm_path}" + +# Keep structure: remove temp directory afterwards +rm -rf "/tmp/${srcdir}" + +echo "[centos/package] Done." diff --git a/scripts/installation/debian/dependencies.sh b/scripts/installation/debian/dependencies.sh new file mode 100755 index 0000000..7f41ba6 --- /dev/null +++ b/scripts/installation/debian/dependencies.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[debian/dependencies] Installing Debian build dependencies..." + +apt-get update +DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + build-essential \ + debhelper \ + dpkg-dev \ + git \ + rsync \ + bash \ + curl \ + ca-certificates \ + xz-utils + +rm -rf /var/lib/apt/lists/* + +echo "[debian/dependencies] Done." diff --git a/scripts/installation/debian/package.sh b/scripts/installation/debian/package.sh new file mode 100755 index 0000000..b9c56d9 --- /dev/null +++ b/scripts/installation/debian/package.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[debian/package] Building Debian package..." + +dpkg-buildpackage -us -uc -b + +echo "[debian/package] Installing generated DEB package..." +apt-get update +DEBIAN_FRONTEND=noninteractive apt-get install -y ./../package-manager_*.deb +rm -rf /var/lib/apt/lists/* + +echo "[debian/package] Done." diff --git a/scripts/installation/fedora/dependencies.sh b/scripts/installation/fedora/dependencies.sh new file mode 100755 index 0000000..90d7488 --- /dev/null +++ b/scripts/installation/fedora/dependencies.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[fedora/dependencies] Installing Fedora build dependencies..." + +dnf -y update +dnf -y install \ + git \ + rsync \ + rpm-build \ + make \ + gcc \ + bash \ + curl \ + ca-certificates \ + python3 \ + xz + +dnf clean all + +echo "[fedora/dependencies] Done." diff --git a/scripts/installation/fedora/package.sh b/scripts/installation/fedora/package.sh new file mode 100755 index 0000000..69d15ef --- /dev/null +++ b/scripts/installation/fedora/package.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[fedora/package] Setting up rpmbuild directories..." +mkdir -p /root/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + +echo "[fedora/package] Extracting version from package-manager.spec..." +version="$(grep -E '^Version:' package-manager.spec | awk '{print $2}')" +if [[ -z "${version}" ]]; then + echo "ERROR: Version missing!" + exit 1 +fi + +srcdir="package-manager-${version}" +echo "[fedora/package] Preparing source tree: ${srcdir}" +rm -rf "/tmp/${srcdir}" +mkdir -p "/tmp/${srcdir}" +cp -a . "/tmp/${srcdir}/" + +echo "[fedora/package] Creating source tarball..." +tar czf "/root/rpmbuild/SOURCES/${srcdir}.tar.gz" -C /tmp "${srcdir}" + +echo "[fedora/package] Copying SPEC..." +cp package-manager.spec /root/rpmbuild/SPECS/ + +echo "[fedora/package] Running rpmbuild..." +cd /root/rpmbuild/SPECS +rpmbuild -bb package-manager.spec + +echo "[fedora/package] Installing generated RPM (local, offline, forced reinstall)..." +rpm_path="$(find /root/rpmbuild/RPMS -name 'package-manager-*.rpm' | head -n1)" +if [[ -z "${rpm_path}" ]]; then + echo "ERROR: RPM not found!" + exit 1 +fi + +# Always force (re)install the freshly built RPM, even if the same +# version is already installed. This is what we want in dev/test containers. +rpm -Uvh --replacepkgs --force "${rpm_path}" + +rm -rf "/tmp/${srcdir}" + +echo "[fedora/package] Done." diff --git a/scripts/installation/lib.sh b/scripts/installation/lib.sh new file mode 100755 index 0000000..9838647 --- /dev/null +++ b/scripts/installation/lib.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +detect_os_id() { + if [[ -f /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release + echo "${ID:-unknown}" + else + echo "unknown" + fi +} diff --git a/scripts/installation/main.sh b/scripts/installation/main.sh new file mode 100755 index 0000000..0a9e469 --- /dev/null +++ b/scripts/installation/main.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ------------------------------------------------------------ +# main.sh +# +# Developer setup entrypoint. +# +# Responsibilities: +# - If inside a Nix shell (IN_NIX_SHELL=1): +# * Skip venv creation and dependency installation +# * Run `python3 main.py install` +# - Otherwise: +# * Create ~/.venvs/pkgmgr virtual environment if missing +# * Install Python dependencies into that venv +# * Append auto-activation to ~/.bashrc and ~/.zshrc +# * Run `main.py install` using the venv Python +# ------------------------------------------------------------ + +echo "[installation/main] Starting developer setup..." + +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "${PROJECT_ROOT}" + +VENV_DIR="${HOME}/.venvs/pkgmgr" +RC_LINE='if [ -d "${HOME}/.venvs/pkgmgr" ]; then . "${HOME}/.venvs/pkgmgr/bin/activate"; if [ -n "${PS1:-}" ]; then echo "Global Python virtual environment '\''~/.venvs/pkgmgr'\'' activated."; fi; fi' + +# ------------------------------------------------------------ +# Nix shell mode: do not touch venv, only run main.py install +# ------------------------------------------------------------ +if [[ -n "${IN_NIX_SHELL:-}" ]]; then + echo "[installation/main] Nix shell detected (IN_NIX_SHELL=1)." + echo "[installation/main] Skipping virtualenv creation and dependency installation." + echo "[installation/main] Running main.py install via system python3..." + python3 main.py install + echo "[installation/main] Developer setup finished (Nix mode)." + exit 0 +fi + +# ------------------------------------------------------------ +# Normal host mode: create/update venv and run main.py install +# ------------------------------------------------------------ + +echo "[installation/main] Ensuring main.py is executable..." +chmod +x main.py || true + +echo "[installation/main] Ensuring global virtualenv root: ${HOME}/.venvs" +mkdir -p "${HOME}/.venvs" + +if [[ ! -d "${VENV_DIR}" ]]; then + echo "[installation/main] Creating virtual environment at: ${VENV_DIR}" + python3 -m venv "${VENV_DIR}" +else + echo "[installation/main] Virtual environment already exists at: ${VENV_DIR}" +fi + +echo "[installation/main] Installing Python tooling into venv..." +"${VENV_DIR}/bin/python" -m ensurepip --upgrade +"${VENV_DIR}/bin/pip" install --upgrade pip setuptools wheel + +if [[ -f "requirements.txt" ]]; then + echo "[installation/main] Installing dependencies from requirements.txt..." + "${VENV_DIR}/bin/pip" install -r requirements.txt +elif [[ -f "_requirements.txt" ]]; then + echo "[installation/main] Installing dependencies from _requirements.txt..." + "${VENV_DIR}/bin/pip" install -r _requirements.txt +else + echo "[installation/main] No requirements.txt or _requirements.txt found. Skipping dependency installation." +fi + +echo "[installation/main] Ensuring ~/.bashrc and ~/.zshrc exist..." +touch "${HOME}/.bashrc" "${HOME}/.zshrc" + +echo "[installation/main] Ensuring venv auto-activation is present in shell rc files..." +for rc in "${HOME}/.bashrc" "${HOME}/.zshrc"; do + if ! grep -qxF "${RC_LINE}" "$rc"; then + echo "${RC_LINE}" >> "$rc" + echo "[installation/main] Appended auto-activation to $rc" + else + echo "[installation/main] Auto-activation already present in $rc" + fi +done + +echo "[installation/main] Running main.py install via venv Python..." +"${VENV_DIR}/bin/python" main.py install + +echo +echo "[installation/main] Developer setup complete." +echo "Restart your shell (or run 'exec bash' or 'exec zsh') to activate the environment." diff --git a/scripts/installation/run-dependencies.sh b/scripts/installation/run-dependencies.sh new file mode 100755 index 0000000..41187b7 --- /dev/null +++ b/scripts/installation/run-dependencies.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=/dev/null +source "${SCRIPT_DIR}/lib.sh" + +OS_ID="$(detect_os_id)" + +echo "[run-dependencies] Detected OS: ${OS_ID}" + +case "${OS_ID}" in + arch|debian|ubuntu|fedora|centos) + DEP_SCRIPT="${SCRIPT_DIR}/${OS_ID}/dependencies.sh" + ;; + *) + echo "[run-dependencies] Unsupported OS: ${OS_ID}" + exit 1 + ;; +esac + +if [[ ! -f "${DEP_SCRIPT}" ]]; then + echo "[run-dependencies] Dependency script not found: ${DEP_SCRIPT}" + exit 1 +fi + +echo "[run-dependencies] Executing: ${DEP_SCRIPT}" +exec bash "${DEP_SCRIPT}" diff --git a/scripts/installation/run-package.sh b/scripts/installation/run-package.sh new file mode 100755 index 0000000..4b4d593 --- /dev/null +++ b/scripts/installation/run-package.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=/dev/null +source "${SCRIPT_DIR}/lib.sh" + +OS_ID="$(detect_os_id)" + +echo "[run-package] Detected OS: ${OS_ID}" + +case "${OS_ID}" in + arch|debian|ubuntu|fedora|centos) + PKG_SCRIPT="${SCRIPT_DIR}/${OS_ID}/package.sh" + ;; + *) + echo "[run-package] Unsupported OS: ${OS_ID}" + exit 1 + ;; +esac + +if [[ ! -f "${PKG_SCRIPT}" ]]; then + echo "[run-package] Package script not found: ${PKG_SCRIPT}" + exit 1 +fi + +echo "[run-package] Executing: ${PKG_SCRIPT}" +exec bash "${PKG_SCRIPT}" diff --git a/scripts/installation/ubuntu/dependencies.sh b/scripts/installation/ubuntu/dependencies.sh new file mode 100755 index 0000000..642b9a3 --- /dev/null +++ b/scripts/installation/ubuntu/dependencies.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[ubuntu/dependencies] Installing Ubuntu build dependencies..." + +apt-get update +DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + build-essential \ + debhelper \ + dpkg-dev \ + git \ + tzdata \ + lsb-release \ + rsync \ + bash \ + curl \ + ca-certificates \ + xz-utils + +rm -rf /var/lib/apt/lists/* + +echo "[ubuntu/dependencies] Done." diff --git a/scripts/installation/ubuntu/package.sh b/scripts/installation/ubuntu/package.sh new file mode 100755 index 0000000..7ae3b97 --- /dev/null +++ b/scripts/installation/ubuntu/package.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[ubuntu/package] Building Ubuntu (Debian-style) package..." + +dpkg-buildpackage -us -uc -b + +echo "[ubuntu/package] Installing generated DEB package..." +apt-get update +DEBIAN_FRONTEND=noninteractive apt-get install -y ./../package-manager_*.deb +rm -rf /var/lib/apt/lists/* + +echo "[ubuntu/package] Done." diff --git a/scripts/pkgmgr-wrapper.sh b/scripts/pkgmgr-wrapper.sh old mode 100644 new mode 100755 index 52179d7..68fa573 --- a/scripts/pkgmgr-wrapper.sh +++ b/scripts/pkgmgr-wrapper.sh @@ -1,10 +1,40 @@ #!/usr/bin/env bash set -euo pipefail +# Ensure NIX_CONFIG has our defaults if not already set if [[ -z "${NIX_CONFIG:-}" ]]; then export NIX_CONFIG="experimental-features = nix-command flakes" fi FLAKE_DIR="/usr/lib/package-manager" -exec nix run "${FLAKE_DIR}#pkgmgr" -- "$@" +# ------------------------------------------------------------ +# Try to ensure that "nix" is on PATH +# ------------------------------------------------------------ +if ! command -v nix >/dev/null 2>&1; then + # Common locations for Nix installations + CANDIDATES=( + "/nix/var/nix/profiles/default/bin/nix" + "${HOME:-/root}/.nix-profile/bin/nix" + ) + + for candidate in "${CANDIDATES[@]}"; do + if [[ -x "$candidate" ]]; then + # Prepend the directory of the candidate to PATH + PATH="$(dirname "$candidate"):${PATH}" + export PATH + break + fi + done +fi + +# ------------------------------------------------------------ +# Primary (and only) path: use Nix flake if available +# ------------------------------------------------------------ +if command -v nix >/dev/null 2>&1; then + exec nix run "${FLAKE_DIR}#pkgmgr" -- "$@" +fi + +echo "[pkgmgr-wrapper] ERROR: 'nix' binary not found on PATH." +echo "[pkgmgr-wrapper] Nix is required to run pkgmgr (no Python fallback)." +exit 1 diff --git a/scripts/test/test-common.sh b/scripts/test/test-common.sh new file mode 100755 index 0000000..5b93fa5 --- /dev/null +++ b/scripts/test/test-common.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +detect_container_distro() { + if [[ -f /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release + echo "${ID:-unknown}" + else + echo "unknown" + fi +} diff --git a/scripts/test/test-container.sh b/scripts/test/test-container.sh new file mode 100755 index 0000000..372eb26 --- /dev/null +++ b/scripts/test/test-container.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "============================================================" +echo ">>> Running sanity test: verifying test containers start" +echo "============================================================" + +for distro in $DISTROS; do + IMAGE="package-manager-test-$distro" + + echo + echo "------------------------------------------------------------" + echo ">>> Testing container: $IMAGE" + echo "------------------------------------------------------------" + + echo "[test-container] Running: docker run --rm --entrypoint pkgmgr $IMAGE --help" + echo + + # Run the command and capture the output + if OUTPUT=$(docker run --rm \ + -e PKGMGR_DEV=1 \ + -v "$(pwd):/src" \ + -v "pkgmgr_nix_cache:/root/.cache/nix" \ + "$IMAGE" 2>&1); then + echo "$OUTPUT" + echo + echo "[test-container] SUCCESS: $IMAGE responded to 'pkgmgr --help'" + + else + echo "$OUTPUT" + echo + echo "[test-container] ERROR: $IMAGE failed to run 'pkgmgr --help'" + exit 1 + fi +done + +echo +echo "============================================================" +echo ">>> All containers passed the sanity check" +echo "============================================================" diff --git a/scripts/test/test-e2e.sh b/scripts/test/test-e2e.sh new file mode 100755 index 0000000..256f2ff --- /dev/null +++ b/scripts/test/test-e2e.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo ">>> Running E2E tests in all distros: $DISTROS" + +for distro in $DISTROS; do + echo "============================================================" + echo ">>> Running E2E tests: $distro" + echo "============================================================" + + MOUNT_NIX="" + if [[ "$distro" == "arch" ]]; then + MOUNT_NIX="-v pkgmgr_nix_store:/nix" + fi + + docker run --rm \ + -v "$(pwd):/src" \ + $MOUNT_NIX \ + -v "pkgmgr_nix_cache:/root/.cache/nix" \ + -e PKGMGR_DEV=1 \ + --workdir /src \ + --entrypoint bash \ + "package-manager-test-$distro" \ + -c ' + set -e; + + if [ -f /etc/os-release ]; then + . /etc/os-release; + fi; + + echo "Running tests inside distro: $ID"; + + # Try to load nix environment + if [ -f "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh" ]; then + . "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"; + fi + + if [ -f "$HOME/.nix-profile/etc/profile.d/nix.sh" ]; then + . "$HOME/.nix-profile/etc/profile.d/nix.sh"; + fi + + PATH="/nix/var/nix/profiles/default/bin:$HOME/.nix-profile/bin:$PATH"; + + command -v nix >/dev/null || { + echo "ERROR: nix not found."; + exit 1; + } + + git config --global --add safe.directory /src || true; + + nix develop .#default --no-write-lock-file -c \ + python3 -m unittest discover \ + -s /src/tests/e2e \ + -p "test_*.py"; + ' +done diff --git a/scripts/test/test-integration.sh b/scripts/test/test-integration.sh new file mode 100755 index 0000000..54f016a --- /dev/null +++ b/scripts/test/test-integration.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "============================================================" +echo ">>> Running INTEGRATION tests in Arch container" +echo "============================================================" + +docker run --rm \ + -v "$(pwd):/src" \ + -v "pkgmgr_nix_cache:/root/.cache/nix" \ + --workdir /src \ + -e PKGMGR_DEV=1 \ + --entrypoint bash \ + "package-manager-test-arch" \ + -c ' + set -e; + git config --global --add safe.directory /src || true; + nix develop .#default --no-write-lock-file -c \ + python -m unittest discover \ + -s tests/integration \ + -t /src \ + -p "test_*.py"; + ' diff --git a/scripts/test/test-unit.sh b/scripts/test/test-unit.sh new file mode 100755 index 0000000..f4a10c2 --- /dev/null +++ b/scripts/test/test-unit.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "============================================================" +echo ">>> Running UNIT tests in Arch container" +echo "============================================================" + +docker run --rm \ + -v "$(pwd):/src" \ + -v "pkgmgr_nix_cache:/root/.cache/nix" \ + --workdir /src \ + -e PKGMGR_DEV=1 \ + --entrypoint bash \ + "package-manager-test-arch" \ + -c ' + set -e; + git config --global --add safe.directory /src || true; + nix develop .#default --no-write-lock-file -c \ + python -m unittest discover \ + -s tests/unit \ + -t /src \ + -p "test_*.py"; + ' diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100755 index 0000000..e4d7daf --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[uninstall] Starting pkgmgr uninstall..." + +VENV_DIR="${HOME}/.venvs/pkgmgr" + +# ------------------------------------------------------------ +# Remove virtual environment +# ------------------------------------------------------------ +echo "[uninstall] Removing global user virtual environment if it exists..." +if [[ -d "$VENV_DIR" ]]; then + rm -rf "$VENV_DIR" + echo "[uninstall] Removed: $VENV_DIR" +else + echo "[uninstall] No venv found at: $VENV_DIR" +fi + +# ------------------------------------------------------------ +# Remove auto-activation lines from shell RC files +# ------------------------------------------------------------ +RC_PATTERN='\.venvs\/pkgmgr\/bin\/activate"; if \[ -n "\$${PS1:-}" \]; then echo "Global Python virtual environment '\''~\/\.venvs\/pkgmgr'\'' activated."; fi; fi' + +echo "[uninstall] Cleaning up ~/.bashrc and ~/.zshrc entries..." +for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do + if [[ -f "$rc" ]]; then + sed -i "/$RC_PATTERN/d" "$rc" + echo "[uninstall] Cleaned $rc" + else + echo "[uninstall] File not found: $rc (skipped)" + fi +done + +echo "[uninstall] Done. Restart your shell (or run 'exec bash' or 'exec zsh') to apply changes."