Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
103f49c8f6 | ||
|
|
f5d428950e | ||
|
|
b40787ffc5 | ||
|
|
0482a7f88d | ||
|
|
8c127cc45a | ||
|
|
2761e829cb | ||
|
|
d0c01b6955 | ||
|
|
b2421c9b84 | ||
|
|
f950bb493c | ||
|
|
fb0b81954d | ||
|
|
b9b4c3fa59 | ||
|
|
3642f92776 | ||
|
|
8f38edde67 | ||
|
|
5875441b23 | ||
|
|
9190f0d901 | ||
|
|
f227734185 | ||
|
|
c7ef77559c | ||
|
|
2385601ed5 | ||
|
|
ac5ae95369 | ||
|
|
31f7f47fe2 | ||
|
|
c8bf1c91ad | ||
|
|
f2caa68e3d | ||
|
|
03c232c308 | ||
|
|
e882e17737 | ||
|
|
b9edcf7101 | ||
|
|
8b8ebf329f | ||
|
|
9598c17ea0 | ||
|
|
67bd358e12 | ||
|
|
340c1700dc | ||
|
|
0dfbaa0f6b | ||
|
|
08ab9fb142 |
66
.github/workflows/publish-containers.yml
vendored
Normal file
66
.github/workflows/publish-containers.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: Publish container images (GHCR)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Mark stable commit"]
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository (with tags)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
|
||||||
|
- name: Checkout workflow_run commit and refresh tags
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git checkout -f "${{ github.event.workflow_run.head_sha }}"
|
||||||
|
git fetch --tags --force
|
||||||
|
git tag --list 'stable' 'v*' --sort=version:refname | tail -n 20
|
||||||
|
|
||||||
|
- name: Compute version and stable flag
|
||||||
|
id: info
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
SHA="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
|
V_TAG="$(git tag --points-at "${SHA}" --list 'v*' | sort -V | tail -n1)"
|
||||||
|
[[ -n "$V_TAG" ]] || { echo "No version tag found"; exit 1; }
|
||||||
|
VERSION="${V_TAG#v}"
|
||||||
|
|
||||||
|
STABLE_SHA="$(git rev-parse -q --verify refs/tags/stable^{commit} 2>/dev/null || true)"
|
||||||
|
IS_STABLE=false
|
||||||
|
[[ -n "${STABLE_SHA}" && "${STABLE_SHA}" == "${SHA}" ]] && IS_STABLE=true
|
||||||
|
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "is_stable=${IS_STABLE}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
use: true
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Publish all images
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
OWNER="${{ github.repository_owner }}" \
|
||||||
|
VERSION="${{ steps.info.outputs.version }}" \
|
||||||
|
IS_STABLE="${{ steps.info.outputs.is_stable }}" \
|
||||||
|
bash scripts/build/publish.sh
|
||||||
38
.github/workflows/test-virgin-root.yml
vendored
38
.github/workflows/test-virgin-root.yml
vendored
@@ -7,6 +7,10 @@ jobs:
|
|||||||
test-virgin-root:
|
test-virgin-root:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
distro: [arch, debian, ubuntu, fedora, centos]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -15,44 +19,38 @@ jobs:
|
|||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
run: docker version
|
run: docker version
|
||||||
|
|
||||||
- name: Virgin Arch pkgmgr flake test (root)
|
# 🔹 BUILD virgin image if missing
|
||||||
|
- name: Build virgin container (${{ matrix.distro }})
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
distro="${{ matrix.distro }}" make build-missing-virgin
|
||||||
|
|
||||||
echo ">>> Starting virgin ArchLinux container test (root, with shared caches)..."
|
# 🔹 RUN test inside virgin image
|
||||||
|
- name: Virgin ${{ matrix.distro }} pkgmgr test (root)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
-v "$PWD":/src \
|
-v "$PWD":/src \
|
||||||
-v pkgmgr_repos:/root/Repositories \
|
-v pkgmgr_repos:/root/Repositories \
|
||||||
-v pkgmgr_pip_cache:/root/.cache/pip \
|
-v pkgmgr_pip_cache:/root/.cache/pip \
|
||||||
-w /src \
|
-w /src \
|
||||||
archlinux:latest \
|
"pkgmgr-${{ matrix.distro }}-virgin" \
|
||||||
bash -lc '
|
bash -lc '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo ">>> Updating and upgrading Arch system..."
|
git config --global --add safe.directory /src
|
||||||
pacman -Syu --noconfirm git python python-pip nix >/dev/null
|
|
||||||
|
|
||||||
echo ">>> Creating isolated virtual environment for pkgmgr..."
|
make install
|
||||||
python -m venv /tmp/pkgmgr-venv
|
make setup
|
||||||
|
|
||||||
echo ">>> Activating virtual environment..."
|
. "$HOME/.venvs/pkgmgr/bin/activate"
|
||||||
source /tmp/pkgmgr-venv/bin/activate
|
|
||||||
|
|
||||||
echo ">>> Upgrading pip (cached)..."
|
|
||||||
python -m pip install --upgrade pip >/dev/null
|
|
||||||
|
|
||||||
echo ">>> Installing pkgmgr from current source tree (cached pip)..."
|
|
||||||
python -m pip install /src >/dev/null
|
|
||||||
|
|
||||||
echo ">>> Enabling Nix experimental features..."
|
|
||||||
export NIX_CONFIG="experimental-features = nix-command flakes"
|
export NIX_CONFIG="experimental-features = nix-command flakes"
|
||||||
|
|
||||||
echo ">>> Running: pkgmgr update pkgmgr --clone-mode shallow --no-verification"
|
|
||||||
pkgmgr update pkgmgr --clone-mode shallow --no-verification
|
pkgmgr update pkgmgr --clone-mode shallow --no-verification
|
||||||
|
|
||||||
echo ">>> Running: pkgmgr version pkgmgr"
|
|
||||||
pkgmgr version pkgmgr
|
pkgmgr version pkgmgr
|
||||||
|
|
||||||
echo ">>> Virgin Arch (root) test completed successfully."
|
echo ">>> Running Nix-based: nix run .#pkgmgr -- version pkgmgr"
|
||||||
|
nix run /src#pkgmgr -- version pkgmgr
|
||||||
'
|
'
|
||||||
|
|||||||
60
.github/workflows/test-virgin-user.yml
vendored
60
.github/workflows/test-virgin-user.yml
vendored
@@ -7,6 +7,10 @@ jobs:
|
|||||||
test-virgin-user:
|
test-virgin-user:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
distro: [arch, debian, ubuntu, fedora, centos]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -15,59 +19,47 @@ jobs:
|
|||||||
- name: Show Docker version
|
- name: Show Docker version
|
||||||
run: docker version
|
run: docker version
|
||||||
|
|
||||||
- name: Virgin Arch pkgmgr user test (non-root with sudo)
|
# 🔹 BUILD virgin image if missing
|
||||||
|
- name: Build virgin container (${{ matrix.distro }})
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
distro="${{ matrix.distro }}" make build-missing-virgin
|
||||||
|
|
||||||
|
# 🔹 RUN test inside virgin image as non-root
|
||||||
|
- name: Virgin ${{ matrix.distro }} pkgmgr test (user)
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo ">>> Starting virgin ArchLinux container test (non-root user with sudo)..."
|
|
||||||
|
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
-v "$PWD":/src \
|
-v "$PWD":/src \
|
||||||
archlinux:latest \
|
-w /src \
|
||||||
|
"pkgmgr-${{ matrix.distro }}-virgin" \
|
||||||
bash -lc '
|
bash -lc '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo ">>> [root] Updating and upgrading Arch system..."
|
make install
|
||||||
pacman -Syu --noconfirm git python python-pip sudo base-devel debugedit
|
|
||||||
|
|
||||||
echo ">>> [root] Creating non-root user dev..."
|
|
||||||
useradd -m dev
|
useradd -m dev
|
||||||
|
|
||||||
echo ">>> [root] Allowing passwordless sudo for dev..."
|
|
||||||
echo "dev ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/dev
|
echo "dev ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/dev
|
||||||
chmod 0440 /etc/sudoers.d/dev
|
chmod 0440 /etc/sudoers.d/dev
|
||||||
|
|
||||||
echo ">>> [root] Adjusting ownership of /src for dev..."
|
|
||||||
chown -R dev:dev /src
|
chown -R dev:dev /src
|
||||||
|
|
||||||
echo ">>> [root] Running pkgmgr flow as non-root user dev..."
|
mkdir -p /nix/store /nix/var/nix /nix/var/log/nix /nix/var/nix/profiles
|
||||||
sudo -u dev env PKGMGR_DISABLE_NIX_FLAKE_INSTALLER=1 bash -lc "
|
chown -R dev:dev /nix
|
||||||
|
chmod 0755 /nix
|
||||||
|
chmod 1777 /nix/store
|
||||||
|
|
||||||
|
sudo -H -u dev env HOME=/home/dev PKGMGR_DISABLE_NIX_FLAKE_INSTALLER=1 bash -lc "
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd /src
|
cd /src
|
||||||
|
|
||||||
echo \">>> [dev] Using user: \$(whoami)\"
|
make setup-venv
|
||||||
echo \">>> [dev] Running scripts/installation/main.sh...\"
|
|
||||||
bash scripts/installation/main.sh
|
|
||||||
|
|
||||||
echo \">>> [dev] Activating venv...\"
|
|
||||||
. \"\$HOME/.venvs/pkgmgr/bin/activate\"
|
. \"\$HOME/.venvs/pkgmgr/bin/activate\"
|
||||||
|
|
||||||
echo \">>> [dev] Installing pkgmgr into venv via pip...\"
|
|
||||||
python -m pip install /src >/dev/null
|
|
||||||
|
|
||||||
echo \">>> [dev] PKGMGR_DISABLE_NIX_FLAKE_INSTALLER=\$PKGMGR_DISABLE_NIX_FLAKE_INSTALLER\"
|
|
||||||
echo \">>> [dev] Updating managed repo package-manager via pkgmgr...\"
|
|
||||||
pkgmgr update pkgmgr --clone-mode shallow --no-verification
|
|
||||||
|
|
||||||
echo \">>> [dev] PATH:\"
|
|
||||||
echo \"\$PATH\"
|
|
||||||
|
|
||||||
echo \">>> [dev] which pkgmgr:\"
|
|
||||||
which pkgmgr || echo \">>> [dev] pkgmgr not found in PATH\"
|
|
||||||
|
|
||||||
echo \">>> [dev] Running: pkgmgr version pkgmgr\"
|
|
||||||
pkgmgr version pkgmgr
|
pkgmgr version pkgmgr
|
||||||
"
|
|
||||||
|
|
||||||
echo ">>> [root] Container flow finished."
|
export NIX_REMOTE=local
|
||||||
|
export NIX_CONFIG=\"experimental-features = nix-command flakes\"
|
||||||
|
nix run /src#pkgmgr -- version pkgmgr
|
||||||
|
"
|
||||||
'
|
'
|
||||||
|
|||||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,3 +1,34 @@
|
|||||||
|
## [1.4.1] - 2025-12-12
|
||||||
|
|
||||||
|
* Fixed (#1) stable release container publishing
|
||||||
|
|
||||||
|
|
||||||
|
## [1.4.0] - 2025-12-12
|
||||||
|
|
||||||
|
* **Docker Container Building**
|
||||||
|
|
||||||
|
* New official container images are automatically published on each release.
|
||||||
|
* Images are available per distribution and as a default Arch-based image.
|
||||||
|
* Stable releases now provide an additional `stable` container tag.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.3.1] - 2025-12-12
|
||||||
|
|
||||||
|
* Updated documentation with better run and installation instructions
|
||||||
|
|
||||||
|
|
||||||
|
## [1.3.0] - 2025-12-12
|
||||||
|
|
||||||
|
* **Minor release – Stability & CI hardening**
|
||||||
|
|
||||||
|
* Stabilized Nix resolution and global symlink handling across Arch, CentOS, Debian, and Ubuntu
|
||||||
|
* Ensured Nix works reliably in CI, sudo, login, and non-login shells without overriding distro-managed paths
|
||||||
|
* Improved error handling and deterministic behavior for non-root environments
|
||||||
|
* Refactored Docker and CI workflows for reproducible multi-distro virgin tests
|
||||||
|
* Made E2E tests more realistic by executing real CLI commands
|
||||||
|
* Fixed Python compatibility and missing dependencies on affected distros
|
||||||
|
|
||||||
|
|
||||||
## [1.2.1] - 2025-12-12
|
## [1.2.1] - 2025-12-12
|
||||||
|
|
||||||
* **Changed**
|
* **Changed**
|
||||||
|
|||||||
81
Dockerfile
81
Dockerfile
@@ -1,61 +1,58 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Base image selector — overridden by Makefile
|
# Base image selector — overridden by build args / Makefile
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
ARG BASE_IMAGE
|
ARG BASE_IMAGE
|
||||||
FROM ${BASE_IMAGE}
|
|
||||||
|
|
||||||
RUN echo "BASE_IMAGE=${BASE_IMAGE}" && \
|
# ============================================================
|
||||||
cat /etc/os-release || true
|
# Target: virgin
|
||||||
|
# - installs distro deps (incl. make)
|
||||||
|
# - no pkgmgr build
|
||||||
|
# - no entrypoint
|
||||||
|
# ============================================================
|
||||||
|
FROM ${BASE_IMAGE} AS virgin
|
||||||
|
SHELL ["/bin/bash", "-lc"]
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
RUN echo "BASE_IMAGE=${BASE_IMAGE}" && cat /etc/os-release || true
|
||||||
# Nix environment defaults
|
|
||||||
#
|
|
||||||
# Nix itself is installed by your system packages (via init-nix.sh).
|
|
||||||
# Here we only define default configuration options.
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
ENV NIX_CONFIG="experimental-features = nix-command flakes"
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# Unprivileged user for Arch package build (makepkg)
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
RUN useradd -m aur_builder || true
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# Copy scripts and install distro dependencies
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
# Copy only scripts first so dependency installation can run early
|
# Copy scripts first so dependency installation can be cached
|
||||||
COPY scripts/ scripts/
|
COPY scripts/installation/ scripts/installation/
|
||||||
RUN find scripts -type f -name '*.sh' -exec chmod +x {} \;
|
|
||||||
|
|
||||||
# Install distro-specific build dependencies (and AUR builder on Arch)
|
# Install distro-specific build dependencies (including make)
|
||||||
RUN scripts/installation/run-dependencies.sh
|
RUN bash scripts/installation/dependencies.sh
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# Virgin default
|
||||||
# Select distro-specific Docker entrypoint
|
CMD ["bash"]
|
||||||
# ------------------------------------------------------------
|
|
||||||
# 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
|
# ============================================================
|
||||||
# via Makefile `install` target (calls scripts/installation/run-package.sh)
|
# Target: full
|
||||||
# ------------------------------------------------------------
|
# - inherits from virgin
|
||||||
|
# - builds + installs pkgmgr
|
||||||
|
# - sets entrypoint + default cmd
|
||||||
|
# ============================================================
|
||||||
|
FROM virgin AS full
|
||||||
|
|
||||||
|
# Nix environment defaults (only config; nix itself comes from deps/install flow)
|
||||||
|
ENV NIX_CONFIG="experimental-features = nix-command flakes"
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy full repository for build
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN find scripts -type f -name '*.sh' -exec chmod +x {} \;
|
|
||||||
|
|
||||||
RUN set -e; \
|
# Build and install distro-native package-manager package
|
||||||
|
RUN set -euo pipefail; \
|
||||||
echo "Building and installing package-manager via make install..."; \
|
echo "Building and installing package-manager via make install..."; \
|
||||||
make install; \
|
make install; \
|
||||||
rm -rf /build
|
cd /; rm -rf /build
|
||||||
|
|
||||||
|
# Entry point
|
||||||
|
COPY scripts/docker/entry.sh /usr/local/bin/docker-entry.sh
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# Runtime working directory and dev entrypoint
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/docker-entry.sh"]
|
ENTRYPOINT ["/usr/local/bin/docker-entry.sh"]
|
||||||
CMD ["pkgmgr", "--help"]
|
CMD ["pkgmgr", "--help"]
|
||||||
|
|||||||
57
Makefile
57
Makefile
@@ -1,6 +1,8 @@
|
|||||||
.PHONY: install setup uninstall \
|
.PHONY: install uninstall \
|
||||||
test build build-no-cache build-no-cache-all build-missing delete-volumes purge \
|
build build-no-cache build-no-cache-all build-missing \
|
||||||
test-unit test-e2e test-integration test-env-virtual test-env-nix
|
delete-volumes purge \
|
||||||
|
test test-unit test-e2e test-integration test-env-virtual test-env-nix \
|
||||||
|
setup setup-venv setup-nix
|
||||||
|
|
||||||
# Distro
|
# Distro
|
||||||
# Options: arch debian ubuntu fedora centos
|
# Options: arch debian ubuntu fedora centos
|
||||||
@@ -28,21 +30,46 @@ export BASE_IMAGE_CENTOS
|
|||||||
# PYthon Unittest Pattern
|
# PYthon Unittest Pattern
|
||||||
TEST_PATTERN := test_*.py
|
TEST_PATTERN := test_*.py
|
||||||
export TEST_PATTERN
|
export TEST_PATTERN
|
||||||
|
export PYTHONPATH := src
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# PKGMGR setup (developer wrapper -> scripts/installation/main.sh)
|
# System install
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
setup:
|
install:
|
||||||
@bash scripts/installation/main.sh
|
@echo "Building and installing distro-native package-manager for this system..."
|
||||||
|
@bash scripts/installation/init.sh
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# PKGMGR setup
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
# Default: keep current auto-detection behavior
|
||||||
|
setup: setup-nix setup-venv
|
||||||
|
|
||||||
|
# Explicit: developer setup (Python venv + shell RC + install)
|
||||||
|
setup-venv: setup-nix
|
||||||
|
@bash scripts/setup/venv.sh
|
||||||
|
|
||||||
|
# Explicit: Nix shell mode (no venv, no RC changes)
|
||||||
|
setup-nix:
|
||||||
|
@bash scripts/setup/nix.sh
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Docker build targets (delegated to scripts/build)
|
# Docker build targets (delegated to scripts/build)
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
build:
|
build:
|
||||||
@bash scripts/build/build-image.sh
|
@bash scripts/build/image.sh --target virgin
|
||||||
|
@bash scripts/build/image.sh
|
||||||
|
|
||||||
|
build-missing-virgin:
|
||||||
|
@bash scripts/build/image.sh --target virgin --missing
|
||||||
|
|
||||||
|
build-missing: build-missing-virgin
|
||||||
|
@bash scripts/build/image.sh --missing
|
||||||
|
|
||||||
build-no-cache:
|
build-no-cache:
|
||||||
@bash scripts/build/build-image-no-cache.sh
|
@bash scripts/build/image.sh --target virgin --no-cache
|
||||||
|
@bash scripts/build/image.sh --no-cache
|
||||||
|
|
||||||
build-no-cache-all:
|
build-no-cache-all:
|
||||||
@set -e; \
|
@set -e; \
|
||||||
@@ -70,13 +97,6 @@ test-env-virtual: build-missing
|
|||||||
test-env-nix: build-missing
|
test-env-nix: build-missing
|
||||||
@bash scripts/test/test-env-nix.sh
|
@bash scripts/test/test-env-nix.sh
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# Build only missing container images
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
build-missing:
|
|
||||||
@bash scripts/build/build-image-missing.sh
|
|
||||||
|
|
||||||
# Combined test target for local + CI (unit + integration + e2e)
|
# Combined test target for local + CI (unit + integration + e2e)
|
||||||
test: test-env-virtual test-unit test-integration test-e2e
|
test: test-env-virtual test-unit test-integration test-e2e
|
||||||
|
|
||||||
@@ -85,13 +105,6 @@ delete-volumes:
|
|||||||
|
|
||||||
purge: delete-volumes build-no-cache
|
purge: delete-volumes build-no-cache
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# System install (native packages, calls scripts/installation/run-package.sh)
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
install:
|
|
||||||
@echo "Building and installing distro-native package-manager for this system..."
|
|
||||||
@bash scripts/installation/run-package.sh
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Uninstall target
|
# Uninstall target
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|||||||
113
README.md
113
README.md
@@ -8,8 +8,9 @@
|
|||||||
[](https://s.veen.world/paypaldonate)
|
[](https://s.veen.world/paypaldonate)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://github.com/kevinveenbirkenbach/package-manager)
|
[](https://github.com/kevinveenbirkenbach/package-manager)
|
||||||
|
[](https://github.com/kevinveenbirkenbach/package-manager/actions/workflows/mark-stable.yml)
|
||||||
|
|
||||||
**Kevin's Package Manager (PKGMGR)** is a *multi-distro* package manager and workflow orchestrator.
|
[**Kevin's Package Manager (PKGMGR)**](https://s.veen.world/pkgmgr) is a *multi-distro* package manager and workflow orchestrator.
|
||||||
It helps you **develop, package, release and manage projects across multiple Linux-based
|
It helps you **develop, package, release and manage projects across multiple Linux-based
|
||||||
operating systems** (Arch, Debian, Ubuntu, Fedora, CentOS, …).
|
operating systems** (Arch, Debian, Ubuntu, Fedora, CentOS, …).
|
||||||
|
|
||||||
@@ -103,50 +104,88 @@ The following diagram gives a full overview of:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Perfekt, dann hier die **noch kompaktere und korrekt differenzierte Version**, die **nur** zwischen
|
||||||
|
**`make setup`** und **`make setup-venv`** unterscheidet und exakt deinem Verhalten entspricht.
|
||||||
|
|
||||||
|
README-ready, ohne Over-Engineering.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Installation ⚙️
|
## Installation ⚙️
|
||||||
|
|
||||||
### 1. Get the latest stable version
|
PKGMGR can be installed using `make`.
|
||||||
|
The setup mode defines **which runtime layers are prepared**.
|
||||||
|
|
||||||
For a stable setup, use the **latest tagged release** (the tag pointed to by
|
---
|
||||||
`latest`):
|
|
||||||
|
### Dependency installation (optional)
|
||||||
|
|
||||||
|
System dependencies required **before running any *make* commands** are installed via:
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts/installation/dependencies.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The script detects and normalizes the OS and installs the required **system-level dependencies** accordingly.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Setup modes
|
||||||
|
|
||||||
|
| Command | Prepares | Use case |
|
||||||
|
| ------------------- | ----------------------- | --------------------- |
|
||||||
|
| **make setup** | Python venv **and** Nix | Full development & CI |
|
||||||
|
| **make setup-venv** | Python venv only | Local user setup |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Install & setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/kevinveenbirkenbach/package-manager.git
|
git clone https://github.com/kevinveenbirkenbach/package-manager.git
|
||||||
cd package-manager
|
cd package-manager
|
||||||
|
|
||||||
# Optional but recommended: checkout the latest stable tag
|
|
||||||
git fetch --tags
|
|
||||||
git checkout "$(git describe --tags --abbrev=0)"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Install via Make
|
|
||||||
|
|
||||||
The project ships with a Makefile that encapsulates the typical installation
|
|
||||||
flow. On most systems you only need:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Ensure make, Python and pip are installed via your distro package manager
|
|
||||||
# (e.g. pacman -S make python python-pip, apt install make python3-pip, ...)
|
|
||||||
|
|
||||||
make install
|
make install
|
||||||
```
|
```
|
||||||
|
|
||||||
This will:
|
#### Full setup (venv + Nix)
|
||||||
|
|
||||||
* create or reuse a Python virtual environment,
|
|
||||||
* install PKGMGR (and its Python dependencies) into that environment,
|
|
||||||
* expose the `pkgmgr` executable on your PATH (usually via `~/.local/bin`),
|
|
||||||
* prepare Nix-based integration where available so PKGMGR can build and manage
|
|
||||||
packages distribution-independently.
|
|
||||||
|
|
||||||
For development use, you can also run:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make setup
|
make setup
|
||||||
```
|
```
|
||||||
|
|
||||||
which prepares the environment and leaves you with a fully wired development
|
Use this for CI, servers, containers and full development workflows.
|
||||||
workspace (including Nix, tests and scripts).
|
|
||||||
|
#### Venv-only setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup-venv
|
||||||
|
source ~/.venvs/pkgmgr/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
Use this if you want PKGMGR isolated without Nix integration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Run without installation (Nix)
|
||||||
|
|
||||||
|
Run PKGMGR directly via Nix Flakes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run github:kevinveenbirkenbach/package-manager#pkgmgr -- --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run github:kevinveenbirkenbach/package-manager#pkgmgr -- version pkgmgr
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
* full flake URL required
|
||||||
|
* `--` separates Nix and PKGMGR arguments
|
||||||
|
* can be used alongside any setup mode
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -158,21 +197,9 @@ After installation, the main entry point is:
|
|||||||
pkgmgr --help
|
pkgmgr --help
|
||||||
```
|
```
|
||||||
|
|
||||||
This prints a list of all available subcommands, for example:
|
This prints a list of all available subcommands.
|
||||||
|
|
||||||
* `pkgmgr list --all` – show all repositories in the config
|
|
||||||
* `pkgmgr update --all --clone-mode https` – update every repository
|
|
||||||
* `pkgmgr release patch --preview` – simulate a patch release
|
|
||||||
* `pkgmgr version --all` – show version information for all repositories
|
|
||||||
* `pkgmgr mirror setup --preview --all` – prepare Git mirrors (no changes in preview)
|
|
||||||
* `pkgmgr make install --preview pkgmgr` – preview make install for the pkgmgr repo
|
|
||||||
|
|
||||||
The help for each command is available via:
|
The help for each command is available via:
|
||||||
|
|
||||||
```bash
|
|
||||||
pkgmgr <command> --help
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## License 📄
|
## License 📄
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
rec {
|
rec {
|
||||||
pkgmgr = pyPkgs.buildPythonApplication {
|
pkgmgr = pyPkgs.buildPythonApplication {
|
||||||
pname = "package-manager";
|
pname = "package-manager";
|
||||||
version = "1.2.1";
|
version = "1.4.1";
|
||||||
|
|
||||||
# Use the git repo as source
|
# Use the git repo as source
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|||||||
14
main.py
14
main.py
@@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Ensure local src/ overrides installed package
|
|
||||||
ROOT = Path(__file__).resolve().parent
|
|
||||||
SRC = ROOT / "src"
|
|
||||||
if SRC.is_dir():
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
from pkgmgr.cli import main
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -50,9 +50,10 @@ package() {
|
|||||||
install -Dm0755 "scripts/pkgmgr-wrapper.sh" \
|
install -Dm0755 "scripts/pkgmgr-wrapper.sh" \
|
||||||
"$pkgdir/usr/bin/pkgmgr"
|
"$pkgdir/usr/bin/pkgmgr"
|
||||||
|
|
||||||
# Install Nix init helper
|
# Install Nix bootstrap (init + lib)
|
||||||
install -Dm0755 "scripts/init-nix.sh" \
|
install -d "$pkgdir/usr/lib/package-manager/nix"
|
||||||
"$pkgdir/usr/lib/package-manager/init-nix.sh"
|
cp -a scripts/nix/* "$pkgdir/usr/lib/package-manager/nix/"
|
||||||
|
chmod 0755 "$pkgdir/usr/lib/package-manager/nix/init.sh"
|
||||||
|
|
||||||
# Install the full repository into /usr/lib/package-manager
|
# Install the full repository into /usr/lib/package-manager
|
||||||
mkdir -p "$pkgdir/usr/lib/package-manager"
|
mkdir -p "$pkgdir/usr/lib/package-manager"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
post_install() {
|
post_install() {
|
||||||
/usr/lib/package-manager/init-nix.sh || echo ">>> ERROR: /usr/lib/package-manager/init-nix.sh not found or not executable."
|
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
|
||||||
}
|
}
|
||||||
|
|
||||||
post_upgrade() {
|
post_upgrade() {
|
||||||
/usr/lib/package-manager/init-nix.sh || echo ">>> ERROR: /usr/lib/package-manager/init-nix.sh not found or not executable."
|
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
|
||||||
}
|
}
|
||||||
|
|
||||||
post_remove() {
|
post_remove() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ set -e
|
|||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
configure)
|
configure)
|
||||||
/usr/lib/package-manager/init-nix.sh || echo ">>> ERROR: /usr/lib/package-manager/init-nix.sh not found or not executable."
|
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ override_dh_auto_test:
|
|||||||
:
|
:
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Install phase: copy wrapper + init script + full project source
|
# Install phase: copy wrapper + Nix bootstrap (init + lib) + full project source
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
override_dh_auto_install:
|
override_dh_auto_install:
|
||||||
# Create target directories
|
# Create target directories
|
||||||
@@ -31,9 +31,11 @@ override_dh_auto_install:
|
|||||||
install -m0755 scripts/pkgmgr-wrapper.sh \
|
install -m0755 scripts/pkgmgr-wrapper.sh \
|
||||||
debian/package-manager/usr/bin/pkgmgr
|
debian/package-manager/usr/bin/pkgmgr
|
||||||
|
|
||||||
# Install shared Nix init script
|
# Install Nix bootstrap (init + lib)
|
||||||
install -m0755 scripts/init-nix.sh \
|
install -d debian/package-manager/usr/lib/package-manager/nix
|
||||||
debian/package-manager/usr/lib/package-manager/init-nix.sh
|
cp -a scripts/nix/* \
|
||||||
|
debian/package-manager/usr/lib/package-manager/nix/
|
||||||
|
chmod 0755 debian/package-manager/usr/lib/package-manager/nix/init.sh
|
||||||
|
|
||||||
# Copy full project source into /usr/lib/package-manager,
|
# Copy full project source into /usr/lib/package-manager,
|
||||||
# but do not include the debian/ directory itself.
|
# but do not include the debian/ directory itself.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ BuildArch: noarch
|
|||||||
# NOTE:
|
# NOTE:
|
||||||
# Nix is a runtime requirement, but it is *not* declared here as a hard
|
# Nix is a runtime requirement, but it is *not* declared here as a hard
|
||||||
# RPM dependency, because many distributions do not ship a "nix" RPM.
|
# RPM dependency, because many distributions do not ship a "nix" RPM.
|
||||||
# Instead, Nix is installed and initialized by init-nix.sh, which is
|
# Instead, Nix is installed and initialized by nix/init.sh, which is
|
||||||
# called in the %post scriptlet below.
|
# called in the %post scriptlet below.
|
||||||
|
|
||||||
%description
|
%description
|
||||||
@@ -22,7 +22,7 @@ manager via a local Nix flake:
|
|||||||
nix run /usr/lib/package-manager#pkgmgr -- ...
|
nix run /usr/lib/package-manager#pkgmgr -- ...
|
||||||
|
|
||||||
Nix is a runtime requirement and is installed/initialized by the
|
Nix is a runtime requirement and is installed/initialized by the
|
||||||
init-nix.sh helper during package installation if it is not yet
|
nix/init.sh helper during package installation if it is not yet
|
||||||
available on the system.
|
available on the system.
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
@@ -34,8 +34,8 @@ available on the system.
|
|||||||
|
|
||||||
%install
|
%install
|
||||||
rm -rf %{buildroot}
|
rm -rf %{buildroot}
|
||||||
|
|
||||||
install -d %{buildroot}%{_bindir}
|
install -d %{buildroot}%{_bindir}
|
||||||
# Install project tree into a fixed, architecture-independent location.
|
|
||||||
install -d %{buildroot}/usr/lib/package-manager
|
install -d %{buildroot}/usr/lib/package-manager
|
||||||
|
|
||||||
# Copy full project source into /usr/lib/package-manager
|
# Copy full project source into /usr/lib/package-manager
|
||||||
@@ -44,8 +44,10 @@ cp -a . %{buildroot}/usr/lib/package-manager/
|
|||||||
# Wrapper
|
# Wrapper
|
||||||
install -m0755 scripts/pkgmgr-wrapper.sh %{buildroot}%{_bindir}/pkgmgr
|
install -m0755 scripts/pkgmgr-wrapper.sh %{buildroot}%{_bindir}/pkgmgr
|
||||||
|
|
||||||
# Shared Nix init script (ensure it is executable in the installed tree)
|
# Nix bootstrap (init + lib)
|
||||||
install -m0755 scripts/init-nix.sh %{buildroot}/usr/lib/package-manager/init-nix.sh
|
install -d %{buildroot}/usr/lib/package-manager/nix
|
||||||
|
cp -a scripts/nix/* %{buildroot}/usr/lib/package-manager/nix/
|
||||||
|
chmod 0755 %{buildroot}/usr/lib/package-manager/nix/init.sh
|
||||||
|
|
||||||
# Remove packaging-only and development artefacts from the installed tree
|
# Remove packaging-only and development artefacts from the installed tree
|
||||||
rm -rf \
|
rm -rf \
|
||||||
@@ -60,7 +62,7 @@ rm -rf \
|
|||||||
%{buildroot}/usr/lib/package-manager/.gitkeep || true
|
%{buildroot}/usr/lib/package-manager/.gitkeep || true
|
||||||
|
|
||||||
%post
|
%post
|
||||||
/usr/lib/package-manager/init-nix.sh || echo ">>> ERROR: /usr/lib/package-manager/init-nix.sh not found or not executable."
|
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
|
||||||
|
|
||||||
%postun
|
%postun
|
||||||
echo ">>> package-manager removed. Nix itself was not removed."
|
echo ">>> package-manager removed. Nix itself was not removed."
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "package-manager"
|
name = "package-manager"
|
||||||
version = "1.2.1"
|
version = "1.4.1"
|
||||||
description = "Kevin's package-manager tool (pkgmgr)"
|
description = "Kevin's package-manager tool (pkgmgr)"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.9"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
|
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${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}"
|
||||||
|
|
||||||
resolve_base_image() {
|
resolve_base_image() {
|
||||||
local distro="$1"
|
local distro="$1"
|
||||||
|
|
||||||
case "$distro" in
|
case "$distro" in
|
||||||
arch) echo "$BASE_IMAGE_ARCH" ;;
|
arch) echo "$BASE_IMAGE_ARCH" ;;
|
||||||
debian) echo "$BASE_IMAGE_DEBIAN" ;;
|
debian) echo "$BASE_IMAGE_DEBIAN" ;;
|
||||||
ubuntu) echo "$BASE_IMAGE_UBUNTU" ;;
|
ubuntu) echo "$BASE_IMAGE_UBUNTU" ;;
|
||||||
fedora) echo "$BASE_IMAGE_FEDORA" ;;
|
fedora) echo "$BASE_IMAGE_FEDORA" ;;
|
||||||
centos) echo "$BASE_IMAGE_CENTOS" ;;
|
centos) echo "$BASE_IMAGE_CENTOS" ;;
|
||||||
*)
|
*) echo "ERROR: Unknown distro '$distro'" >&2; exit 1 ;;
|
||||||
echo "ERROR: Unknown distro '$distro'" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
source "${SCRIPT_DIR}/resolve-base-image.sh"
|
|
||||||
|
|
||||||
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)"
|
|
||||||
exit 0
|
|
||||||
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" \
|
|
||||||
.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
source "${SCRIPT_DIR}/resolve-base-image.sh"
|
|
||||||
|
|
||||||
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" \
|
|
||||||
.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
source "${SCRIPT_DIR}/resolve-base-image.sh"
|
|
||||||
|
|
||||||
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" \
|
|
||||||
.
|
|
||||||
225
scripts/build/image.sh
Executable file
225
scripts/build/image.sh
Executable file
@@ -0,0 +1,225 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
source "${SCRIPT_DIR}/base.sh"
|
||||||
|
|
||||||
|
: "${distro:?Environment variable 'distro' must be set (arch|debian|ubuntu|fedora|centos)}"
|
||||||
|
|
||||||
|
NO_CACHE=0
|
||||||
|
MISSING_ONLY=0
|
||||||
|
TARGET=""
|
||||||
|
IMAGE_TAG="" # local image name or base tag (without registry)
|
||||||
|
PUSH=0 # if 1 -> use buildx and push (requires docker buildx)
|
||||||
|
PUBLISH=0 # if 1 -> push with semantic tags (latest/version/stable + arch aliases)
|
||||||
|
REGISTRY="" # e.g. ghcr.io
|
||||||
|
OWNER="" # e.g. github org/user
|
||||||
|
REPO_PREFIX="pkgmgr" # image base name (pkgmgr)
|
||||||
|
VERSION="" # X.Y.Z (required for --publish)
|
||||||
|
IS_STABLE="false" # "true" -> publish stable tags
|
||||||
|
DEFAULT_DISTRO="arch"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
local default_tag="pkgmgr-${distro}"
|
||||||
|
if [[ -n "${TARGET:-}" ]]; then
|
||||||
|
default_tag="${default_tag}-${TARGET}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
Usage: distro=<distro> $0 [options]
|
||||||
|
|
||||||
|
Build options:
|
||||||
|
--missing Build only if the image does not already exist (local build only)
|
||||||
|
--no-cache Build with --no-cache
|
||||||
|
--target <name> Build a specific Dockerfile target (e.g. virgin)
|
||||||
|
--tag <image> Override the output image tag (default: ${default_tag})
|
||||||
|
|
||||||
|
Publish options:
|
||||||
|
--push Push the built image (uses docker buildx build --push)
|
||||||
|
--publish Publish semantic tags (latest, <version>, optional stable) + arch aliases
|
||||||
|
--registry <reg> Registry (e.g. ghcr.io)
|
||||||
|
--owner <owner> Registry namespace (e.g. \${GITHUB_REPOSITORY_OWNER})
|
||||||
|
--repo-prefix <name> Image base name (default: pkgmgr)
|
||||||
|
--version <X.Y.Z> Version for --publish
|
||||||
|
--stable <true|false> Whether to publish :stable tags (default: false)
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- --publish implies --push and requires --registry, --owner, and --version.
|
||||||
|
- Local build (no --push) uses "docker build" and creates local images like "pkgmgr-arch" / "pkgmgr-arch-virgin".
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--no-cache) NO_CACHE=1; shift ;;
|
||||||
|
--missing) MISSING_ONLY=1; shift ;;
|
||||||
|
--target)
|
||||||
|
TARGET="${2:-}"
|
||||||
|
[[ -n "${TARGET}" ]] || { echo "ERROR: --target requires a value (e.g. virgin)"; exit 2; }
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--tag)
|
||||||
|
IMAGE_TAG="${2:-}"
|
||||||
|
[[ -n "${IMAGE_TAG}" ]] || { echo "ERROR: --tag requires a value"; exit 2; }
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--push) PUSH=1; shift ;;
|
||||||
|
--publish) PUBLISH=1; PUSH=1; shift ;;
|
||||||
|
--registry)
|
||||||
|
REGISTRY="${2:-}"
|
||||||
|
[[ -n "${REGISTRY}" ]] || { echo "ERROR: --registry requires a value"; exit 2; }
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--owner)
|
||||||
|
OWNER="${2:-}"
|
||||||
|
[[ -n "${OWNER}" ]] || { echo "ERROR: --owner requires a value"; exit 2; }
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--repo-prefix)
|
||||||
|
REPO_PREFIX="${2:-}"
|
||||||
|
[[ -n "${REPO_PREFIX}" ]] || { echo "ERROR: --repo-prefix requires a value"; exit 2; }
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--version)
|
||||||
|
VERSION="${2:-}"
|
||||||
|
[[ -n "${VERSION}" ]] || { echo "ERROR: --version requires a value"; exit 2; }
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--stable)
|
||||||
|
IS_STABLE="${2:-}"
|
||||||
|
[[ -n "${IS_STABLE}" ]] || { echo "ERROR: --stable requires a value (true|false)"; exit 2; }
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unknown argument: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Derive default local tag if not provided
|
||||||
|
if [[ -z "${IMAGE_TAG}" ]]; then
|
||||||
|
IMAGE_TAG="${REPO_PREFIX}-${distro}"
|
||||||
|
if [[ -n "${TARGET}" ]]; then
|
||||||
|
IMAGE_TAG="${IMAGE_TAG}-${TARGET}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
BASE_IMAGE="$(resolve_base_image "$distro")"
|
||||||
|
|
||||||
|
# Local-only "missing" shortcut
|
||||||
|
if [[ "${MISSING_ONLY}" == "1" ]]; then
|
||||||
|
if [[ "${PUSH}" == "1" ]]; then
|
||||||
|
echo "ERROR: --missing is only supported for local builds (without --push/--publish)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then
|
||||||
|
echo "[build] Image already exists: ${IMAGE_TAG} (skipping due to --missing)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate publish parameters
|
||||||
|
if [[ "${PUBLISH}" == "1" ]]; then
|
||||||
|
[[ -n "${REGISTRY}" ]] || { echo "ERROR: --publish requires --registry"; exit 2; }
|
||||||
|
[[ -n "${OWNER}" ]] || { echo "ERROR: --publish requires --owner"; exit 2; }
|
||||||
|
[[ -n "${VERSION}" ]] || { echo "ERROR: --publish requires --version"; exit 2; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Guard: --push without --publish requires fully-qualified --tag
|
||||||
|
if [[ "${PUSH}" == "1" && "${PUBLISH}" != "1" ]]; then
|
||||||
|
if [[ "${IMAGE_TAG}" != */* ]]; then
|
||||||
|
echo "ERROR: --push requires --tag with a fully-qualified name (e.g. ghcr.io/<owner>/<image>:tag), or use --publish" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "------------------------------------------------------------"
|
||||||
|
echo "[build] Building image"
|
||||||
|
echo "distro = ${distro}"
|
||||||
|
echo "BASE_IMAGE = ${BASE_IMAGE}"
|
||||||
|
if [[ -n "${TARGET}" ]]; then echo "target = ${TARGET}"; fi
|
||||||
|
if [[ "${NO_CACHE}" == "1" ]]; then echo "cache = disabled"; fi
|
||||||
|
if [[ "${PUSH}" == "1" ]]; then echo "push = enabled"; fi
|
||||||
|
if [[ "${PUBLISH}" == "1" ]]; then
|
||||||
|
echo "publish = enabled"
|
||||||
|
echo "registry = ${REGISTRY}"
|
||||||
|
echo "owner = ${OWNER}"
|
||||||
|
echo "version = ${VERSION}"
|
||||||
|
echo "stable = ${IS_STABLE}"
|
||||||
|
fi
|
||||||
|
echo "------------------------------------------------------------"
|
||||||
|
|
||||||
|
# Common build args
|
||||||
|
build_args=(--build-arg "BASE_IMAGE=${BASE_IMAGE}")
|
||||||
|
|
||||||
|
if [[ "${NO_CACHE}" == "1" ]]; then
|
||||||
|
build_args+=(--no-cache)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${TARGET}" ]]; then
|
||||||
|
build_args+=(--target "${TARGET}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
compute_publish_tags() {
|
||||||
|
local distro_tag_base="${REGISTRY}/${OWNER}/${REPO_PREFIX}-${distro}"
|
||||||
|
local alias_tag_base=""
|
||||||
|
|
||||||
|
if [[ -n "${TARGET}" ]]; then
|
||||||
|
distro_tag_base="${distro_tag_base}-${TARGET}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${distro}" == "${DEFAULT_DISTRO}" ]]; then
|
||||||
|
alias_tag_base="${REGISTRY}/${OWNER}/${REPO_PREFIX}"
|
||||||
|
if [[ -n "${TARGET}" ]]; then
|
||||||
|
alias_tag_base="${alias_tag_base}-${TARGET}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tags=()
|
||||||
|
tags+=("${distro_tag_base}:latest")
|
||||||
|
tags+=("${distro_tag_base}:${VERSION}")
|
||||||
|
|
||||||
|
if [[ "${IS_STABLE}" == "true" ]]; then
|
||||||
|
tags+=("${distro_tag_base}:stable")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${alias_tag_base}" ]]; then
|
||||||
|
tags+=("${alias_tag_base}:latest")
|
||||||
|
tags+=("${alias_tag_base}:${VERSION}")
|
||||||
|
if [[ "${IS_STABLE}" == "true" ]]; then
|
||||||
|
tags+=("${alias_tag_base}:stable")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "${tags[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${PUSH}" == "1" ]]; then
|
||||||
|
bx_args=(docker buildx build --push)
|
||||||
|
|
||||||
|
if [[ "${PUBLISH}" == "1" ]]; then
|
||||||
|
while IFS= read -r t; do
|
||||||
|
bx_args+=(-t "$t")
|
||||||
|
done < <(compute_publish_tags)
|
||||||
|
else
|
||||||
|
bx_args+=(-t "${IMAGE_TAG}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
bx_args+=("${build_args[@]}")
|
||||||
|
bx_args+=(.)
|
||||||
|
|
||||||
|
echo "[build] Running: ${bx_args[*]}"
|
||||||
|
"${bx_args[@]}"
|
||||||
|
else
|
||||||
|
local_args=(docker build)
|
||||||
|
local_args+=("${build_args[@]}")
|
||||||
|
local_args+=(-t "${IMAGE_TAG}")
|
||||||
|
local_args+=(.)
|
||||||
|
|
||||||
|
echo "[build] Running: ${local_args[*]}"
|
||||||
|
"${local_args[@]}"
|
||||||
|
fi
|
||||||
55
scripts/build/publish.sh
Executable file
55
scripts/build/publish.sh
Executable file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Publish all distro images (full + virgin) to a registry via image.sh --publish
|
||||||
|
#
|
||||||
|
# Required env:
|
||||||
|
# OWNER (e.g. GITHUB_REPOSITORY_OWNER)
|
||||||
|
# VERSION (e.g. 1.2.3)
|
||||||
|
#
|
||||||
|
# Optional env:
|
||||||
|
# REGISTRY (default: ghcr.io)
|
||||||
|
# IS_STABLE (default: false)
|
||||||
|
# DISTROS (default: "arch debian ubuntu fedora centos")
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
REGISTRY="${REGISTRY:-ghcr.io}"
|
||||||
|
IS_STABLE="${IS_STABLE:-false}"
|
||||||
|
DISTROS="${DISTROS:-arch debian ubuntu fedora centos}"
|
||||||
|
|
||||||
|
: "${OWNER:?Environment variable OWNER must be set (e.g. github.repository_owner)}"
|
||||||
|
: "${VERSION:?Environment variable VERSION must be set (e.g. 1.2.3)}"
|
||||||
|
|
||||||
|
echo "[publish] REGISTRY=${REGISTRY}"
|
||||||
|
echo "[publish] OWNER=${OWNER}"
|
||||||
|
echo "[publish] VERSION=${VERSION}"
|
||||||
|
echo "[publish] IS_STABLE=${IS_STABLE}"
|
||||||
|
echo "[publish] DISTROS=${DISTROS}"
|
||||||
|
|
||||||
|
for d in ${DISTROS}; do
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo "[publish] distro=${d}"
|
||||||
|
echo "============================================================"
|
||||||
|
|
||||||
|
# virgin
|
||||||
|
distro="${d}" bash "${SCRIPT_DIR}/image.sh" \
|
||||||
|
--publish \
|
||||||
|
--registry "${REGISTRY}" \
|
||||||
|
--owner "${OWNER}" \
|
||||||
|
--version "${VERSION}" \
|
||||||
|
--stable "${IS_STABLE}" \
|
||||||
|
--target virgin
|
||||||
|
|
||||||
|
# full (default target)
|
||||||
|
distro="${d}" bash "${SCRIPT_DIR}/image.sh" \
|
||||||
|
--publish \
|
||||||
|
--registry "${REGISTRY}" \
|
||||||
|
--owner "${OWNER}" \
|
||||||
|
--version "${VERSION}" \
|
||||||
|
--stable "${IS_STABLE}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[publish] Done."
|
||||||
@@ -1,53 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Detect and export a valid CA bundle so Nix, Git, curl and Python tooling
|
|
||||||
# can successfully perform HTTPS requests on all distros (Debian, Ubuntu,
|
|
||||||
# Fedora, RHEL, CentOS, etc.)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
detect_ca_bundle() {
|
|
||||||
# Common CA bundle locations across major Linux distributions
|
|
||||||
local candidates=(
|
|
||||||
/etc/ssl/certs/ca-certificates.crt # Debian/Ubuntu
|
|
||||||
/etc/ssl/cert.pem # Some distros
|
|
||||||
/etc/pki/tls/certs/ca-bundle.crt # Fedora/RHEL/CentOS
|
|
||||||
/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem # CentOS/RHEL extracted bundle
|
|
||||||
/etc/ssl/ca-bundle.pem # Generic fallback
|
|
||||||
)
|
|
||||||
|
|
||||||
for path in "${candidates[@]}"; do
|
|
||||||
if [[ -f "$path" ]]; then
|
|
||||||
echo "$path"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use existing NIX_SSL_CERT_FILE if provided, otherwise auto-detect
|
|
||||||
CA_BUNDLE="${NIX_SSL_CERT_FILE:-}"
|
|
||||||
|
|
||||||
if [[ -z "${CA_BUNDLE}" ]]; then
|
|
||||||
CA_BUNDLE="$(detect_ca_bundle || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${CA_BUNDLE}" ]]; then
|
|
||||||
# Export for Nix (critical)
|
|
||||||
export NIX_SSL_CERT_FILE="${CA_BUNDLE}"
|
|
||||||
|
|
||||||
# Export for Git, Python requests, curl, etc.
|
|
||||||
export SSL_CERT_FILE="${CA_BUNDLE}"
|
|
||||||
export REQUESTS_CA_BUNDLE="${CA_BUNDLE}"
|
|
||||||
export GIT_SSL_CAINFO="${CA_BUNDLE}"
|
|
||||||
|
|
||||||
echo "[docker] Using CA bundle: ${CA_BUNDLE}"
|
|
||||||
else
|
|
||||||
echo "[docker] WARNING: No CA certificate bundle found."
|
|
||||||
echo "[docker] HTTPS access for Nix flakes and other tools may fail."
|
|
||||||
fi
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
echo "[docker] Starting package-manager container"
|
echo "[docker] Starting package-manager container"
|
||||||
@@ -68,16 +21,10 @@ cd /src
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# DEV mode: rebuild package-manager from the mounted /src tree
|
# DEV mode: rebuild package-manager from the mounted /src tree
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
if [[ "${PKGMGR_DEV:-0}" == "1" ]]; then
|
if [[ "${REINSTALL_PKGMGR:-0}" == "1" ]]; then
|
||||||
echo "[docker] DEV mode enabled (PKGMGR_DEV=1)"
|
echo "[docker] DEV mode enabled (REINSTALL_PKGMGR=1)"
|
||||||
echo "[docker] Rebuilding package-manager from /src via scripts/installation/run-package.sh..."
|
echo "[docker] Rebuilding package-manager from /src via scripts/installation/package.sh..."
|
||||||
|
bash scripts/installation/package.sh || exit 1
|
||||||
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
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,256 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "[init-nix] Starting Nix initialization..."
|
|
||||||
|
|
||||||
NIX_INSTALL_URL="${NIX_INSTALL_URL:-https://nixos.org/nix/install}"
|
|
||||||
NIX_DOWNLOAD_MAX_TIME=300 # 5 minutes
|
|
||||||
NIX_DOWNLOAD_SLEEP_INTERVAL=20 # 20 seconds
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Detect whether we are inside a container (Docker/Podman/etc.)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
is_container() {
|
|
||||||
if [[ -f /.dockerenv ]] || [[ -f /run/.containerenv ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -qiE 'docker|container|podman|lxc' /proc/1/cgroup 2>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${container:-}" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Ensure Nix binaries are on PATH (multi-user or single-user)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
ensure_nix_on_path() {
|
|
||||||
if [[ -x /nix/var/nix/profiles/default/bin/nix ]]; then
|
|
||||||
export PATH="/nix/var/nix/profiles/default/bin:${PATH}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -x "${HOME}/.nix-profile/bin/nix" ]]; then
|
|
||||||
export PATH="${HOME}/.nix-profile/bin:${PATH}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -x /home/nix/.nix-profile/bin/nix ]]; then
|
|
||||||
export PATH="/home/nix/.nix-profile/bin:${PATH}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Ensure Nix build group and users exist (build-users-group = nixbld)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
ensure_nix_build_group() {
|
|
||||||
if ! getent group nixbld >/dev/null 2>&1; then
|
|
||||||
echo "[init-nix] Creating group 'nixbld'..."
|
|
||||||
groupadd -r nixbld
|
|
||||||
fi
|
|
||||||
|
|
||||||
for i in $(seq 1 10); do
|
|
||||||
if ! id "nixbld$i" >/dev/null 2>&1; then
|
|
||||||
echo "[init-nix] Creating build user nixbld$i..."
|
|
||||||
useradd -r -g nixbld -G nixbld -s /usr/sbin/nologin "nixbld$i"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Download and run Nix installer with retry
|
|
||||||
# Usage: install_nix_with_retry daemon|no-daemon [run_as_user]
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
install_nix_with_retry() {
|
|
||||||
local mode="$1"
|
|
||||||
local run_as="${2:-}"
|
|
||||||
local installer elapsed=0 mode_flag
|
|
||||||
|
|
||||||
case "${mode}" in
|
|
||||||
daemon) mode_flag="--daemon" ;;
|
|
||||||
no-daemon) mode_flag="--no-daemon" ;;
|
|
||||||
*)
|
|
||||||
echo "[init-nix] ERROR: Invalid mode '${mode}', expected 'daemon' or 'no-daemon'."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
installer="$(mktemp -t nix-installer.XXXXXX)"
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# FIX: mktemp creates files with 0600 by default, which breaks when we later
|
|
||||||
# run the installer as a different user (e.g., 'nix' in container+root).
|
|
||||||
# Make it readable and (best-effort) owned by the target user.
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
chmod 0644 "${installer}"
|
|
||||||
|
|
||||||
echo "[init-nix] Downloading Nix installer from ${NIX_INSTALL_URL} with retry (max ${NIX_DOWNLOAD_MAX_TIME}s)..."
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
if curl -fL "${NIX_INSTALL_URL}" -o "${installer}"; then
|
|
||||||
echo "[init-nix] Successfully downloaded Nix installer to ${installer}"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
local curl_exit=$?
|
|
||||||
echo "[init-nix] WARNING: Failed to download Nix installer (curl exit code ${curl_exit})."
|
|
||||||
|
|
||||||
elapsed=$((elapsed + NIX_DOWNLOAD_SLEEP_INTERVAL))
|
|
||||||
if (( elapsed >= NIX_DOWNLOAD_MAX_TIME )); then
|
|
||||||
echo "[init-nix] ERROR: Giving up after ${elapsed}s trying to download Nix installer."
|
|
||||||
rm -f "${installer}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[init-nix] Retrying in ${NIX_DOWNLOAD_SLEEP_INTERVAL}s (elapsed: ${elapsed}s/${NIX_DOWNLOAD_MAX_TIME}s)..."
|
|
||||||
sleep "${NIX_DOWNLOAD_SLEEP_INTERVAL}"
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -n "${run_as}" ]]; then
|
|
||||||
# Best-effort: ensure the target user can read the downloaded installer
|
|
||||||
chown "${run_as}:${run_as}" "${installer}" 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "[init-nix] Running installer as user '${run_as}' with mode '${mode}'..."
|
|
||||||
if command -v sudo >/dev/null 2>&1; then
|
|
||||||
sudo -u "${run_as}" bash -lc "sh '${installer}' ${mode_flag}"
|
|
||||||
else
|
|
||||||
su - "${run_as}" -c "sh '${installer}' ${mode_flag}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "[init-nix] Running installer as current user with mode '${mode}'..."
|
|
||||||
sh "${installer}" "${mode_flag}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f "${installer}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Main
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
main() {
|
|
||||||
# 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)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
ensure_nix_on_path
|
|
||||||
|
|
||||||
if command -v nix >/dev/null 2>&1; then
|
|
||||||
echo "[init-nix] Nix found after adjusting PATH: $(command -v nix)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[init-nix] Nix not found, starting installation logic..."
|
|
||||||
|
|
||||||
local IN_CONTAINER=0
|
|
||||||
if is_container; then
|
|
||||||
IN_CONTAINER=1
|
|
||||||
echo "[init-nix] Detected container environment."
|
|
||||||
else
|
|
||||||
echo "[init-nix] No container detected."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Container + root: dedicated "nix" user, single-user install
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
if [[ "${IN_CONTAINER}" -eq 1 && "${EUID:-0}" -eq 0 ]]; then
|
|
||||||
echo "[init-nix] Container + root – installing as 'nix' user (single-user)."
|
|
||||||
|
|
||||||
ensure_nix_build_group
|
|
||||||
|
|
||||||
if ! id nix >/dev/null 2>&1; then
|
|
||||||
echo "[init-nix] Creating user 'nix'..."
|
|
||||||
local BASH_SHELL
|
|
||||||
BASH_SHELL="$(command -v bash || true)"
|
|
||||||
[[ -z "${BASH_SHELL}" ]] && BASH_SHELL="/bin/sh"
|
|
||||||
useradd -m -r -g nixbld -s "${BASH_SHELL}" nix
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -d /nix ]]; then
|
|
||||||
echo "[init-nix] Creating /nix with owner nix:nixbld..."
|
|
||||||
mkdir -m 0755 /nix
|
|
||||||
chown nix:nixbld /nix
|
|
||||||
else
|
|
||||||
local current_owner current_group
|
|
||||||
current_owner="$(stat -c '%U' /nix 2>/dev/null || echo '?')"
|
|
||||||
current_group="$(stat -c '%G' /nix 2>/dev/null || echo '?')"
|
|
||||||
if [[ "${current_owner}" != "nix" || "${current_group}" != "nixbld" ]]; then
|
|
||||||
echo "[init-nix] Fixing /nix ownership from ${current_owner}:${current_group} to nix:nixbld..."
|
|
||||||
chown -R nix:nixbld /nix
|
|
||||||
fi
|
|
||||||
if [[ ! -w /nix ]]; then
|
|
||||||
echo "[init-nix] WARNING: /nix is not writable after chown; Nix installer may fail."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_nix_with_retry "no-daemon" "nix"
|
|
||||||
|
|
||||||
ensure_nix_on_path
|
|
||||||
|
|
||||||
if [[ -x /home/nix/.nix-profile/bin/nix && ! -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
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Host (no container)
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
elif [[ "${IN_CONTAINER}" -eq 0 ]]; then
|
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
|
||||||
echo "[init-nix] Host with systemd – using multi-user install (--daemon)."
|
|
||||||
if [[ "${EUID:-0}" -eq 0 ]]; then
|
|
||||||
ensure_nix_build_group
|
|
||||||
fi
|
|
||||||
install_nix_with_retry "daemon"
|
|
||||||
else
|
|
||||||
if [[ "${EUID:-0}" -eq 0 ]]; then
|
|
||||||
echo "[init-nix] Host without systemd as root – using single-user install (--no-daemon)."
|
|
||||||
ensure_nix_build_group
|
|
||||||
else
|
|
||||||
echo "[init-nix] Host without systemd as non-root – using single-user install (--no-daemon)."
|
|
||||||
fi
|
|
||||||
install_nix_with_retry "no-daemon"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Container, but not root (rare)
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
else
|
|
||||||
echo "[init-nix] Container as non-root – using single-user install (--no-daemon)."
|
|
||||||
install_nix_with_retry "no-daemon"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# After installation: PATH + /etc/profile
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
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."
|
|
||||||
else
|
|
||||||
echo "[init-nix] Nix successfully installed at: $(command -v nix)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -w /etc/profile ]] && ! 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
|
|
||||||
|
|
||||||
echo "[init-nix] Nix initialization complete."
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -12,6 +12,7 @@ pacman -S --noconfirm --needed \
|
|||||||
rsync \
|
rsync \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
python \
|
||||||
xz
|
xz
|
||||||
|
|
||||||
pacman -Scc --noconfirm
|
pacman -Scc --noconfirm
|
||||||
|
|||||||
@@ -1,30 +1,64 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "[arch/package] Building Arch package (makepkg --nodeps)..."
|
echo "[arch/package] Building Arch package (makepkg --nodeps) in an isolated build dir..."
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
||||||
PKG_DIR="${PROJECT_ROOT}/packaging/arch"
|
|
||||||
|
|
||||||
if [[ ! -f "${PKG_DIR}/PKGBUILD" ]]; then
|
# We must not build inside /src (mounted repo). Build in /tmp to avoid permission issues.
|
||||||
echo "[arch/package] ERROR: PKGBUILD not found in ${PKG_DIR}"
|
BUILD_ROOT="/tmp/package-manager-arch-build"
|
||||||
|
PKG_SRC_DIR="${PROJECT_ROOT}/packaging/arch"
|
||||||
|
PKG_BUILD_DIR="${BUILD_ROOT}/packaging/arch"
|
||||||
|
|
||||||
|
if [[ ! -f "${PKG_SRC_DIR}/PKGBUILD" ]]; then
|
||||||
|
echo "[arch/package] ERROR: PKGBUILD not found in ${PKG_SRC_DIR}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd "${PKG_DIR}"
|
echo "[arch/package] Preparing build directory: ${BUILD_ROOT}"
|
||||||
|
rm -rf "${BUILD_ROOT}"
|
||||||
|
mkdir -p "${BUILD_ROOT}"
|
||||||
|
|
||||||
if id aur_builder >/dev/null 2>&1; then
|
echo "[arch/package] Syncing project sources to ${BUILD_ROOT}..."
|
||||||
echo "[arch/package] Using 'aur_builder' user for makepkg..."
|
# Keep it simple: copy everything; adjust excludes if needed later.
|
||||||
chown -R aur_builder:aur_builder "${PKG_DIR}"
|
rsync -a --delete \
|
||||||
su aur_builder -c "cd '${PKG_DIR}' && rm -f package-manager-*.pkg.tar.* && makepkg --noconfirm --clean --nodeps"
|
--exclude '.git' \
|
||||||
else
|
--exclude '.venv' \
|
||||||
echo "[arch/package] WARNING: user 'aur_builder' not found, running makepkg as current user..."
|
--exclude '.venvs' \
|
||||||
rm -f package-manager-*.pkg.tar.*
|
--exclude '__pycache__' \
|
||||||
makepkg --noconfirm --clean --nodeps
|
--exclude '*.pyc' \
|
||||||
|
"${PROJECT_ROOT}/" "${BUILD_ROOT}/"
|
||||||
|
|
||||||
|
if [[ ! -d "${PKG_BUILD_DIR}" ]]; then
|
||||||
|
echo "[arch/package] ERROR: Build PKG dir missing: ${PKG_BUILD_DIR}"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Unprivileged user for Arch package build (makepkg)
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
if ! id aur_builder >/dev/null 2>&1; then
|
||||||
|
echo "[arch/package] ERROR: user 'aur_builder' not found. Run scripts/installation/arch/aur-builder-setup.sh first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[arch/package] Using 'aur_builder' user for makepkg..."
|
||||||
|
chown -R aur_builder:aur_builder "${BUILD_ROOT}"
|
||||||
|
|
||||||
|
echo "[arch/package] Running makepkg in: ${PKG_BUILD_DIR}"
|
||||||
|
su aur_builder -c "cd '${PKG_BUILD_DIR}' && rm -f package-manager-*.pkg.tar.* && makepkg --noconfirm --clean --nodeps"
|
||||||
|
|
||||||
echo "[arch/package] Installing generated Arch package..."
|
echo "[arch/package] Installing generated Arch package..."
|
||||||
pacman -U --noconfirm package-manager-*.pkg.tar.*
|
pkg_path="$(find "${PKG_BUILD_DIR}" -maxdepth 1 -type f -name 'package-manager-*.pkg.tar.*' | head -n1)"
|
||||||
|
if [[ -z "${pkg_path}" ]]; then
|
||||||
|
echo "[arch/package] ERROR: Built package not found in ${PKG_BUILD_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
pacman -U --noconfirm "${pkg_path}"
|
||||||
|
|
||||||
|
echo "[arch/package] Cleanup build directory..."
|
||||||
|
rm -rf "${BUILD_ROOT}"
|
||||||
|
|
||||||
echo "[arch/package] Done."
|
echo "[arch/package] Done."
|
||||||
|
|||||||
@@ -13,9 +13,64 @@ dnf -y install \
|
|||||||
bash \
|
bash \
|
||||||
curl-minimal \
|
curl-minimal \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
python3 \
|
||||||
sudo \
|
sudo \
|
||||||
xz
|
xz
|
||||||
|
|
||||||
dnf clean all
|
dnf clean all
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Persist CA bundle configuration system-wide (virgin-compatible)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
detect_ca_bundle() {
|
||||||
|
local candidates=(
|
||||||
|
/etc/pki/tls/certs/ca-bundle.crt
|
||||||
|
/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
|
||||||
|
/etc/ssl/certs/ca-certificates.crt
|
||||||
|
/etc/ssl/cert.pem
|
||||||
|
/etc/ssl/ca-bundle.pem
|
||||||
|
)
|
||||||
|
|
||||||
|
for path in "${candidates[@]}"; do
|
||||||
|
if [[ -f "$path" ]]; then
|
||||||
|
echo "$path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
CA_BUNDLE="$(detect_ca_bundle || true)"
|
||||||
|
|
||||||
|
if [[ -n "${CA_BUNDLE}" ]]; then
|
||||||
|
echo "[centos/dependencies] Persisting CA bundle: ${CA_BUNDLE}"
|
||||||
|
|
||||||
|
# 1) Make it available for login shells
|
||||||
|
cat >/etc/profile.d/pkgmgr-ca.sh <<EOF
|
||||||
|
# Generated by package-manager
|
||||||
|
export NIX_SSL_CERT_FILE="${CA_BUNDLE}"
|
||||||
|
export SSL_CERT_FILE="${CA_BUNDLE}"
|
||||||
|
export REQUESTS_CA_BUNDLE="${CA_BUNDLE}"
|
||||||
|
export GIT_SSL_CAINFO="${CA_BUNDLE}"
|
||||||
|
EOF
|
||||||
|
chmod 0644 /etc/profile.d/pkgmgr-ca.sh
|
||||||
|
|
||||||
|
# 2) Ensure Nix uses it even without environment variables
|
||||||
|
mkdir -p /etc/nix
|
||||||
|
if [[ -f /etc/nix/nix.conf ]]; then
|
||||||
|
# Replace existing ssl-cert-file or append it
|
||||||
|
if grep -qE '^\s*ssl-cert-file\s*=' /etc/nix/nix.conf; then
|
||||||
|
sed -i "s|^\s*ssl-cert-file\s*=.*|ssl-cert-file = ${CA_BUNDLE}|" /etc/nix/nix.conf
|
||||||
|
else
|
||||||
|
echo "ssl-cert-file = ${CA_BUNDLE}" >>/etc/nix/nix.conf
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ssl-cert-file = ${CA_BUNDLE}" >/etc/nix/nix.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "[centos/dependencies] WARNING: No CA bundle found after installing ca-certificates."
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[centos/dependencies] Done."
|
echo "[centos/dependencies] Done."
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
python3 \
|
||||||
|
python3-venv \
|
||||||
xz-utils
|
xz-utils
|
||||||
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -3,22 +3,19 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
# shellcheck source=/dev/null
|
# shellcheck disable=SC1091
|
||||||
source "${SCRIPT_DIR}/lib.sh"
|
source "${SCRIPT_DIR}/os_resolver.sh"
|
||||||
|
|
||||||
OS_ID="$(detect_os_id)"
|
OS_ID="$(osr_get_os_id)"
|
||||||
|
|
||||||
echo "[run-dependencies] Detected OS: ${OS_ID}"
|
echo "[run-dependencies] Detected OS: ${OS_ID}"
|
||||||
|
|
||||||
case "${OS_ID}" in
|
if ! osr_is_supported "${OS_ID}"; then
|
||||||
arch|debian|ubuntu|fedora|centos)
|
|
||||||
DEP_SCRIPT="${SCRIPT_DIR}/${OS_ID}/dependencies.sh"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "[run-dependencies] Unsupported OS: ${OS_ID}"
|
echo "[run-dependencies] Unsupported OS: ${OS_ID}"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
fi
|
||||||
esac
|
|
||||||
|
DEP_SCRIPT="$(osr_script_path_for "${SCRIPT_DIR}" "${OS_ID}" "dependencies")"
|
||||||
|
|
||||||
if [[ ! -f "${DEP_SCRIPT}" ]]; then
|
if [[ ! -f "${DEP_SCRIPT}" ]]; then
|
||||||
echo "[run-dependencies] Dependency script not found: ${DEP_SCRIPT}"
|
echo "[run-dependencies] Dependency script not found: ${DEP_SCRIPT}"
|
||||||
15
scripts/installation/init.sh
Executable file
15
scripts/installation/init.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||||
|
echo "[installation/install] Warning: Installation is just possible via root."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[installation] Running as root (EUID=0)."
|
||||||
|
echo "[installation] Install Package Dependencies..."
|
||||||
|
bash scripts/installation/dependencies.sh
|
||||||
|
echo "[installation] Install Distribution Package..."
|
||||||
|
bash scripts/installation/package.sh
|
||||||
|
echo "[installation] Root/system setup complete."
|
||||||
|
exit 0
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/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
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# main.sh
|
|
||||||
#
|
|
||||||
# Developer / system setup entrypoint.
|
|
||||||
#
|
|
||||||
# Responsibilities:
|
|
||||||
# - If inside a Nix shell (IN_NIX_SHELL=1):
|
|
||||||
# * Skip venv creation and dependency installation
|
|
||||||
# * Run `python3 main.py install`
|
|
||||||
# - If running as root (EUID=0):
|
|
||||||
# * Run system-level installer (run-package.sh)
|
|
||||||
# - Otherwise (normal user):
|
|
||||||
# * 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 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'
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# 1) 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] Setup finished (Nix mode)."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# 2) Root mode: system / distro-level installation
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then
|
|
||||||
echo "[installation/main] Running as root (EUID=0)."
|
|
||||||
echo "[installation/main] Skipping user virtualenv and shell RC modifications."
|
|
||||||
echo "[installation/main] Delegating to scripts/installation/run-package.sh..."
|
|
||||||
bash scripts/installation/run-package.sh
|
|
||||||
echo "[installation/main] Root/system setup complete."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# 3) Normal user mode: dev setup with venv
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
|
|
||||||
echo "[installation/main] Running in normal user mode (developer setup)."
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
echo "[installation/main] Creating/updating virtualenv via helper..."
|
|
||||||
PKGMGR_VENV_DIR="${VENV_DIR}" bash scripts/installation/venv-create.sh
|
|
||||||
|
|
||||||
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."
|
|
||||||
82
scripts/installation/os_resolver.sh
Executable file
82
scripts/installation/os_resolver.sh
Executable file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# OsResolver (bash "class-style" module)
|
||||||
|
# Centralizes OS detection + normalization + supported checks + script paths.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
osr_detect_raw_id() {
|
||||||
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. /etc/os-release
|
||||||
|
echo "${ID:-unknown}"
|
||||||
|
else
|
||||||
|
echo "unknown"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
osr_detect_id_like() {
|
||||||
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. /etc/os-release
|
||||||
|
echo "${ID_LIKE:-}"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
osr_normalize_id() {
|
||||||
|
local raw="${1:-unknown}"
|
||||||
|
local like="${2:-}"
|
||||||
|
|
||||||
|
# Explicit mapping first (your bugfix: manjaro -> arch everywhere)
|
||||||
|
case "${raw}" in
|
||||||
|
manjaro) echo "arch"; return 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Keep direct IDs when they are already supported
|
||||||
|
case "${raw}" in
|
||||||
|
arch|debian|ubuntu|fedora|centos) echo "${raw}"; return 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Fallback mapping via ID_LIKE for better portability
|
||||||
|
# Example: many Arch derivatives expose ID_LIKE="arch"
|
||||||
|
if [[ " ${like} " == *" arch "* ]]; then
|
||||||
|
echo "arch"; return 0
|
||||||
|
fi
|
||||||
|
if [[ " ${like} " == *" debian "* ]]; then
|
||||||
|
echo "debian"; return 0
|
||||||
|
fi
|
||||||
|
if [[ " ${like} " == *" fedora "* ]]; then
|
||||||
|
echo "fedora"; return 0
|
||||||
|
fi
|
||||||
|
if [[ " ${like} " == *" rhel "* || " ${like} " == *" centos "* ]]; then
|
||||||
|
echo "centos"; return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${raw}"
|
||||||
|
}
|
||||||
|
|
||||||
|
osr_get_os_id() {
|
||||||
|
local raw like
|
||||||
|
raw="$(osr_detect_raw_id)"
|
||||||
|
like="$(osr_detect_id_like)"
|
||||||
|
osr_normalize_id "${raw}" "${like}"
|
||||||
|
}
|
||||||
|
|
||||||
|
osr_is_supported() {
|
||||||
|
local id="${1:-unknown}"
|
||||||
|
case "${id}" in
|
||||||
|
arch|debian|ubuntu|fedora|centos) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
osr_script_path_for() {
|
||||||
|
local script_dir="${1:?script_dir required}"
|
||||||
|
local os_id="${2:?os_id required}"
|
||||||
|
local kind="${3:?kind required}" # "dependencies" or "package"
|
||||||
|
|
||||||
|
echo "${script_dir}/${os_id}/${kind}.sh"
|
||||||
|
}
|
||||||
26
scripts/installation/package.sh
Executable file
26
scripts/installation/package.sh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "${SCRIPT_DIR}/os_resolver.sh"
|
||||||
|
|
||||||
|
OS_ID="$(osr_get_os_id)"
|
||||||
|
|
||||||
|
echo "[package] Detected OS: ${OS_ID}"
|
||||||
|
|
||||||
|
if ! osr_is_supported "${OS_ID}"; then
|
||||||
|
echo "[package] Unsupported OS: ${OS_ID}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PKG_SCRIPT="$(osr_script_path_for "${SCRIPT_DIR}" "${OS_ID}" "package")"
|
||||||
|
|
||||||
|
if [[ ! -f "${PKG_SCRIPT}" ]]; then
|
||||||
|
echo "[package] Package script not found: ${PKG_SCRIPT}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[package] Executing: ${PKG_SCRIPT}"
|
||||||
|
exec bash "${PKG_SCRIPT}"
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#!/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)"
|
|
||||||
|
|
||||||
# Map Manjaro to Arch
|
|
||||||
if [[ "${OS_ID}" == "manjaro" ]]; then
|
|
||||||
echo "[run-package] Mapping OS 'manjaro' → 'arch'"
|
|
||||||
OS_ID="arch"
|
|
||||||
fi
|
|
||||||
|
|
||||||
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}"
|
|
||||||
@@ -14,6 +14,9 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|||||||
rsync \
|
rsync \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
|
make \
|
||||||
|
python3 \
|
||||||
|
python3-venv \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
xz-utils
|
xz-utils
|
||||||
|
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# venv-create.sh
|
|
||||||
#
|
|
||||||
# Create/update a Python virtual environment for pkgmgr and install dependencies.
|
|
||||||
#
|
|
||||||
# Priority order:
|
|
||||||
# 1) pyproject.toml -> pip install (editable by default)
|
|
||||||
# 2) requirements.txt
|
|
||||||
# 3) _requirements.txt (legacy)
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# PKGMGR_VENV_DIR=/home/dev/.venvs/pkgmgr bash scripts/installation/venv-create.sh
|
|
||||||
# or
|
|
||||||
# bash scripts/installation/venv-create.sh /home/dev/.venvs/pkgmgr
|
|
||||||
#
|
|
||||||
# Optional:
|
|
||||||
# PKGMGR_PIP_EDITABLE=0 # install non-editable (default: 1)
|
|
||||||
# PKGMGR_PIP_EXTRAS="dev,test" # install extras: .[dev,test]
|
|
||||||
# PKGMGR_PREFER_NIX=1 # print Nix hint and exit non-zero
|
|
||||||
|
|
||||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
||||||
cd "${PROJECT_ROOT}"
|
|
||||||
|
|
||||||
VENV_DIR="${PKGMGR_VENV_DIR:-${1:-${HOME}/.venvs/pkgmgr}}"
|
|
||||||
PIP_EDITABLE="${PKGMGR_PIP_EDITABLE:-1}"
|
|
||||||
PIP_EXTRAS="${PKGMGR_PIP_EXTRAS:-}"
|
|
||||||
PREFER_NIX="${PKGMGR_PREFER_NIX:-0}"
|
|
||||||
|
|
||||||
echo "[venv-create] Using VENV_DIR=${VENV_DIR}"
|
|
||||||
|
|
||||||
if [[ "${PREFER_NIX}" == "1" ]]; then
|
|
||||||
echo "[venv-create] PKGMGR_PREFER_NIX=1 set."
|
|
||||||
echo "[venv-create] Hint: Use Nix instead of a venv for reproducible installs:"
|
|
||||||
echo "[venv-create] nix develop"
|
|
||||||
echo "[venv-create] nix run .#pkgmgr -- --help"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[venv-create] Ensuring virtualenv parent directory exists..."
|
|
||||||
mkdir -p "$(dirname "${VENV_DIR}")"
|
|
||||||
|
|
||||||
if [[ ! -d "${VENV_DIR}" ]]; then
|
|
||||||
echo "[venv-create] Creating virtual environment at: ${VENV_DIR}"
|
|
||||||
python3 -m venv "${VENV_DIR}"
|
|
||||||
else
|
|
||||||
echo "[venv-create] Virtual environment already exists at: ${VENV_DIR}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[venv-create] Installing Python tooling into venv..."
|
|
||||||
"${VENV_DIR}/bin/python" -m ensurepip --upgrade
|
|
||||||
"${VENV_DIR}/bin/pip" install --upgrade pip setuptools wheel
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Install dependencies
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
if [[ -f "pyproject.toml" ]]; then
|
|
||||||
echo "[venv-create] Detected pyproject.toml. Installing project via pip..."
|
|
||||||
|
|
||||||
target="."
|
|
||||||
if [[ -n "${PIP_EXTRAS}" ]]; then
|
|
||||||
target=".[${PIP_EXTRAS}]"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${PIP_EDITABLE}" == "1" ]]; then
|
|
||||||
echo "[venv-create] pip install -e ${target}"
|
|
||||||
"${VENV_DIR}/bin/pip" install -e "${target}"
|
|
||||||
else
|
|
||||||
echo "[venv-create] pip install ${target}"
|
|
||||||
"${VENV_DIR}/bin/pip" install "${target}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
elif [[ -f "requirements.txt" ]]; then
|
|
||||||
echo "[venv-create] Installing dependencies from requirements.txt..."
|
|
||||||
"${VENV_DIR}/bin/pip" install -r requirements.txt
|
|
||||||
|
|
||||||
elif [[ -f "_requirements.txt" ]]; then
|
|
||||||
echo "[venv-create] Installing dependencies from _requirements.txt (legacy)..."
|
|
||||||
"${VENV_DIR}/bin/pip" install -r _requirements.txt
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "[venv-create] No pyproject.toml, requirements.txt, or _requirements.txt found. Skipping dependency installation."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[venv-create] Done."
|
|
||||||
53
scripts/nix/README.md
Normal file
53
scripts/nix/README.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Nix Bootstrap (package-manager)
|
||||||
|
|
||||||
|
This directory contains the **Nix initialization and bootstrap logic** used by *package-manager* to ensure the `nix` command is available on supported systems (host machines and CI containers).
|
||||||
|
|
||||||
|
It is invoked during package installation (Arch/Debian/Fedora scriptlets) and can also be called manually.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entry Point
|
||||||
|
|
||||||
|
- *scripts/nix/init.sh*
|
||||||
|
Main bootstrap script. It:
|
||||||
|
- checks whether `nix` is already available
|
||||||
|
- adjusts `PATH` for common Nix locations
|
||||||
|
- installs Nix when missing (daemon install on systemd hosts, single-user in containers)
|
||||||
|
- ensures predictable `nix` availability via symlinks (without overwriting distro-managed paths)
|
||||||
|
- validates that `nix` is usable at the end (CI-safe)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Library Layout
|
||||||
|
|
||||||
|
The entry point sources small, focused modules from *scripts/nix/lib/*:
|
||||||
|
|
||||||
|
- *config.sh* — configuration defaults (installer URL, retry timing)
|
||||||
|
- *detect.sh* — container detection helpers
|
||||||
|
- *path.sh* — PATH adjustments and `nix` binary resolution helpers
|
||||||
|
- *symlinks.sh* — user/global symlink helpers for stable `nix` discovery
|
||||||
|
- *users.sh* — build group/users and container ownership/perms helpers
|
||||||
|
- *install.sh* — installer download + retry logic and execution helpers
|
||||||
|
|
||||||
|
Each library file includes a simple guard to prevent double-sourcing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## When It Runs
|
||||||
|
|
||||||
|
This bootstrap is typically executed automatically:
|
||||||
|
|
||||||
|
- Arch: post-install / post-upgrade hook
|
||||||
|
- Debian: `postinst`
|
||||||
|
- Fedora/RPM: `%post`
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes / Design Goals
|
||||||
|
|
||||||
|
- **Cross-distro compatibility:** supports common Linux layouts (including Arch placing `nix` in */usr/sbin*).
|
||||||
|
- **Non-destructive behavior:** avoids overwriting distro-managed `nix` binaries.
|
||||||
|
- **CI robustness:** retry logic for downloads and a final `nix` availability check.
|
||||||
|
- **Container-safe defaults:** single-user install as a dedicated `nix` user when running as root in containers.
|
||||||
|
|
||||||
130
scripts/nix/init.sh
Executable file
130
scripts/nix/init.sh
Executable file
@@ -0,0 +1,130 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# shellcheck source=lib/config.sh
|
||||||
|
# shellcheck source=lib/detect.sh
|
||||||
|
# shellcheck source=lib/path.sh
|
||||||
|
# shellcheck source=lib/symlinks.sh
|
||||||
|
# shellcheck source=lib/users.sh
|
||||||
|
# shellcheck source=lib/install.sh
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
source "${SCRIPT_DIR}/lib/config.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/detect.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/path.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/symlinks.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/users.sh"
|
||||||
|
source "${SCRIPT_DIR}/lib/install.sh"
|
||||||
|
|
||||||
|
echo "[init-nix] Starting Nix initialization..."
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# Fast path: already available
|
||||||
|
if command -v nix >/dev/null 2>&1; then
|
||||||
|
echo "[init-nix] Nix already available on PATH: $(command -v nix)"
|
||||||
|
ensure_nix_on_path
|
||||||
|
|
||||||
|
if [[ "${EUID:-0}" -eq 0 ]]; then
|
||||||
|
ensure_global_nix_symlinks "$(resolve_nix_bin 2>/dev/null || true)"
|
||||||
|
else
|
||||||
|
ensure_user_nix_symlink "$(resolve_nix_bin 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_nix_on_path
|
||||||
|
|
||||||
|
if command -v nix >/dev/null 2>&1; then
|
||||||
|
echo "[init-nix] Nix found after PATH adjustment: $(command -v nix)"
|
||||||
|
if [[ "${EUID:-0}" -eq 0 ]]; then
|
||||||
|
ensure_global_nix_symlinks "$(resolve_nix_bin 2>/dev/null || true)"
|
||||||
|
else
|
||||||
|
ensure_user_nix_symlink "$(resolve_nix_bin 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local IN_CONTAINER=0
|
||||||
|
if is_container; then
|
||||||
|
IN_CONTAINER=1
|
||||||
|
echo "[init-nix] Detected container environment."
|
||||||
|
else
|
||||||
|
echo "[init-nix] No container detected."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Container + root: dedicated "nix" user, single-user install
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
if [[ "$IN_CONTAINER" -eq 1 && "${EUID:-0}" -eq 0 ]]; then
|
||||||
|
echo "[init-nix] Container + root: installing as 'nix' user (single-user)."
|
||||||
|
|
||||||
|
ensure_nix_build_group
|
||||||
|
|
||||||
|
if ! id nix >/dev/null 2>&1; then
|
||||||
|
echo "[init-nix] Creating user 'nix'..."
|
||||||
|
local BASH_SHELL
|
||||||
|
BASH_SHELL="$(command -v bash || true)"
|
||||||
|
[[ -z "$BASH_SHELL" ]] && BASH_SHELL="/bin/sh"
|
||||||
|
useradd -m -r -g nixbld -s "$BASH_SHELL" nix
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_nix_store_dir_for_container_user
|
||||||
|
|
||||||
|
install_nix_with_retry "no-daemon" "nix"
|
||||||
|
|
||||||
|
ensure_nix_on_path
|
||||||
|
|
||||||
|
# Ensure stable global symlink(s) (sudo secure_path friendly)
|
||||||
|
ensure_global_nix_symlinks "/home/nix/.nix-profile/bin/nix"
|
||||||
|
|
||||||
|
# Ensure non-root users can traverse and execute nix user profile
|
||||||
|
ensure_container_profile_perms
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Host (no container)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
else
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
echo "[init-nix] Host with systemd: using multi-user install (--daemon)."
|
||||||
|
if [[ "${EUID:-0}" -eq 0 ]]; then
|
||||||
|
ensure_nix_build_group
|
||||||
|
fi
|
||||||
|
install_nix_with_retry "daemon"
|
||||||
|
else
|
||||||
|
echo "[init-nix] No systemd detected: using single-user install (--no-daemon)."
|
||||||
|
if [[ "${EUID:-0}" -eq 0 ]]; then
|
||||||
|
ensure_nix_build_group
|
||||||
|
fi
|
||||||
|
install_nix_with_retry "no-daemon"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# After install: PATH + symlink(s)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
ensure_nix_on_path
|
||||||
|
|
||||||
|
local nix_bin_post
|
||||||
|
nix_bin_post="$(resolve_nix_bin 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ "${EUID:-0}" -eq 0 ]]; then
|
||||||
|
ensure_global_nix_symlinks "$nix_bin_post"
|
||||||
|
else
|
||||||
|
ensure_user_nix_symlink "$nix_bin_post"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Final verification (must succeed for CI)
|
||||||
|
if ! command -v nix >/dev/null 2>&1; then
|
||||||
|
echo "[init-nix] ERROR: nix not found after installation."
|
||||||
|
echo "[init-nix] DEBUG: resolved nix path = ${nix_bin_post:-<empty>}"
|
||||||
|
echo "[init-nix] DEBUG: PATH = $PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[init-nix] Nix successfully available at: $(command -v nix)"
|
||||||
|
echo "[init-nix] Nix initialization complete."
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
11
scripts/nix/lib/config.sh
Executable file
11
scripts/nix/lib/config.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Prevent double-sourcing
|
||||||
|
if [[ -n "${PKGMGR_NIX_CONFIG_SH:-}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
PKGMGR_NIX_CONFIG_SH=1
|
||||||
|
|
||||||
|
NIX_INSTALL_URL="${NIX_INSTALL_URL:-https://nixos.org/nix/install}"
|
||||||
|
NIX_DOWNLOAD_MAX_TIME="${NIX_DOWNLOAD_MAX_TIME:-300}"
|
||||||
|
NIX_DOWNLOAD_SLEEP_INTERVAL="${NIX_DOWNLOAD_SLEEP_INTERVAL:-20}"
|
||||||
14
scripts/nix/lib/detect.sh
Executable file
14
scripts/nix/lib/detect.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [[ -n "${PKGMGR_NIX_DETECT_SH:-}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
PKGMGR_NIX_DETECT_SH=1
|
||||||
|
|
||||||
|
# Detect whether we are inside a container (Docker/Podman/etc.)
|
||||||
|
is_container() {
|
||||||
|
[[ -f /.dockerenv || -f /run/.containerenv ]] && return 0
|
||||||
|
grep -qiE 'docker|container|podman|lxc' /proc/1/cgroup 2>/dev/null && return 0
|
||||||
|
[[ -n "${container:-}" ]] && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
63
scripts/nix/lib/install.sh
Executable file
63
scripts/nix/lib/install.sh
Executable file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [[ -n "${PKGMGR_NIX_INSTALL_SH:-}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
PKGMGR_NIX_INSTALL_SH=1
|
||||||
|
|
||||||
|
# Requires: NIX_INSTALL_URL, NIX_DOWNLOAD_MAX_TIME, NIX_DOWNLOAD_SLEEP_INTERVAL
|
||||||
|
|
||||||
|
# Download and run Nix installer with retry
|
||||||
|
# Usage: install_nix_with_retry daemon|no-daemon [run_as_user]
|
||||||
|
install_nix_with_retry() {
|
||||||
|
local mode="$1"
|
||||||
|
local run_as="${2:-}"
|
||||||
|
local installer elapsed=0 mode_flag
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
daemon) mode_flag="--daemon" ;;
|
||||||
|
no-daemon) mode_flag="--no-daemon" ;;
|
||||||
|
*)
|
||||||
|
echo "[init-nix] ERROR: Invalid mode '$mode' (expected 'daemon' or 'no-daemon')."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
installer="$(mktemp -t nix-installer.XXXXXX)"
|
||||||
|
chmod 0644 "$installer"
|
||||||
|
|
||||||
|
echo "[init-nix] Downloading Nix installer from $NIX_INSTALL_URL (max ${NIX_DOWNLOAD_MAX_TIME}s)..."
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if curl -fL "$NIX_INSTALL_URL" -o "$installer"; then
|
||||||
|
echo "[init-nix] Successfully downloaded installer to $installer"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
elapsed=$((elapsed + NIX_DOWNLOAD_SLEEP_INTERVAL))
|
||||||
|
echo "[init-nix] WARNING: Download failed. Retrying in ${NIX_DOWNLOAD_SLEEP_INTERVAL}s (elapsed ${elapsed}s)..."
|
||||||
|
|
||||||
|
if (( elapsed >= NIX_DOWNLOAD_MAX_TIME )); then
|
||||||
|
echo "[init-nix] ERROR: Giving up after ${elapsed}s trying to download Nix installer."
|
||||||
|
rm -f "$installer"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep "$NIX_DOWNLOAD_SLEEP_INTERVAL"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n "$run_as" ]]; then
|
||||||
|
chown "$run_as:$run_as" "$installer" 2>/dev/null || true
|
||||||
|
echo "[init-nix] Running installer as user '$run_as' ($mode_flag)..."
|
||||||
|
if command -v sudo >/dev/null 2>&1; then
|
||||||
|
sudo -u "$run_as" bash -lc "sh '$installer' $mode_flag"
|
||||||
|
else
|
||||||
|
su - "$run_as" -c "sh '$installer' $mode_flag"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[init-nix] Running installer as current user ($mode_flag)..."
|
||||||
|
sh "$installer" "$mode_flag"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$installer"
|
||||||
|
}
|
||||||
68
scripts/nix/lib/path.sh
Executable file
68
scripts/nix/lib/path.sh
Executable file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [[ -n "${PKGMGR_NIX_PATH_SH:-}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
PKGMGR_NIX_PATH_SH=1
|
||||||
|
|
||||||
|
# Ensure Nix binaries are on PATH (additive, never destructive)
|
||||||
|
ensure_nix_on_path() {
|
||||||
|
if [[ -x /nix/var/nix/profiles/default/bin/nix ]]; then
|
||||||
|
PATH="/nix/var/nix/profiles/default/bin:$PATH"
|
||||||
|
fi
|
||||||
|
if [[ -x "$HOME/.nix-profile/bin/nix" ]]; then
|
||||||
|
PATH="$HOME/.nix-profile/bin:$PATH"
|
||||||
|
fi
|
||||||
|
if [[ -x /home/nix/.nix-profile/bin/nix ]]; then
|
||||||
|
PATH="/home/nix/.nix-profile/bin:$PATH"
|
||||||
|
fi
|
||||||
|
if [[ -d "$HOME/.local/bin" ]]; then
|
||||||
|
PATH="$HOME/.local/bin:$PATH"
|
||||||
|
fi
|
||||||
|
export PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve a path to a real executable (follows symlinks)
|
||||||
|
real_exe() {
|
||||||
|
local p="${1:-}"
|
||||||
|
[[ -z "$p" ]] && return 1
|
||||||
|
|
||||||
|
local r
|
||||||
|
r="$(readlink -f "$p" 2>/dev/null || echo "$p")"
|
||||||
|
|
||||||
|
[[ -x "$r" ]] && { echo "$r"; return 0; }
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve nix binary path robustly (works across distros + Arch /usr/sbin)
|
||||||
|
resolve_nix_bin() {
|
||||||
|
local nix_cmd=""
|
||||||
|
nix_cmd="$(command -v nix 2>/dev/null || true)"
|
||||||
|
[[ -n "$nix_cmd" ]] && real_exe "$nix_cmd" && return 0
|
||||||
|
|
||||||
|
# IMPORTANT: prefer system locations before /usr/local to avoid self-symlink traps
|
||||||
|
[[ -x /usr/sbin/nix ]] && { echo "/usr/sbin/nix"; return 0; } # Arch package can land here
|
||||||
|
[[ -x /usr/bin/nix ]] && { echo "/usr/bin/nix"; return 0; }
|
||||||
|
[[ -x /bin/nix ]] && { echo "/bin/nix"; return 0; }
|
||||||
|
|
||||||
|
# /usr/local last, and only if it resolves to a real executable
|
||||||
|
[[ -e /usr/local/bin/nix ]] && real_exe "/usr/local/bin/nix" && return 0
|
||||||
|
|
||||||
|
[[ -x /nix/var/nix/profiles/default/bin/nix ]] && {
|
||||||
|
echo "/nix/var/nix/profiles/default/bin/nix"; return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -x "$HOME/.nix-profile/bin/nix" ]] && {
|
||||||
|
echo "$HOME/.nix-profile/bin/nix"; return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -x "$HOME/.local/bin/nix" ]] && {
|
||||||
|
echo "$HOME/.local/bin/nix"; return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -x /home/nix/.nix-profile/bin/nix ]] && {
|
||||||
|
echo "/home/nix/.nix-profile/bin/nix"; return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
95
scripts/nix/lib/symlinks.sh
Executable file
95
scripts/nix/lib/symlinks.sh
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [[ -n "${PKGMGR_NIX_SYMLINKS_SH:-}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
PKGMGR_NIX_SYMLINKS_SH=1
|
||||||
|
|
||||||
|
# Requires: real_exe, resolve_nix_bin
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
|
||||||
|
# Ensure globally reachable nix symlink(s) (CI / non-login shells) - root only
|
||||||
|
ensure_global_nix_symlinks() {
|
||||||
|
local nix_bin="${1:-}"
|
||||||
|
|
||||||
|
[[ -z "$nix_bin" ]] && nix_bin="$(resolve_nix_bin 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ -z "$nix_bin" || ! -x "$nix_bin" ]]; then
|
||||||
|
echo "[init-nix] WARNING: nix binary not found, cannot create global symlink(s)."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Always link to the real executable to avoid /usr/local/bin/nix -> /usr/local/bin/nix
|
||||||
|
nix_bin="$(real_exe "$nix_bin" 2>/dev/null || echo "$nix_bin")"
|
||||||
|
|
||||||
|
local targets=()
|
||||||
|
|
||||||
|
# Always provide /usr/local/bin/nix for CI shells
|
||||||
|
mkdir -p /usr/local/bin 2>/dev/null || true
|
||||||
|
targets+=("/usr/local/bin/nix")
|
||||||
|
|
||||||
|
# Provide sudo-friendly locations only if they are NOT present (do not override distro paths)
|
||||||
|
if [[ ! -e /usr/bin/nix ]]; then
|
||||||
|
targets+=("/usr/bin/nix")
|
||||||
|
fi
|
||||||
|
if [[ ! -e /usr/sbin/nix ]]; then
|
||||||
|
targets+=("/usr/sbin/nix")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local target current_real
|
||||||
|
for target in "${targets[@]}"; do
|
||||||
|
current_real=""
|
||||||
|
if [[ -e "$target" ]]; then
|
||||||
|
current_real="$(real_exe "$target" 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$current_real" && "$current_real" == "$nix_bin" ]]; then
|
||||||
|
echo "[init-nix] $target already points to: $nix_bin"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If something exists but is not the same (and we promised not to override), skip.
|
||||||
|
if [[ -e "$target" && "$target" != "/usr/local/bin/nix" ]]; then
|
||||||
|
echo "[init-nix] WARNING: $target exists; not overwriting."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ln -sf "$nix_bin" "$target" 2>/dev/null; then
|
||||||
|
echo "[init-nix] Ensured $target -> $nix_bin"
|
||||||
|
else
|
||||||
|
echo "[init-nix] WARNING: Failed to ensure $target symlink."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure user-level nix symlink (works without root; CI-safe)
|
||||||
|
ensure_user_nix_symlink() {
|
||||||
|
local nix_bin="${1:-}"
|
||||||
|
|
||||||
|
[[ -z "$nix_bin" ]] && nix_bin="$(resolve_nix_bin 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ -z "$nix_bin" || ! -x "$nix_bin" ]]; then
|
||||||
|
echo "[init-nix] WARNING: nix binary not found, cannot create user symlink."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
nix_bin="$(real_exe "$nix_bin" 2>/dev/null || echo "$nix_bin")"
|
||||||
|
|
||||||
|
mkdir -p "$HOME/.local/bin" 2>/dev/null || true
|
||||||
|
ln -sf "$nix_bin" "$HOME/.local/bin/nix"
|
||||||
|
|
||||||
|
echo "[init-nix] Ensured $HOME/.local/bin/nix -> $nix_bin"
|
||||||
|
|
||||||
|
PATH="$HOME/.local/bin:$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
if [[ -w "$HOME/.profile" ]] && ! grep -q 'nix/init.sh' "$HOME/.profile" 2>/dev/null; then
|
||||||
|
cat >>"$HOME/.profile" <<'EOF'
|
||||||
|
|
||||||
|
# PATH for nix (added by package-manager nix/init.sh)
|
||||||
|
if [ -d "$HOME/.local/bin" ]; then
|
||||||
|
PATH="$HOME/.local/bin:$PATH"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
}
|
||||||
49
scripts/nix/lib/users.sh
Executable file
49
scripts/nix/lib/users.sh
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [[ -n "${PKGMGR_NIX_USERS_SH:-}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
PKGMGR_NIX_USERS_SH=1
|
||||||
|
|
||||||
|
# Ensure Nix build group and users exist (build-users-group = nixbld) - root only
|
||||||
|
ensure_nix_build_group() {
|
||||||
|
if ! getent group nixbld >/dev/null 2>&1; then
|
||||||
|
echo "[init-nix] Creating group 'nixbld'..."
|
||||||
|
groupadd -r nixbld
|
||||||
|
fi
|
||||||
|
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
if ! id "nixbld$i" >/dev/null 2>&1; then
|
||||||
|
echo "[init-nix] Creating build user nixbld$i..."
|
||||||
|
useradd -r -g nixbld -G nixbld -s /usr/sbin/nologin "nixbld$i"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Container-only helper: /nix ownership + perms for single-user install as 'nix'
|
||||||
|
ensure_nix_store_dir_for_container_user() {
|
||||||
|
if [[ ! -d /nix ]]; then
|
||||||
|
echo "[init-nix] Creating /nix with owner nix:nixbld..."
|
||||||
|
mkdir -m 0755 /nix
|
||||||
|
chown nix:nixbld /nix
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local current_owner current_group
|
||||||
|
current_owner="$(stat -c '%U' /nix 2>/dev/null || echo '?')"
|
||||||
|
current_group="$(stat -c '%G' /nix 2>/dev/null || echo '?')"
|
||||||
|
if [[ "$current_owner" != "nix" || "$current_group" != "nixbld" ]]; then
|
||||||
|
echo "[init-nix] Fixing /nix ownership from $current_owner:$current_group to nix:nixbld..."
|
||||||
|
chown -R nix:nixbld /nix
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Container-only helper: make nix profile executable/traversable for non-root
|
||||||
|
ensure_container_profile_perms() {
|
||||||
|
if [[ -d /home/nix ]]; then
|
||||||
|
chmod o+rx /home/nix 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [[ -d /home/nix/.nix-profile ]]; then
|
||||||
|
chmod -R o+rx /home/nix/.nix-profile 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -28,11 +28,11 @@ if ! command -v nix >/dev/null 2>&1; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# If nix is still missing, try to run init-nix.sh once
|
# If nix is still missing, try to run nix/init.sh once
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
if ! command -v nix >/dev/null 2>&1; then
|
if ! command -v nix >/dev/null 2>&1; then
|
||||||
if [[ -x "${FLAKE_DIR}/init-nix.sh" ]]; then
|
if [[ -x "${FLAKE_DIR}/nix/init.sh" ]]; then
|
||||||
"${FLAKE_DIR}/init-nix.sh" || true
|
"${FLAKE_DIR}/nix/init.sh" || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
9
scripts/setup/nix.sh
Executable file
9
scripts/setup/nix.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
# ------------------------------------------------------------
|
||||||
|
# Nix shell mode: do not touch venv, only run install
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
echo "[setup] Nix mode enabled (NIX_ENABLED=1)."
|
||||||
|
echo "[setup] Skipping virtualenv creation and dependency installation."
|
||||||
|
echo "[setup] Running install via system python3..."
|
||||||
|
python3 -m pkgmgr install
|
||||||
|
echo "[setup] Setup finished (Nix mode)."
|
||||||
95
scripts/setup/venv.sh
Executable file
95
scripts/setup/venv.sh
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "[setup] Starting 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'
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Normal user mode: dev setup with venv
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
echo "[setup] Running in normal user mode (developer setup)."
|
||||||
|
|
||||||
|
echo "[setup] Ensuring global virtualenv root: ${HOME}/.venvs"
|
||||||
|
mkdir -p "${HOME}/.venvs"
|
||||||
|
|
||||||
|
echo "[setup] Creating/updating virtualenv via helper..."
|
||||||
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
cd "${PROJECT_ROOT}"
|
||||||
|
|
||||||
|
PIP_EDITABLE="${PKGMGR_PIP_EDITABLE:-1}"
|
||||||
|
PIP_EXTRAS="${PKGMGR_PIP_EXTRAS:-}"
|
||||||
|
PREFER_NIX="${PKGMGR_PREFER_NIX:-0}"
|
||||||
|
|
||||||
|
echo "[venv] Using VENV_DIR=${VENV_DIR}"
|
||||||
|
|
||||||
|
if [[ "${PREFER_NIX}" == "1" ]]; then
|
||||||
|
echo "[venv] PKGMGR_PREFER_NIX=1 set."
|
||||||
|
echo "[venv] Hint: Use Nix instead of a venv for reproducible installs:"
|
||||||
|
echo "[venv] nix develop"
|
||||||
|
echo "[venv] nix run .#pkgmgr -- --help"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[venv] Ensuring virtualenv parent directory exists..."
|
||||||
|
mkdir -p "$(dirname "${VENV_DIR}")"
|
||||||
|
|
||||||
|
if [[ ! -d "${VENV_DIR}" ]]; then
|
||||||
|
echo "[venv] Creating virtual environment at: ${VENV_DIR}"
|
||||||
|
python3 -m venv "${VENV_DIR}"
|
||||||
|
else
|
||||||
|
echo "[venv] Virtual environment already exists at: ${VENV_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[venv] Installing Python tooling into venv..."
|
||||||
|
"${VENV_DIR}/bin/python" -m ensurepip --upgrade
|
||||||
|
"${VENV_DIR}/bin/pip" install --upgrade pip setuptools wheel
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Install dependencies
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [[ -f "pyproject.toml" ]]; then
|
||||||
|
echo "[venv] Detected pyproject.toml. Installing project via pip..."
|
||||||
|
|
||||||
|
target="."
|
||||||
|
if [[ -n "${PIP_EXTRAS}" ]]; then
|
||||||
|
target=".[${PIP_EXTRAS}]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${PIP_EDITABLE}" == "1" ]]; then
|
||||||
|
echo "[venv] pip install -e ${target}"
|
||||||
|
"${VENV_DIR}/bin/pip" install -e "${target}"
|
||||||
|
else
|
||||||
|
echo "[venv] pip install ${target}"
|
||||||
|
"${VENV_DIR}/bin/pip" install "${target}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[venv] No pyproject.toml found. Skipping dependency installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[venv] Done."
|
||||||
|
|
||||||
|
echo "[setup] Ensuring ~/.bashrc and ~/.zshrc exist..."
|
||||||
|
touch "${HOME}/.bashrc" "${HOME}/.zshrc"
|
||||||
|
|
||||||
|
echo "[setup] 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 "[setup] Appended auto-activation to $rc"
|
||||||
|
else
|
||||||
|
echo "[setup] Auto-activation already present in $rc"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[setup] Running install via venv Python..."
|
||||||
|
"${VENV_DIR}/bin/python" -m pkgmgr install
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "[setup] Developer setup complete."
|
||||||
|
echo "Restart your shell (or run 'exec bash' or 'exec zsh') to activate the environment."
|
||||||
@@ -9,10 +9,10 @@ docker run --rm \
|
|||||||
-v "$(pwd):/src" \
|
-v "$(pwd):/src" \
|
||||||
-v "pkgmgr_nix_store_${distro}:/nix" \
|
-v "pkgmgr_nix_store_${distro}:/nix" \
|
||||||
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
||||||
-e PKGMGR_DEV=1 \
|
-e REINSTALL_PKGMGR=1 \
|
||||||
-e TEST_PATTERN="${TEST_PATTERN}" \
|
-e TEST_PATTERN="${TEST_PATTERN}" \
|
||||||
--workdir /src \
|
--workdir /src \
|
||||||
"package-manager-test-${distro}" \
|
"pkgmgr-${distro}" \
|
||||||
bash -lc '
|
bash -lc '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|||||||
4
scripts/test/test-env-nix.sh
Normal file → Executable file
4
scripts/test/test-env-nix.sh
Normal file → Executable file
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
IMAGE="package-manager-test-${distro}"
|
IMAGE="pkgmgr-${distro}"
|
||||||
|
|
||||||
echo "============================================================"
|
echo "============================================================"
|
||||||
echo ">>> Running Nix flake-only test in ${distro} container"
|
echo ">>> Running Nix flake-only test in ${distro} container"
|
||||||
@@ -13,7 +13,7 @@ docker run --rm \
|
|||||||
-v "pkgmgr_nix_store_${distro}:/nix" \
|
-v "pkgmgr_nix_store_${distro}:/nix" \
|
||||||
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
||||||
--workdir /src \
|
--workdir /src \
|
||||||
-e PKGMGR_DEV=0 \
|
-e REINSTALL_PKGMGR=1 \
|
||||||
"${IMAGE}" \
|
"${IMAGE}" \
|
||||||
bash -lc '
|
bash -lc '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
IMAGE="package-manager-test-$distro"
|
IMAGE="pkgmgr-$distro"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "------------------------------------------------------------"
|
echo "------------------------------------------------------------"
|
||||||
@@ -15,7 +15,7 @@ echo
|
|||||||
|
|
||||||
# Run the command and capture the output
|
# Run the command and capture the output
|
||||||
if OUTPUT=$(docker run --rm \
|
if OUTPUT=$(docker run --rm \
|
||||||
-e PKGMGR_DEV=1 \
|
-e REINSTALL_PKGMGR=1 \
|
||||||
-v pkgmgr_nix_store_${distro}:/nix \
|
-v pkgmgr_nix_store_${distro}:/nix \
|
||||||
-v "$(pwd):/src" \
|
-v "$(pwd):/src" \
|
||||||
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ docker run --rm \
|
|||||||
-v pkgmgr_nix_store_${distro}:/nix \
|
-v pkgmgr_nix_store_${distro}:/nix \
|
||||||
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
||||||
--workdir /src \
|
--workdir /src \
|
||||||
-e PKGMGR_DEV=1 \
|
-e REINSTALL_PKGMGR=1 \
|
||||||
-e TEST_PATTERN="${TEST_PATTERN}" \
|
-e TEST_PATTERN="${TEST_PATTERN}" \
|
||||||
"package-manager-test-${distro}" \
|
"pkgmgr-${distro}" \
|
||||||
bash -lc '
|
bash -lc '
|
||||||
set -e;
|
set -e;
|
||||||
git config --global --add safe.directory /src || true;
|
git config --global --add safe.directory /src || true;
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ docker run --rm \
|
|||||||
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
|
||||||
-v pkgmgr_nix_store_${distro}:/nix \
|
-v pkgmgr_nix_store_${distro}:/nix \
|
||||||
--workdir /src \
|
--workdir /src \
|
||||||
-e PKGMGR_DEV=1 \
|
-e REINSTALL_PKGMGR=1 \
|
||||||
-e TEST_PATTERN="${TEST_PATTERN}" \
|
-e TEST_PATTERN="${TEST_PATTERN}" \
|
||||||
"package-manager-test-${distro}" \
|
"pkgmgr-${distro}" \
|
||||||
bash -lc '
|
bash -lc '
|
||||||
set -e;
|
set -e;
|
||||||
git config --global --add safe.directory /src || true;
|
git config --global --add safe.directory /src || true;
|
||||||
|
|||||||
5
src/pkgmgr/__main__.py
Executable file
5
src/pkgmgr/__main__.py
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from pkgmgr.cli import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pkgmgr.core.git import run_git, GitError, get_current_branch
|
from pkgmgr.core.git import run_git, GitError, get_current_branch
|
||||||
from .utils import _resolve_base_branch
|
from .utils import _resolve_base_branch
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pkgmgr.core.git import run_git, GitError, get_current_branch
|
from pkgmgr.core.git import run_git, GitError, get_current_branch
|
||||||
from .utils import _resolve_base_branch
|
from .utils import _resolve_base_branch
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pkgmgr.core.git import run_git, GitError
|
from pkgmgr.core.git import run_git, GitError
|
||||||
from .utils import _resolve_base_branch
|
from .utils import _resolve_base_branch
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Responsibilities:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
@@ -63,7 +63,7 @@ def _ensure_repo_dir(
|
|||||||
no_verification: bool,
|
no_verification: bool,
|
||||||
clone_mode: str,
|
clone_mode: str,
|
||||||
identifier: str,
|
identifier: str,
|
||||||
) -> str | None:
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Compute and, if necessary, clone the repository directory.
|
Compute and, if necessary, clone the repository directory.
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from __future__ import annotations
|
|||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Iterable, TYPE_CHECKING
|
from typing import Iterable, TYPE_CHECKING, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pkgmgr.actions.install.context import RepoContext
|
from pkgmgr.actions.install.context import RepoContext
|
||||||
@@ -46,7 +46,7 @@ if TYPE_CHECKING:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _read_text_if_exists(path: str) -> str | None:
|
def _read_text_if_exists(path: str) -> Optional[str]:
|
||||||
"""Read a file as UTF-8 text, returning None if it does not exist or fails."""
|
"""Read a file as UTF-8 text, returning None if it does not exist or fails."""
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
return None
|
return None
|
||||||
@@ -75,7 +75,7 @@ def _scan_files_for_patterns(files: Iterable[str], patterns: Iterable[str]) -> b
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _first_spec_file(repo_dir: str) -> str | None:
|
def _first_spec_file(repo_dir: str) -> Optional[str]:
|
||||||
"""Return the first *.spec file in repo_dir, if any."""
|
"""Return the first *.spec file in repo_dir, if any."""
|
||||||
matches = glob.glob(os.path.join(repo_dir, "*.spec"))
|
matches = glob.glob(os.path.join(repo_dir, "*.spec"))
|
||||||
if not matches:
|
if not matches:
|
||||||
@@ -360,7 +360,7 @@ def detect_capabilities(
|
|||||||
|
|
||||||
def resolve_effective_capabilities(
|
def resolve_effective_capabilities(
|
||||||
ctx: "RepoContext",
|
ctx: "RepoContext",
|
||||||
layers: Iterable[str] | None = None,
|
layers: Optional[Iterable[str]] = None,
|
||||||
) -> dict[str, set[str]]:
|
) -> dict[str, set[str]]:
|
||||||
"""
|
"""
|
||||||
Resolve *effective* capabilities for each layer using a bottom-up strategy.
|
Resolve *effective* capabilities for each layer using a bottom-up strategy.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Base interface for all installer components in the pkgmgr installation pipeline.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Set
|
from typing import Set, Optional
|
||||||
|
|
||||||
from pkgmgr.actions.install.context import RepoContext
|
from pkgmgr.actions.install.context import RepoContext
|
||||||
from pkgmgr.actions.install.capabilities import CAPABILITY_MATCHERS
|
from pkgmgr.actions.install.capabilities import CAPABILITY_MATCHERS
|
||||||
@@ -24,7 +24,7 @@ class BaseInstaller(ABC):
|
|||||||
# Examples: "nix", "python", "makefile".
|
# Examples: "nix", "python", "makefile".
|
||||||
# This is used by capability matchers to decide which patterns to
|
# This is used by capability matchers to decide which patterns to
|
||||||
# search for in the repository.
|
# search for in the repository.
|
||||||
layer: str | None = None
|
layer: Optional[str] = None
|
||||||
|
|
||||||
def discover_capabilities(self, ctx: RepoContext) -> Set[str]:
|
def discover_capabilities(self, ctx: RepoContext) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ apt/dpkg tooling are available.
|
|||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
from pkgmgr.actions.install.context import RepoContext
|
from pkgmgr.actions.install.context import RepoContext
|
||||||
from pkgmgr.actions.install.installers.base import BaseInstaller
|
from pkgmgr.actions.install.installers.base import BaseInstaller
|
||||||
@@ -67,7 +67,7 @@ class DebianControlInstaller(BaseInstaller):
|
|||||||
pattern = os.path.join(parent, "*.deb")
|
pattern = os.path.join(parent, "*.deb")
|
||||||
return sorted(glob.glob(pattern))
|
return sorted(glob.glob(pattern))
|
||||||
|
|
||||||
def _privileged_prefix(self) -> str | None:
|
def _privileged_prefix(self) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Determine how to run privileged commands:
|
Determine how to run privileged commands:
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import List, Optional, Set
|
|
||||||
|
|
||||||
from pkgmgr.core.command.run import run_command
|
from pkgmgr.core.command.run import run_command
|
||||||
from pkgmgr.core.git import GitError, run_git
|
from pkgmgr.core.git import GitError, run_git
|
||||||
|
from typing import List, Optional, Set
|
||||||
|
|
||||||
from .types import MirrorMap, RepoMirrorContext, Repository
|
from .types import MirrorMap, RepoMirrorContext, Repository
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ USER_CONFIG_PATH = os.path.expanduser("~/.config/pkgmgr/config.yaml")
|
|||||||
DESCRIPTION_TEXT = """\
|
DESCRIPTION_TEXT = """\
|
||||||
\033[1;32mPackage Manager 🤖📦\033[0m
|
\033[1;32mPackage Manager 🤖📦\033[0m
|
||||||
\033[3mKevin's multi-distro package and workflow manager.\033[0m
|
\033[3mKevin's multi-distro package and workflow manager.\033[0m
|
||||||
\033[1;34mKevin Veen-Birkenbach\033[0m – \033[4mhttps://www.veen.world/\033[0m
|
\033[1;34mKevin Veen-Birkenbach\033[0m – \033[4mhttps://s.veen.world/pkgmgr\033[0m
|
||||||
|
|
||||||
Built in \033[1;33mPython\033[0m on top of \033[1;33mNix flakes\033[0m to manage many
|
Built in \033[1;33mPython\033[0m on top of \033[1;33mNix flakes\033[0m to manage many
|
||||||
repositories and packaging formats (pyproject.toml, flake.nix,
|
repositories and packaging formats (pyproject.toml, flake.nix,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ def _load_user_config(user_config_path: str) -> Dict[str, Any]:
|
|||||||
return {"repositories": []}
|
return {"repositories": []}
|
||||||
|
|
||||||
|
|
||||||
def _find_defaults_source_dir() -> str | None:
|
def _find_defaults_source_dir() -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Find the directory inside the installed pkgmgr package OR the
|
Find the directory inside the installed pkgmgr package OR the
|
||||||
project root that contains default config files.
|
project root that contains default config files.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from typing import Optional
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from typing import Optional
|
||||||
# pkgmgr/run_command.py
|
# pkgmgr/run_command.py
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Tuple
|
from typing import Any, Dict, List, Tuple, Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ def _repo_key(repo: Repo) -> Tuple[str, str, str]:
|
|||||||
def _merge_repo_lists(
|
def _merge_repo_lists(
|
||||||
base_list: List[Repo],
|
base_list: List[Repo],
|
||||||
new_list: List[Repo],
|
new_list: List[Repo],
|
||||||
category_name: str | None = None,
|
category_name: Optional[str] = None,
|
||||||
) -> List[Repo]:
|
) -> List[Repo]:
|
||||||
"""
|
"""
|
||||||
Merge two repository lists, matching by (provider, account, repository).
|
Merge two repository lists, matching by (provider, account, repository).
|
||||||
@@ -143,7 +143,7 @@ def _load_yaml_file(path: Path) -> Dict[str, Any]:
|
|||||||
|
|
||||||
def _load_layer_dir(
|
def _load_layer_dir(
|
||||||
config_dir: Path,
|
config_dir: Path,
|
||||||
skip_filename: str | None = None,
|
skip_filename: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Load all *.yml/*.yaml from a directory as layered defaults.
|
Load all *.yml/*.yaml from a directory as layered defaults.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class TestIntegrationBranchCommands(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
# argv[0] is the program name; the rest are CLI arguments.
|
# argv[0] is the program name; the rest are CLI arguments.
|
||||||
sys.argv = ["pkgmgr"] + list(extra_args)
|
sys.argv = ["pkgmgr"] + list(extra_args)
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
finally:
|
finally:
|
||||||
sys.argv = original_argv
|
sys.argv = original_argv
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def _run_pkgmgr_help(argv_tail: list[str]) -> str:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with redirect_stdout(buffer), redirect_stderr(buffer):
|
with redirect_stdout(buffer), redirect_stderr(buffer):
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
code = exc.code if isinstance(exc.code, int) else None
|
code = exc.code if isinstance(exc.code, int) else None
|
||||||
if code not in (0, None):
|
if code not in (0, None):
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class TestIntegrationChangelogCommands(unittest.TestCase):
|
|||||||
sys.argv = ["pkgmgr", "changelog"] + list(extra_args)
|
sys.argv = ["pkgmgr", "changelog"] + list(extra_args)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class TestIntegrationCloneAllHttps(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
# Execute main.py as if it was called from CLI.
|
# Execute main.py as if it was called from CLI.
|
||||||
# This will run the full clone pipeline inside the container.
|
# This will run the full clone pipeline inside the container.
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
# Determine the exit code (int or string)
|
# Determine the exit code (int or string)
|
||||||
exit_code = exc.code
|
exit_code = exc.code
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ def _run_pkgmgr_config(extra_args: list[str]) -> None:
|
|||||||
sys.argv = ["pkgmgr"] + extra_args
|
sys.argv = ["pkgmgr"] + extra_args
|
||||||
|
|
||||||
try:
|
try:
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class TestIntegrationInstalPKGMGRShallow(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Execute installation via main.py
|
# Execute installation via main.py
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
|
|
||||||
# Debug: interactive shell test
|
# Debug: interactive shell test
|
||||||
pkgmgr_help_debug()
|
pkgmgr_help_debug()
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class TestIntegrationListCommands(unittest.TestCase):
|
|||||||
sys.argv = ["pkgmgr"] + args
|
sys.argv = ["pkgmgr"] + args
|
||||||
|
|
||||||
try:
|
try:
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class TestIntegrationMakeCommands(unittest.TestCase):
|
|||||||
sys.argv = ["pkgmgr"] + extra_args
|
sys.argv = ["pkgmgr"] + extra_args
|
||||||
|
|
||||||
try:
|
try:
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class TestIntegrationMirrorCommands(unittest.TestCase):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with redirect_stdout(buffer), redirect_stderr(buffer):
|
with redirect_stdout(buffer), redirect_stderr(buffer):
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
code = exc.code if isinstance(exc.code, int) else None
|
code = exc.code if isinstance(exc.code, int) else None
|
||||||
if code not in (0, None):
|
if code not in (0, None):
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class TestPathCommandsE2E(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
# Capture stdout while running the CLI entry point.
|
# Capture stdout while running the CLI entry point.
|
||||||
with redirect_stdout(buffer):
|
with redirect_stdout(buffer):
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
# Determine the exit code (int or string)
|
# Determine the exit code (int or string)
|
||||||
exit_code = exc.code
|
exit_code = exc.code
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class TestIntegrationProxyCommands(unittest.TestCase):
|
|||||||
sys.argv = ["pkgmgr"] + args
|
sys.argv = ["pkgmgr"] + args
|
||||||
|
|
||||||
try:
|
try:
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class TestIntegrationReleaseCommand(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
# argv[0] is the program name; the rest are CLI arguments.
|
# argv[0] is the program name; the rest are CLI arguments.
|
||||||
sys.argv = ["pkgmgr"] + list(extra_args)
|
sys.argv = ["pkgmgr"] + list(extra_args)
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
finally:
|
finally:
|
||||||
sys.argv = original_argv
|
sys.argv = original_argv
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ class TestIntegrationReleaseCommand(unittest.TestCase):
|
|||||||
# argparse will call sys.exit(), so we expect a SystemExit here.
|
# argparse will call sys.exit(), so we expect a SystemExit here.
|
||||||
with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):
|
with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):
|
||||||
with self.assertRaises(SystemExit) as cm:
|
with self.assertRaises(SystemExit) as cm:
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
finally:
|
finally:
|
||||||
sys.argv = original_argv
|
sys.argv = original_argv
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class TestIntegrationToolsCommands(unittest.TestCase):
|
|||||||
sys.argv = ["pkgmgr"] + extra_args
|
sys.argv = ["pkgmgr"] + extra_args
|
||||||
|
|
||||||
try:
|
try:
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
|
|||||||
@@ -18,14 +18,6 @@ import sys
|
|||||||
import unittest
|
import unittest
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
# Resolve project root (the repo where main.py lives, e.g. /src)
|
|
||||||
PROJECT_ROOT = os.path.abspath(
|
|
||||||
os.path.join(os.path.dirname(__file__), "..", "..")
|
|
||||||
)
|
|
||||||
MAIN_PATH = os.path.join(PROJECT_ROOT, "main.py")
|
|
||||||
|
|
||||||
|
|
||||||
def _run_main(argv: List[str]) -> None:
|
def _run_main(argv: List[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Helper to run main.py with the given argv.
|
Helper to run main.py with the given argv.
|
||||||
@@ -40,7 +32,7 @@ def _run_main(argv: List[str]) -> None:
|
|||||||
try:
|
try:
|
||||||
sys.argv = ["pkgmgr"] + argv
|
sys.argv = ["pkgmgr"] + argv
|
||||||
try:
|
try:
|
||||||
runpy.run_path(MAIN_PATH, run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc: # argparse uses this for --help
|
except SystemExit as exc: # argparse uses this for --help
|
||||||
# SystemExit.code can be int, str or None; for our purposes:
|
# SystemExit.code can be int, str or None; for our purposes:
|
||||||
code = exc.code
|
code = exc.code
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ This test is intended to be run inside the Docker container where:
|
|||||||
- the config/config.yaml is present,
|
- the config/config.yaml is present,
|
||||||
- and it is safe to perform real git operations.
|
- and it is safe to perform real git operations.
|
||||||
|
|
||||||
It passes if the command completes without raising an exception.
|
It passes if BOTH commands complete successfully (in separate tests):
|
||||||
|
1) pkgmgr update --all --clone-mode https --no-verification
|
||||||
|
2) nix run .#pkgmgr -- update --all --clone-mode https --no-verification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import runpy
|
import os
|
||||||
import sys
|
import subprocess
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from test_install_pkgmgr_shallow import (
|
from test_install_pkgmgr_shallow import (
|
||||||
@@ -22,55 +24,35 @@ from test_install_pkgmgr_shallow import (
|
|||||||
|
|
||||||
|
|
||||||
class TestIntegrationUpdateAllHttps(unittest.TestCase):
|
class TestIntegrationUpdateAllHttps(unittest.TestCase):
|
||||||
def _run_pkgmgr_update_all_https(self) -> None:
|
def _run_cmd(self, cmd: list[str], label: str) -> None:
|
||||||
"""
|
"""
|
||||||
Helper that runs the CLI command via main.py and provides
|
Run a real CLI command and raise a helpful assertion on failure.
|
||||||
extra diagnostics if the command exits with a non-zero code.
|
|
||||||
"""
|
"""
|
||||||
cmd_repr = "pkgmgr update --all --clone-mode https --no-verification"
|
cmd_repr = " ".join(cmd)
|
||||||
original_argv = sys.argv
|
env = os.environ.copy()
|
||||||
try:
|
|
||||||
sys.argv = [
|
|
||||||
"pkgmgr",
|
|
||||||
"update",
|
|
||||||
"--all",
|
|
||||||
"--clone-mode",
|
|
||||||
"https",
|
|
||||||
"--no-verification",
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Execute main.py as if it was called from CLI.
|
print(f"\n[TEST] Running ({label}): {cmd_repr}")
|
||||||
# This will run the full update pipeline inside the container.
|
subprocess.run(
|
||||||
runpy.run_module("main", run_name="__main__")
|
cmd,
|
||||||
except SystemExit as exc:
|
check=True,
|
||||||
# Convert SystemExit into a more helpful assertion with debug output.
|
cwd=os.getcwd(),
|
||||||
exit_code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
env=env,
|
||||||
|
text=True,
|
||||||
print("\n[TEST] pkgmgr update --all failed with SystemExit")
|
)
|
||||||
|
except subprocess.CalledProcessError as exc:
|
||||||
|
print(f"\n[TEST] Command failed ({label})")
|
||||||
print(f"[TEST] Command : {cmd_repr}")
|
print(f"[TEST] Command : {cmd_repr}")
|
||||||
print(f"[TEST] Exit code: {exit_code}")
|
print(f"[TEST] Exit code: {exc.returncode}")
|
||||||
|
|
||||||
# Additional Nix profile debug on failure (useful if any update
|
nix_profile_list_debug(f"ON FAILURE ({label})")
|
||||||
# step interacts with Nix-based tooling).
|
|
||||||
nix_profile_list_debug("ON FAILURE (AFTER SystemExit)")
|
|
||||||
|
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
f"{cmd_repr!r} failed with exit code {exit_code}. "
|
f"({label}) {cmd_repr!r} failed with exit code {exc.returncode}. "
|
||||||
"Scroll up to see the full pkgmgr/make output inside the container."
|
"Scroll up to see the full pkgmgr/nix output inside the container."
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
finally:
|
def _common_setup(self) -> None:
|
||||||
sys.argv = original_argv
|
|
||||||
|
|
||||||
def test_update_all_repositories_https(self) -> None:
|
|
||||||
"""
|
|
||||||
Run: pkgmgr update --all --clone-mode https --no-verification
|
|
||||||
|
|
||||||
This will perform real git update operations inside the container.
|
|
||||||
The test succeeds if no exception is raised and `pkgmgr --help`
|
|
||||||
works in a fresh interactive bash session afterwards.
|
|
||||||
"""
|
|
||||||
# Debug before cleanup
|
# Debug before cleanup
|
||||||
nix_profile_list_debug("BEFORE CLEANUP")
|
nix_profile_list_debug("BEFORE CLEANUP")
|
||||||
|
|
||||||
@@ -81,11 +63,28 @@ class TestIntegrationUpdateAllHttps(unittest.TestCase):
|
|||||||
# Debug after cleanup
|
# Debug after cleanup
|
||||||
nix_profile_list_debug("AFTER CLEANUP")
|
nix_profile_list_debug("AFTER CLEANUP")
|
||||||
|
|
||||||
# Run the actual update with extended diagnostics
|
def test_update_all_repositories_https_pkgmgr(self) -> None:
|
||||||
self._run_pkgmgr_update_all_https()
|
"""
|
||||||
|
Run: pkgmgr update --all --clone-mode https --no-verification
|
||||||
|
"""
|
||||||
|
self._common_setup()
|
||||||
|
|
||||||
# After successful update: show `pkgmgr --help`
|
args = ["update", "--all", "--clone-mode", "https", "--no-verification"]
|
||||||
# via interactive bash (same helper as in the other integration tests).
|
self._run_cmd(["pkgmgr", *args], label="pkgmgr")
|
||||||
|
|
||||||
|
# After successful update: show `pkgmgr --help` via interactive bash
|
||||||
|
pkgmgr_help_debug()
|
||||||
|
|
||||||
|
def test_update_all_repositories_https_nix_pkgmgr(self) -> None:
|
||||||
|
"""
|
||||||
|
Run: nix run .#pkgmgr -- update --all --clone-mode https --no-verification
|
||||||
|
"""
|
||||||
|
self._common_setup()
|
||||||
|
|
||||||
|
args = ["update", "--all", "--clone-mode", "https", "--no-verification"]
|
||||||
|
self._run_cmd(["nix", "run", ".#pkgmgr", "--", *args], label="nix run .#pkgmgr")
|
||||||
|
|
||||||
|
# After successful update: show `pkgmgr --help` via interactive bash
|
||||||
pkgmgr_help_debug()
|
pkgmgr_help_debug()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class TestIntegrationVersionCommands(unittest.TestCase):
|
|||||||
sys.argv = ["pkgmgr", "version"] + extra_args
|
sys.argv = ["pkgmgr", "version"] + extra_args
|
||||||
|
|
||||||
try:
|
try:
|
||||||
runpy.run_module("main", run_name="__main__")
|
runpy.run_module("pkgmgr", run_name="__main__")
|
||||||
except SystemExit as exc:
|
except SystemExit as exc:
|
||||||
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
||||||
if code != 0:
|
if code != 0:
|
||||||
|
|||||||
Reference in New Issue
Block a user