Compare commits

...

31 Commits

Author SHA1 Message Date
Kevin Veen-Birkenbach
0482a7f88d Release version 1.4.0
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Publish container images (GHCR) / publish (push) Has been cancelled
2025-12-12 22:20:07 +01:00
Kevin Veen-Birkenbach
8c127cc45a ci: fix container publish workflow to run on version tag pushes
Switch publish-containers workflow from workflow_run to direct v* tag triggers,
remove obsolete workflow_run logic, simplify version detection via GITHUB_REF_NAME,
and keep stable-tag detection aligned with the stable ref.

https://chatgpt.com/share/693c836b-0b00-800f-9536-9e273abd0fb5
2025-12-12 22:17:32 +01:00
Kevin Veen-Birkenbach
2761e829cb ci: add GHCR container publish pipeline with semantic tags
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Introduce a dedicated publish-containers workflow triggered after stable releases.
Unify container build and publish logic via scripts, add buildx-based multi-tag publishing,
default base image resolution, and Arch alias tags for latest/version/stable.

https://chatgpt.com/share/693c836b-0b00-800f-9536-9e273abd0fb5
2025-12-12 22:04:39 +01:00
Kevin Veen-Birkenbach
d0c01b6955 Updated dependencies instructions
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 21:37:50 +01:00
Kevin Veen-Birkenbach
b2421c9b84 **Refactor OS detection and normalize Manjaro to Arch**
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
* Centralize OS detection and normalization in a dedicated resolver module
* Treat Manjaro consistently as Arch across dependencies and package install
* Remove duplicated OS logic and legacy lib.sh
* Rename installation entrypoint to init.sh and update Makefile accordingly

https://chatgpt.com/share/693c7b50-3be0-800f-8aeb-daf3ee929ea3
2025-12-12 21:30:03 +01:00
Kevin Veen-Birkenbach
f950bb493c Release version 1.3.1
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 21:16:27 +01:00
Kevin Veen-Birkenbach
fb0b81954d **Fix Nix bootstrap installation by shipping init script and libraries together**
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
* Install the complete Nix bootstrap (*init.sh* and *lib/*) into */usr/lib/package-manager/nix/* for Arch, Debian, and Fedora
* Align packaging paths with the expectations of the modularized *nix/init.sh*
* Prevent runtime failures caused by missing sourced library scripts

https://chatgpt.com/share/693c7159-b340-800f-929e-2515eeb0dd03
2025-12-12 21:02:26 +01:00
Kevin Veen-Birkenbach
b9b4c3fa59 **Refactor Nix init into modular scripts and update packaging paths**
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
* Move the Nix bootstrap from *scripts/init-nix.sh* to *scripts/nix/init.sh* with split-out helpers in *scripts/nix/lib/*
* Update Arch/Debian/Fedora packaging hooks to call */usr/lib/package-manager/nix/init.sh*
* Keep bootstrap behavior the same while improving maintainability and reuse

https://chatgpt.com/share/693c7159-b340-800f-929e-2515eeb0dd03
2025-12-12 20:47:31 +01:00
Kevin Veen-Birkenbach
3642f92776 Release version 1.3.0
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 20:35:02 +01:00
Kevin Veen-Birkenbach
8f38edde67 **Fix Nix global symlinks for sudo secure_path without overriding distro paths**
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
* Ensure nix is reachable for sudo on CentOS by providing /usr/bin and /usr/sbin fallbacks when absent
* Keep /usr/local/bin as primary CI path without breaking non-login shells
* Never overwrite distro-managed nix binaries (Arch-safe)
* Stabilize e2e and virgin-user tests across all distros

https://chatgpt.com/share/693c6013-af2c-800f-a1bc-baed0d29fab7
2025-12-12 20:23:29 +01:00
Kevin Veen-Birkenbach
5875441b23 **Fix Nix resolution and symlink handling on Arch without overriding system paths**
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
* Resolve the real *nix* executable to avoid self-referential symlink loops
* Prefer distro-managed paths (*/usr/sbin*, */usr/bin*) over */usr/local*
* Restrict global symlink creation to */usr/local/bin/nix* only
* Never overwrite Arch-managed */usr/bin/nix* or */bin/nix*
* Make CI and non-login shells reliable while preserving native Arch behavior

https://chatgpt.com/share/693c6013-af2c-800f-a1bc-baed0d29fab7
2025-12-12 20:05:17 +01:00
Kevin Veen-Birkenbach
9190f0d901 Fix init-nix so it works for non-root CI shells across distros
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
https://chatgpt.com/share/693c6013-af2c-800f-a1bc-baed0d29fab7
2025-12-12 19:50:25 +01:00
Kevin Veen-Birkenbach
f227734185 **Fix init-nix for CI and Arch shells**
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
* Simplify *init-nix.sh* while keeping container/host install paths
* Prefer canonical *nix* locations and avoid brittle PATH assumptions
* Ensure global *nix* symlinks for non-login shells (CI reliability)
* Keep retry download + nixbld bootstrap logic intact

https://chatgpt.com/share/693c6013-af2c-800f-a1bc-baed0d29fab7
2025-12-12 19:40:21 +01:00
Kevin Veen-Birkenbach
c7ef77559c Ensure nix is reachable in CI shells via robust lookup and global symlinks
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Add resolve_nix_bin to reliably locate the nix binary in non-login shells.
Create and enforce global nix symlinks for CI environments (/usr/local/bin, best-effort /usr/bin and /bin).
Apply symlink enforcement on fast path, after PATH adjustments, and post-install when running as root.
Improve warnings when nix is installed but not on PATH.

https://chatgpt.com/share/693c6013-af2c-800f-a1bc-baed0d29fab7
2025-12-12 19:33:52 +01:00
Kevin Veen-Birkenbach
2385601ed5 Persist CA bundle configuration on CentOS for Nix and HTTPS tools
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Move CA bundle detection from the Docker entrypoint to CentOS dependencies and persist it system-wide.
This ensures Nix, Git, curl, and Python HTTPS access works in virgin environments by configuring `/etc/profile.d` and `/etc/nix/nix.conf`.
Removes runtime-only CA exports from the container entrypoint and makes the setup reproducible and distro-correct.

https://chatgpt.com/share/693c5ddf-3260-800f-ac94-38c635dba307
2025-12-12 19:24:12 +01:00
Kevin Veen-Birkenbach
ac5ae95369 fix(py39): replace PEP 604 union types with Optional for Python 3.9 compatibility
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
- Replaced all `X | None` type hints with `Optional[X]`
- Adjusted typing imports across modules
- Fixed import order and removed invalid future-import placements
- Ensured code runs correctly on Python 3.9

https://chatgpt.com/share/693c58e1-ce70-800f-9088-5864571e024a
2025-12-12 19:02:54 +01:00
Kevin Veen-Birkenbach
31f7f47fe2 Downgraded python to 3.9 for CentOS
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 18:38:37 +01:00
Kevin Veen-Birkenbach
c8bf1c91ad **test(e2e): split update-all HTTPS integration test into pkgmgr and nix runs**
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Refactored the E2E update-all test to execute real CLI commands instead of invoking *main.py*.
The test is now split into two independent cases: one running *pkgmgr update* directly and one running the same command via *nix run .#pkgmgr*.
This improves realism, diagnostics, and parity with actual user workflows inside the container.

https://chatgpt.com/share/693c52cb-cc10-800f-994b-5b2940dcf948
2025-12-12 18:37:07 +01:00
Kevin Veen-Birkenbach
f2caa68e3d fix(nix): ensure non-root access to Nix installation with strict error handling
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Ensure /home/nix and .nix-profile are accessible for non-root users,
create /usr/local/bin/nix symlink with fail-fast behavior, and replace
silent permission fixes with explicit checks, clear error messages,
and deterministic exit codes.

https://chatgpt.com/share/693c29d9-9b28-800f-a549-5661c783d968
2025-12-12 18:19:51 +01:00
Kevin Veen-Birkenbach
03c232c308 Performance optimation for workflows
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 18:07:25 +01:00
Kevin Veen-Birkenbach
e882e17737 Changed CentOS to python 3.11
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 17:26:39 +01:00
Kevin Veen-Birkenbach
b9edcf7101 Patched python version for centos
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 17:12:30 +01:00
Kevin Veen-Birkenbach
8b8ebf329f Added venv to debian and ubuntu virgin
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 17:04:34 +01:00
Kevin Veen-Birkenbach
9598c17ea0 Added python dependency to virgin container
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 16:58:12 +01:00
Kevin Veen-Birkenbach
67bd358e12 fix(docker): enforce bash shell to support pipefail across distros
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Use bash as the default shell in Docker build stages to ensure
`set -euo pipefail` works reliably on all base images, including
Ubuntu where /bin/sh does not support pipefail.

https://chatgpt.com/share/693c29d9-9b28-800f-a549-5661c783d968
2025-12-12 16:50:32 +01:00
Kevin Veen-Birkenbach
340c1700dc Added missing 'make' to ubuntu
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 16:42:45 +01:00
Kevin Veen-Birkenbach
0dfbaa0f6b ci/docker: unify image build logic and run virgin tests across all distros
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Refactor Dockerfile into multi-stage virgin/full targets and introduce a single
flag-based image build script. Standardize image naming, remove redundant build
scripts, and update Makefile targets accordingly. CI workflows now build missing
virgin images and run root and user tests consistently across all supported
distributions.

https://chatgpt.com/share/693c29d9-9b28-800f-a549-5661c783d968
2025-12-12 16:40:21 +01:00
Kevin Veen-Birkenbach
08ab9fb142 feat(ci): stabilize virgin Arch tests with Makefile install/setup and Nix Git safety
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
* Switch virgin root/user workflows to use *make install* + *make setup/setup-venv*
* Add Git *safe.directory /src* to avoid flake evaluation failures on mounted repos
* Enable Nix flake run in workflows and prepare */nix* for non-root execution
* Refactor Arch packaging to build in an isolated */tmp* directory via *aur_builder*
* Rename installer scripts (*run-** → *dependencies.sh* / *package.sh*) and adjust Docker entry + env var to *REINSTALL_PKGMGR*

https://chatgpt.com/share/693c29d9-9b28-800f-a549-5661c783d968
2025-12-12 15:42:25 +01:00
Kevin Veen-Birkenbach
804245325d Release version 1.2.1
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
2025-12-12 12:32:33 +01:00
Kevin Veen-Birkenbach
c05e77658a ci(docker): remove build-time nix check and rely on runtime env test
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Why:
The Dockerfile previously validated `nix --version` during image build,
which is environment-sensitive and behaves differently in GitHub Actions
vs local/act builds due to PATH and non-login shell differences.

The actual contract is runtime availability of Nix, not build-step PATH
resolution. This is now reliably enforced by the dedicated `test-env-nix`
container test, which validates nix presence and flake execution in the
real execution environment.

This removes flaky CI behavior while keeping stronger, more accurate
coverage of the intended guarantee.

https://chatgpt.com/share/693bfbc7-63d8-800f-9ceb-728c7a58e963
2025-12-12 12:25:36 +01:00
Kevin Veen-Birkenbach
324f6db1f3 ci: split container tests into virtualenv and Nix flake environments
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Refactor CI to clearly separate virtualenv-based container tests from pure Nix flake tests across all distros (arch, debian, ubuntu, fedora, centos).
Introduce dedicated test-env-nix workflow and Makefile targets, rename former container tests to test-env-virtual, and update stable pipeline dependencies.
Improve Nix reliability in containers by fixing installer permissions and explicitly validating nix availability and version during image build and tests.
2025-12-12 12:15:40 +01:00
72 changed files with 1625 additions and 853 deletions

View File

@@ -13,8 +13,11 @@ jobs:
test-integration:
uses: ./.github/workflows/test-integration.yml
test-container:
uses: ./.github/workflows/test-container.yml
test-env-virtual:
uses: ./.github/workflows/test-env-virtual.yml
test-env-nix:
uses: ./.github/workflows/test-env-nix.yml
test-e2e:
uses: ./.github/workflows/test-e2e.yml

View File

@@ -14,8 +14,11 @@ jobs:
test-integration:
uses: ./.github/workflows/test-integration.yml
test-container:
uses: ./.github/workflows/test-container.yml
test-env-virtual:
uses: ./.github/workflows/test-env-virtual.yml
test-env-nix:
uses: ./.github/workflows/test-env-nix.yml
test-e2e:
uses: ./.github/workflows/test-e2e.yml
@@ -30,7 +33,8 @@ jobs:
needs:
- test-unit
- test-integration
- test-container
- test-env-nix
- test-env-virtual
- test-e2e
- test-virgin-user
- test-virgin-root

View File

@@ -0,0 +1,56 @@
name: Publish container images (GHCR)
on:
push:
tags:
- "v*"
jobs:
publish:
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: Compute version and stable flag
id: info
run: |
set -euo pipefail
SHA="$(git rev-parse HEAD)"
VERSION="${GITHUB_REF_NAME#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

26
.github/workflows/test-env-nix.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Test Virgin Nix (flake only)
on:
workflow_call:
jobs:
test-env-nix:
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
distro: [arch, debian, ubuntu, fedora, centos]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Show Docker version
run: docker version
- name: Nix flake-only test (${{ matrix.distro }})
run: |
set -euo pipefail
distro="${{ matrix.distro }}" make test-env-nix

View File

@@ -4,7 +4,7 @@ on:
workflow_call:
jobs:
test-container:
test-env-virtual:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
@@ -25,4 +25,4 @@ jobs:
- name: Run container tests (${{ matrix.distro }})
run: |
set -euo pipefail
distro="${{ matrix.distro }}" make test-container
distro="${{ matrix.distro }}" make test-env-virtual

View File

@@ -7,6 +7,10 @@ jobs:
test-virgin-root:
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
distro: [arch, debian, ubuntu, fedora, centos]
steps:
- name: Checkout repository
@@ -15,44 +19,38 @@ jobs:
- name: Show Docker version
run: docker version
- name: Virgin Arch pkgmgr flake test (root)
# 🔹 BUILD virgin image if missing
- name: Build virgin container (${{ matrix.distro }})
run: |
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 \
-v "$PWD":/src \
-v pkgmgr_repos:/root/Repositories \
-v pkgmgr_pip_cache:/root/.cache/pip \
-w /src \
archlinux:latest \
"pkgmgr-${{ matrix.distro }}-virgin" \
bash -lc '
set -euo pipefail
echo ">>> Updating and upgrading Arch system..."
pacman -Syu --noconfirm git python python-pip nix >/dev/null
git config --global --add safe.directory /src
echo ">>> Creating isolated virtual environment for pkgmgr..."
python -m venv /tmp/pkgmgr-venv
make install
make setup
echo ">>> Activating virtual environment..."
source /tmp/pkgmgr-venv/bin/activate
. "$HOME/.venvs/pkgmgr/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"
echo ">>> Running: pkgmgr update pkgmgr --clone-mode shallow --no-verification"
pkgmgr update pkgmgr --clone-mode shallow --no-verification
echo ">>> Running: 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
'

View File

@@ -7,6 +7,10 @@ jobs:
test-virgin-user:
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
distro: [arch, debian, ubuntu, fedora, centos]
steps:
- name: Checkout repository
@@ -15,59 +19,47 @@ jobs:
- name: Show 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: |
set -euo pipefail
echo ">>> Starting virgin ArchLinux container test (non-root user with sudo)..."
docker run --rm \
-v "$PWD":/src \
archlinux:latest \
-w /src \
"pkgmgr-${{ matrix.distro }}-virgin" \
bash -lc '
set -euo pipefail
echo ">>> [root] Updating and upgrading Arch system..."
pacman -Syu --noconfirm git python python-pip sudo base-devel debugedit
make install
echo ">>> [root] Creating non-root user dev..."
useradd -m dev
echo ">>> [root] Allowing passwordless sudo for dev..."
echo "dev ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/dev
chmod 0440 /etc/sudoers.d/dev
echo ">>> [root] Adjusting ownership of /src for dev..."
chown -R dev:dev /src
echo ">>> [root] Running pkgmgr flow as non-root user dev..."
sudo -u dev env PKGMGR_DISABLE_NIX_FLAKE_INSTALLER=1 bash -lc "
mkdir -p /nix/store /nix/var/nix /nix/var/log/nix /nix/var/nix/profiles
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
cd /src
echo \">>> [dev] Using user: \$(whoami)\"
echo \">>> [dev] Running scripts/installation/main.sh...\"
bash scripts/installation/main.sh
echo \">>> [dev] Activating venv...\"
make setup-venv
. \"\$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
"
echo ">>> [root] Container flow finished."
export NIX_REMOTE=local
export NIX_CONFIG=\"experimental-features = nix-command flakes\"
nix run /src#pkgmgr -- version pkgmgr
"
'

View File

@@ -1,3 +1,46 @@
## [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
* **Changed**
* Split container tests into *virtualenv* and *Nix flake* environments to clearly separate Python and Nix responsibilities.
**Fixed**
* Fixed Nix installer permission issues when running under a different user in containers.
* Improved reliability of post-install Nix initialization across all distro packages.
**CI**
* Replaced generic container tests with explicit environment checks.
* Validate Nix availability via *nix flake* tests instead of Docker build-time side effects.
## [1.2.0] - 2025-12-12
* **Release workflow overhaul**

View File

@@ -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
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"]
# ------------------------------------------------------------
# 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"
RUN echo "BASE_IMAGE=${BASE_IMAGE}" && cat /etc/os-release || true
# ------------------------------------------------------------
# Unprivileged user for Arch package build (makepkg)
# ------------------------------------------------------------
RUN useradd -m aur_builder || true
# ------------------------------------------------------------
# Copy scripts and install distro dependencies
# ------------------------------------------------------------
WORKDIR /build
# Copy only scripts first so dependency installation can run early
COPY scripts/ scripts/
RUN find scripts -type f -name '*.sh' -exec chmod +x {} \;
# Copy scripts first so dependency installation can be cached
COPY scripts/installation/ scripts/installation/
# Install distro-specific build dependencies (and AUR builder on Arch)
RUN scripts/installation/run-dependencies.sh
# Install distro-specific build dependencies (including make)
RUN bash scripts/installation/dependencies.sh
# ------------------------------------------------------------
# Select distro-specific Docker entrypoint
# ------------------------------------------------------------
# Docker entrypoint (distro-agnostic, nutzt run-package.sh)
# ------------------------------------------------------------
COPY scripts/docker/entry.sh /usr/local/bin/docker-entry.sh
RUN chmod +x /usr/local/bin/docker-entry.sh
# Virgin default
CMD ["bash"]
# ------------------------------------------------------------
# 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 . .
RUN find scripts -type f -name '*.sh' -exec chmod +x {} \;
RUN set -e; \
echo "Building and installing package-manager via make install..."; \
make install; \
rm -rf /build
# Build and install distro-native package-manager package
RUN set -euo pipefail; \
echo "Building and installing package-manager via make install..."; \
make install; \
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
ENTRYPOINT ["/usr/local/bin/docker-entry.sh"]
CMD ["pkgmgr", "--help"]

View File

@@ -1,9 +1,12 @@
.PHONY: install setup uninstall \
test build build-no-cache test-unit test-e2e test-integration \
test-container
.PHONY: install uninstall \
build build-no-cache build-no-cache-all build-missing \
delete-volumes purge \
test test-unit test-e2e test-integration test-env-virtual test-env-nix \
setup setup-venv setup-nix
# Distro
# Options: arch debian ubuntu fedora centos
DISTROS ?= arch debian ubuntu fedora centos
distro ?= arch
export distro
@@ -29,19 +32,50 @@ TEST_PATTERN := test_*.py
export TEST_PATTERN
# ------------------------------------------------------------
# PKGMGR setup (developer wrapper -> scripts/installation/main.sh)
# System install
# ------------------------------------------------------------
setup:
@bash scripts/installation/main.sh
install:
@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 + main.py 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)
# ------------------------------------------------------------
build-no-cache:
@bash scripts/build/build-image-no-cache.sh
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:
@bash scripts/build/image.sh --target virgin --no-cache
@bash scripts/build/image.sh --no-cache
build-no-cache-all:
@set -e; \
for d in $(DISTROS); do \
echo "=== build-no-cache: $$d ==="; \
distro="$$d" $(MAKE) build-no-cache; \
done
# ------------------------------------------------------------
# Test targets (delegated to scripts/test)
@@ -56,30 +90,20 @@ test-integration: build-missing
test-e2e: build-missing
@bash scripts/test/test-e2e.sh
test-container: build-missing
@bash scripts/test/test-container.sh
test-env-virtual: build-missing
@bash scripts/test/test-env-virtual.sh
# ------------------------------------------------------------
# Build only missing container images
# ------------------------------------------------------------
build-missing:
@bash scripts/build/build-image-missing.sh
test-env-nix: build-missing
@bash scripts/test/test-env-nix.sh
# Combined test target for local + CI (unit + integration + e2e)
test: test-container test-unit test-integration test-e2e
test: test-env-virtual test-unit test-integration test-e2e
delete-volumes:
@docker volume rm pkgmgr_nix_store_${distro} pkgmgr_nix_cache_${distro} || true
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
# ------------------------------------------------------------

115
README.md
View File

@@ -8,8 +8,9 @@
[![PayPal](https://img.shields.io/badge/Donate-PayPal-blue?logo=paypal)](https://s.veen.world/paypaldonate)
[![GitHub license](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![GitHub repo size](https://img.shields.io/github/repo-size/kevinveenbirkenbach/package-manager)](https://github.com/kevinveenbirkenbach/package-manager)
[![Mark stable commit](https://github.com/kevinveenbirkenbach/package-manager/actions/workflows/mark-stable.yml/badge.svg)](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
operating systems** (Arch, Debian, Ubuntu, Fedora, CentOS, …).
@@ -98,55 +99,93 @@ The following diagram gives a full overview of:
![PKGMGR Architecture](assets/map.png)
**Diagram status:** 11 December 2025
**Diagram status:** 12 December 2025
**Always-up-to-date version:** [https://s.veen.world/pkgmgrmp](https://s.veen.world/pkgmgrmp)
---
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 ⚙️
### 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
git clone https://github.com/kevinveenbirkenbach/package-manager.git
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
```
This will:
* 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:
#### Full setup (venv + Nix)
```bash
make setup
```
which prepares the environment and leaves you with a fully wired development
workspace (including Nix, tests and scripts).
Use this for CI, servers, containers and full development workflows.
#### 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
```
This prints a list of all available subcommands, for example:
* `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
This prints a list of all available subcommands.
The help for each command is available via:
```bash
pkgmgr <command> --help
```
---
## License 📄

View File

@@ -36,7 +36,7 @@
rec {
pkgmgr = pyPkgs.buildPythonApplication {
pname = "package-manager";
version = "1.2.0";
version = "1.4.0";
# Use the git repo as source
src = ./.;

View File

@@ -50,9 +50,10 @@ package() {
install -Dm0755 "scripts/pkgmgr-wrapper.sh" \
"$pkgdir/usr/bin/pkgmgr"
# Install Nix init helper
install -Dm0755 "scripts/init-nix.sh" \
"$pkgdir/usr/lib/package-manager/init-nix.sh"
# Install Nix bootstrap (init + lib)
install -d "$pkgdir/usr/lib/package-manager/nix"
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
mkdir -p "$pkgdir/usr/lib/package-manager"

View File

@@ -1,9 +1,9 @@
post_install() {
/usr/lib/package-manager/init-nix.sh || true
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
}
post_upgrade() {
/usr/lib/package-manager/init-nix.sh || true
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
}
post_remove() {

View File

@@ -3,11 +3,7 @@ set -e
case "$1" in
configure)
if [ -x /usr/lib/package-manager/init-nix.sh ]; then
/usr/lib/package-manager/init-nix.sh || true
else
echo ">>> Warning: /usr/lib/package-manager/init-nix.sh not found or not executable."
fi
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
;;
esac

View File

@@ -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:
# Create target directories
@@ -31,9 +31,11 @@ override_dh_auto_install:
install -m0755 scripts/pkgmgr-wrapper.sh \
debian/package-manager/usr/bin/pkgmgr
# Install shared Nix init script
install -m0755 scripts/init-nix.sh \
debian/package-manager/usr/lib/package-manager/init-nix.sh
# Install Nix bootstrap (init + lib)
install -d debian/package-manager/usr/lib/package-manager/nix
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,
# but do not include the debian/ directory itself.

View File

@@ -12,7 +12,7 @@ BuildArch: noarch
# NOTE:
# 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.
# 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.
%description
@@ -22,7 +22,7 @@ manager via a local Nix flake:
nix run /usr/lib/package-manager#pkgmgr -- ...
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.
%prep
@@ -34,8 +34,8 @@ available on the system.
%install
rm -rf %{buildroot}
install -d %{buildroot}%{_bindir}
# Install project tree into a fixed, architecture-independent location.
install -d %{buildroot}/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
install -m0755 scripts/pkgmgr-wrapper.sh %{buildroot}%{_bindir}/pkgmgr
# Shared Nix init script (ensure it is executable in the installed tree)
install -m0755 scripts/init-nix.sh %{buildroot}/usr/lib/package-manager/init-nix.sh
# Nix bootstrap (init + lib)
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
rm -rf \
@@ -60,12 +62,7 @@ rm -rf \
%{buildroot}/usr/lib/package-manager/.gitkeep || true
%post
# Initialize Nix (if needed) after installing the package-manager files.
if [ -x /usr/lib/package-manager/init-nix.sh ]; then
/usr/lib/package-manager/init-nix.sh || true
else
echo ">>> Warning: /usr/lib/package-manager/init-nix.sh not found or not executable."
fi
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
%postun
echo ">>> package-manager removed. Nix itself was not removed."

View File

@@ -7,10 +7,10 @@ build-backend = "setuptools.build_meta"
[project]
name = "package-manager"
version = "1.2.0"
version = "1.4.0"
description = "Kevin's package-manager tool (pkgmgr)"
readme = "README.md"
requires-python = ">=3.11"
requires-python = ">=3.9"
license = { text = "MIT" }
authors = [

View File

@@ -1,18 +1,20 @@
#!/usr/bin/env bash
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() {
local distro="$1"
case "$distro" in
arch) echo "$BASE_IMAGE_ARCH" ;;
debian) echo "$BASE_IMAGE_DEBIAN" ;;
ubuntu) echo "$BASE_IMAGE_UBUNTU" ;;
fedora) echo "$BASE_IMAGE_FEDORA" ;;
centos) echo "$BASE_IMAGE_CENTOS" ;;
*)
echo "ERROR: Unknown distro '$distro'" >&2
exit 1
;;
*) echo "ERROR: Unknown distro '$distro'" >&2; exit 1 ;;
esac
}

View File

@@ -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" \
.

View File

@@ -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" \
.

View File

@@ -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
View 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
View 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."

View File

@@ -1,53 +1,6 @@
#!/usr/bin/env bash
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)"
echo "[docker] Starting package-manager container"
@@ -68,16 +21,10 @@ cd /src
# ---------------------------------------------------------------------------
# DEV mode: rebuild package-manager from the mounted /src tree
# ---------------------------------------------------------------------------
if [[ "${PKGMGR_DEV:-0}" == "1" ]]; then
echo "[docker] DEV mode enabled (PKGMGR_DEV=1)"
echo "[docker] Rebuilding package-manager from /src via scripts/installation/run-package.sh..."
if [[ -x scripts/installation/run-package.sh ]]; then
bash scripts/installation/run-package.sh
else
echo "[docker] ERROR: scripts/installation/run-package.sh not found or not executable"
exit 1
fi
if [[ "${REINSTALL_PKGMGR:-0}" == "1" ]]; then
echo "[docker] DEV mode enabled (REINSTALL_PKGMGR=1)"
echo "[docker] Rebuilding package-manager from /src via scripts/installation/package.sh..."
bash scripts/installation/package.sh || exit 1
fi
# ---------------------------------------------------------------------------

View File

@@ -1,246 +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)"
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
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 "$@"

View File

@@ -12,6 +12,7 @@ pacman -S --noconfirm --needed \
rsync \
curl \
ca-certificates \
python \
xz
pacman -Scc --noconfirm

View File

@@ -1,30 +1,64 @@
#!/usr/bin/env bash
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)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
PKG_DIR="${PROJECT_ROOT}/packaging/arch"
if [[ ! -f "${PKG_DIR}/PKGBUILD" ]]; then
echo "[arch/package] ERROR: PKGBUILD not found in ${PKG_DIR}"
# We must not build inside /src (mounted repo). Build in /tmp to avoid permission issues.
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
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] Using 'aur_builder' user for makepkg..."
chown -R aur_builder:aur_builder "${PKG_DIR}"
su aur_builder -c "cd '${PKG_DIR}' && rm -f package-manager-*.pkg.tar.* && makepkg --noconfirm --clean --nodeps"
else
echo "[arch/package] WARNING: user 'aur_builder' not found, running makepkg as current user..."
rm -f package-manager-*.pkg.tar.*
makepkg --noconfirm --clean --nodeps
echo "[arch/package] Syncing project sources to ${BUILD_ROOT}..."
# Keep it simple: copy everything; adjust excludes if needed later.
rsync -a --delete \
--exclude '.git' \
--exclude '.venv' \
--exclude '.venvs' \
--exclude '__pycache__' \
--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
# ------------------------------------------------------------
# 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..."
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."

View File

@@ -13,9 +13,64 @@ dnf -y install \
bash \
curl-minimal \
ca-certificates \
python3 \
sudo \
xz
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."

View File

@@ -13,6 +13,8 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
bash \
curl \
ca-certificates \
python3 \
python3-venv \
xz-utils
rm -rf /var/lib/apt/lists/*

View File

@@ -3,22 +3,19 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/lib.sh"
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/os_resolver.sh"
OS_ID="$(detect_os_id)"
OS_ID="$(osr_get_os_id)"
echo "[run-dependencies] Detected OS: ${OS_ID}"
case "${OS_ID}" in
arch|debian|ubuntu|fedora|centos)
DEP_SCRIPT="${SCRIPT_DIR}/${OS_ID}/dependencies.sh"
;;
*)
echo "[run-dependencies] Unsupported OS: ${OS_ID}"
exit 1
;;
esac
if ! osr_is_supported "${OS_ID}"; then
echo "[run-dependencies] Unsupported OS: ${OS_ID}"
exit 1
fi
DEP_SCRIPT="$(osr_script_path_for "${SCRIPT_DIR}" "${OS_ID}" "dependencies")"
if [[ ! -f "${DEP_SCRIPT}" ]]; then
echo "[run-dependencies] Dependency script not found: ${DEP_SCRIPT}"

15
scripts/installation/init.sh Executable file
View 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

View File

@@ -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
}

View File

@@ -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."

View 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
View 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}"

View File

@@ -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}"

View File

@@ -14,6 +14,9 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
rsync \
bash \
curl \
make \
python3 \
python3-venv \
ca-certificates \
xz-utils

View File

@@ -1,44 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# venv-create.sh
#
# Small helper to create/update a Python virtual environment for pkgmgr.
#
# 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
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "${PROJECT_ROOT}"
VENV_DIR="${PKGMGR_VENV_DIR:-${1:-${HOME}/.venvs/pkgmgr}}"
echo "[venv-create] Using VENV_DIR=${VENV_DIR}"
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
if [[ -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..."
"${VENV_DIR}/bin/pip" install -r _requirements.txt
else
echo "[venv-create] No requirements.txt or _requirements.txt found. Skipping dependency installation."
fi
echo "[venv-create] Done."

53
scripts/nix/README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View File

@@ -28,11 +28,11 @@ if ! command -v nix >/dev/null 2>&1; then
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 [[ -x "${FLAKE_DIR}/init-nix.sh" ]]; then
"${FLAKE_DIR}/init-nix.sh" || true
if [[ -x "${FLAKE_DIR}/nix/init.sh" ]]; then
"${FLAKE_DIR}/nix/init.sh" || true
fi
fi

9
scripts/setup/nix.sh Executable file
View File

@@ -0,0 +1,9 @@
# ------------------------------------------------------------
# Nix shell mode: do not touch venv, only run main.py install
# ------------------------------------------------------------
echo "[setup] Nix mode enabled (NIX_ENABLED=1)."
echo "[setup] Skipping virtualenv creation and dependency installation."
echo "[setup] Running main.py install via system python3..."
python3 main.py install
echo "[setup] Setup finished (Nix mode)."

98
scripts/setup/venv.sh Executable file
View File

@@ -0,0 +1,98 @@
#!/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 main.py is executable..."
chmod +x main.py || true
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 main.py install via venv Python..."
"${VENV_DIR}/bin/python" main.py install
echo
echo "[setup] Developer setup complete."
echo "Restart your shell (or run 'exec bash' or 'exec zsh') to activate the environment."

View File

@@ -9,10 +9,10 @@ docker run --rm \
-v "$(pwd):/src" \
-v "pkgmgr_nix_store_${distro}:/nix" \
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
-e PKGMGR_DEV=1 \
-e REINSTALL_PKGMGR=1 \
-e TEST_PATTERN="${TEST_PATTERN}" \
--workdir /src \
"package-manager-test-${distro}" \
"pkgmgr-${distro}" \
bash -lc '
set -euo pipefail

48
scripts/test/test-env-nix.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -euo pipefail
IMAGE="pkgmgr-${distro}"
echo "============================================================"
echo ">>> Running Nix flake-only test in ${distro} container"
echo ">>> Image: ${IMAGE}"
echo "============================================================"
docker run --rm \
-v "$(pwd):/src" \
-v "pkgmgr_nix_store_${distro}:/nix" \
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
--workdir /src \
-e REINSTALL_PKGMGR=1 \
"${IMAGE}" \
bash -lc '
set -euo pipefail
if command -v git >/dev/null 2>&1; then
git config --global --add safe.directory /src || true
git config --global --add safe.directory /src/.git || true
git config --global --add safe.directory "*" || true
fi
echo ">>> preflight: nix must exist in image"
if ! command -v nix >/dev/null 2>&1; then
echo "NO_NIX"
echo "ERROR: nix not found in image '\'''"${IMAGE}"''\'' (distro='"${distro}"')"
echo "HINT: Ensure Nix is installed during image build for this distro."
exit 1
fi
echo ">>> nix version"
nix --version
echo ">>> nix flake show"
nix flake show . --no-write-lock-file >/dev/null
echo ">>> nix build .#default"
nix build .#default --no-link --no-write-lock-file
echo ">>> nix run .#pkgmgr -- --help"
nix run .#pkgmgr -- --help --no-write-lock-file
echo ">>> OK: Nix flake-only test succeeded."
'

View File

@@ -1,32 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
IMAGE="package-manager-test-$distro"
IMAGE="pkgmgr-$distro"
echo
echo "------------------------------------------------------------"
echo ">>> Testing container: $IMAGE"
echo ">>> Testing VENV: $IMAGE"
echo "------------------------------------------------------------"
echo "[test-container] Inspect image metadata:"
echo "[test-env-virtual] Inspect image metadata:"
docker image inspect "$IMAGE" | sed -n '1,40p'
echo "[test-container] Running: docker run --rm --entrypoint pkgmgr $IMAGE --help"
echo "[test-env-virtual] Running: docker run --rm --entrypoint pkgmgr $IMAGE --help"
echo
# Run the command and capture the output
if OUTPUT=$(docker run --rm \
-e PKGMGR_DEV=1 \
-e REINSTALL_PKGMGR=1 \
-v pkgmgr_nix_store_${distro}:/nix \
-v "$(pwd):/src" \
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
"$IMAGE" 2>&1); then
echo "$OUTPUT"
echo
echo "[test-container] SUCCESS: $IMAGE responded to 'pkgmgr --help'"
echo "[test-env-virtual] SUCCESS: $IMAGE responded to 'pkgmgr --help'"
else
echo "$OUTPUT"
echo
echo "[test-container] ERROR: $IMAGE failed to run 'pkgmgr --help'"
echo "[test-env-virtual] ERROR: $IMAGE failed to run 'pkgmgr --help'"
exit 1
fi

View File

@@ -10,9 +10,9 @@ docker run --rm \
-v pkgmgr_nix_store_${distro}:/nix \
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
--workdir /src \
-e PKGMGR_DEV=1 \
-e REINSTALL_PKGMGR=1 \
-e TEST_PATTERN="${TEST_PATTERN}" \
"package-manager-test-${distro}" \
"pkgmgr-${distro}" \
bash -lc '
set -e;
git config --global --add safe.directory /src || true;

View File

@@ -10,9 +10,9 @@ docker run --rm \
-v "pkgmgr_nix_cache_${distro}:/root/.cache/nix" \
-v pkgmgr_nix_store_${distro}:/nix \
--workdir /src \
-e PKGMGR_DEV=1 \
-e REINSTALL_PKGMGR=1 \
-e TEST_PATTERN="${TEST_PATTERN}" \
"package-manager-test-${distro}" \
"pkgmgr-${distro}" \
bash -lc '
set -e;
git config --global --add safe.directory /src || true;

View File

@@ -1,5 +1,4 @@
from __future__ import annotations
from typing import Optional
from pkgmgr.core.git import run_git, GitError, get_current_branch
from .utils import _resolve_base_branch

View File

@@ -1,5 +1,4 @@
from __future__ import annotations
from typing import Optional
from pkgmgr.core.git import run_git, GitError, get_current_branch
from .utils import _resolve_base_branch

View File

@@ -1,5 +1,4 @@
from __future__ import annotations
from typing import Optional
from pkgmgr.core.git import run_git, GitError
from .utils import _resolve_base_branch

View File

@@ -15,7 +15,7 @@ Responsibilities:
from __future__ import annotations
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.dir import get_repo_dir
@@ -63,7 +63,7 @@ def _ensure_repo_dir(
no_verification: bool,
clone_mode: str,
identifier: str,
) -> str | None:
) -> Optional[str]:
"""
Compute and, if necessary, clone the repository directory.

View File

@@ -35,7 +35,7 @@ from __future__ import annotations
import glob
import os
from abc import ABC, abstractmethod
from typing import Iterable, TYPE_CHECKING
from typing import Iterable, TYPE_CHECKING, Optional
if TYPE_CHECKING:
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."""
if not os.path.exists(path):
return None
@@ -75,7 +75,7 @@ def _scan_files_for_patterns(files: Iterable[str], patterns: Iterable[str]) -> b
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."""
matches = glob.glob(os.path.join(repo_dir, "*.spec"))
if not matches:
@@ -360,7 +360,7 @@ def detect_capabilities(
def resolve_effective_capabilities(
ctx: "RepoContext",
layers: Iterable[str] | None = None,
layers: Optional[Iterable[str]] = None,
) -> dict[str, set[str]]:
"""
Resolve *effective* capabilities for each layer using a bottom-up strategy.

View File

@@ -6,7 +6,7 @@ Base interface for all installer components in the pkgmgr installation pipeline.
"""
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.capabilities import CAPABILITY_MATCHERS
@@ -24,7 +24,7 @@ class BaseInstaller(ABC):
# Examples: "nix", "python", "makefile".
# This is used by capability matchers to decide which patterns to
# search for in the repository.
layer: str | None = None
layer: Optional[str] = None
def discover_capabilities(self, ctx: RepoContext) -> Set[str]:
"""

View File

@@ -17,7 +17,7 @@ apt/dpkg tooling are available.
import glob
import os
import shutil
from typing import List
from typing import List, Optional
from pkgmgr.actions.install.context import RepoContext
from pkgmgr.actions.install.installers.base import BaseInstaller
@@ -67,7 +67,7 @@ class DebianControlInstaller(BaseInstaller):
pattern = os.path.join(parent, "*.deb")
return sorted(glob.glob(pattern))
def _privileged_prefix(self) -> str | None:
def _privileged_prefix(self) -> Optional[str]:
"""
Determine how to run privileged commands:

View File

@@ -1,10 +1,10 @@
from __future__ import annotations
import os
from typing import List, Optional, Set
from pkgmgr.core.command.run import run_command
from pkgmgr.core.git import GitError, run_git
from typing import List, Optional, Set
from .types import MirrorMap, RepoMirrorContext, Repository

View File

@@ -1,4 +1,5 @@
from __future__ import annotations
from typing import Optional
import os
import sys

View File

@@ -19,7 +19,7 @@ USER_CONFIG_PATH = os.path.expanduser("~/.config/pkgmgr/config.yaml")
DESCRIPTION_TEXT = """\
\033[1;32mPackage 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
repositories and packaging formats (pyproject.toml, flake.nix,

View File

@@ -1,4 +1,5 @@
from __future__ import annotations
from typing import Optional
import os
import sys

View File

@@ -7,7 +7,7 @@ import os
import sys
import shutil
from pathlib import Path
from typing import Any, Dict
from typing import Any, Dict, Optional
import yaml
@@ -36,7 +36,7 @@ def _load_user_config(user_config_path: str) -> Dict[str, Any]:
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
project root that contains default config files.

View File

@@ -1,4 +1,5 @@
from __future__ import annotations
from typing import Optional
import os
import sys

View File

@@ -1,3 +1,4 @@
from typing import Optional
import os
import shutil
from typing import Optional, List, Dict, Any

View File

@@ -1,3 +1,4 @@
from typing import Optional
# pkgmgr/run_command.py
import subprocess
import sys

View File

@@ -40,7 +40,7 @@ from __future__ import annotations
import os
from pathlib import Path
from typing import Any, Dict, List, Tuple
from typing import Any, Dict, List, Tuple, Optional
import yaml
@@ -83,7 +83,7 @@ def _repo_key(repo: Repo) -> Tuple[str, str, str]:
def _merge_repo_lists(
base_list: List[Repo],
new_list: List[Repo],
category_name: str | None = None,
category_name: Optional[str] = None,
) -> List[Repo]:
"""
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(
config_dir: Path,
skip_filename: str | None = None,
skip_filename: Optional[str] = None,
) -> Dict[str, Any]:
"""
Load all *.yml/*.yaml from a directory as layered defaults.

View File

@@ -7,11 +7,13 @@ This test is intended to be run inside the Docker container where:
- the config/config.yaml is present,
- 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 sys
import os
import subprocess
import unittest
from test_install_pkgmgr_shallow import (
@@ -22,55 +24,35 @@ from test_install_pkgmgr_shallow import (
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
extra diagnostics if the command exits with a non-zero code.
Run a real CLI command and raise a helpful assertion on failure.
"""
cmd_repr = "pkgmgr update --all --clone-mode https --no-verification"
original_argv = sys.argv
cmd_repr = " ".join(cmd)
env = os.environ.copy()
try:
sys.argv = [
"pkgmgr",
"update",
"--all",
"--clone-mode",
"https",
"--no-verification",
]
print(f"\n[TEST] Running ({label}): {cmd_repr}")
subprocess.run(
cmd,
check=True,
cwd=os.getcwd(),
env=env,
text=True,
)
except subprocess.CalledProcessError as exc:
print(f"\n[TEST] Command failed ({label})")
print(f"[TEST] Command : {cmd_repr}")
print(f"[TEST] Exit code: {exc.returncode}")
try:
# Execute main.py as if it was called from CLI.
# This will run the full update pipeline inside the container.
runpy.run_module("main", run_name="__main__")
except SystemExit as exc:
# Convert SystemExit into a more helpful assertion with debug output.
exit_code = exc.code if isinstance(exc.code, int) else str(exc.code)
nix_profile_list_debug(f"ON FAILURE ({label})")
print("\n[TEST] pkgmgr update --all failed with SystemExit")
print(f"[TEST] Command : {cmd_repr}")
print(f"[TEST] Exit code: {exit_code}")
raise AssertionError(
f"({label}) {cmd_repr!r} failed with exit code {exc.returncode}. "
"Scroll up to see the full pkgmgr/nix output inside the container."
) from exc
# Additional Nix profile debug on failure (useful if any update
# step interacts with Nix-based tooling).
nix_profile_list_debug("ON FAILURE (AFTER SystemExit)")
raise AssertionError(
f"{cmd_repr!r} failed with exit code {exit_code}. "
"Scroll up to see the full pkgmgr/make output inside the container."
) from exc
finally:
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.
"""
def _common_setup(self) -> None:
# Debug before cleanup
nix_profile_list_debug("BEFORE CLEANUP")
@@ -81,11 +63,28 @@ class TestIntegrationUpdateAllHttps(unittest.TestCase):
# Debug after cleanup
nix_profile_list_debug("AFTER CLEANUP")
# Run the actual update with extended diagnostics
self._run_pkgmgr_update_all_https()
def test_update_all_repositories_https_pkgmgr(self) -> None:
"""
Run: pkgmgr update --all --clone-mode https --no-verification
"""
self._common_setup()
# After successful update: show `pkgmgr --help`
# via interactive bash (same helper as in the other integration tests).
args = ["update", "--all", "--clone-mode", "https", "--no-verification"]
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()