Initial commit: DarkForge Linux — Phases 0-12
Complete from-scratch Linux distribution targeting AMD Ryzen 9 9950X3D + NVIDIA RTX 5090 on ASUS ROG CROSSHAIR X870E HERO. Deliverables: - dpack: custom package manager in Rust (3,800 lines) - TOML package parser, dependency resolver, build sandbox - CRUX Pkgfile and Gentoo ebuild converters - Shared library conflict detection - 124 package definitions across 4 repos (core/extra/desktop/gaming) - 34 toolchain bootstrap scripts (LFS 13.0 adapted for Zen 5) - Linux 6.19.8 kernel config (hardware-specific, fully commented) - SysVinit init system with rc.d service scripts - Live ISO builder (UEFI-only, squashfs+xorriso) - Interactive installer (GPT partitioning, EFISTUB boot) - Integration test checklist (docs/TESTING.md) No systemd. No bootloader. No display manager. Kernel boots via EFISTUB → auto-login → dwl Wayland compositor. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Build artifacts
|
||||||
|
/build/
|
||||||
|
/toolchain/sources/
|
||||||
|
/toolchain/logs/
|
||||||
|
|
||||||
|
# Rust build output
|
||||||
|
src/dpack/target/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
._*
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Reference materials (large, not part of the project source)
|
||||||
|
/reference/
|
||||||
|
|
||||||
|
# ISO output
|
||||||
|
*.iso
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
/tmp/
|
||||||
|
*.tmp
|
||||||
712
CLAUDE.md
Normal file
712
CLAUDE.md
Normal file
@@ -0,0 +1,712 @@
|
|||||||
|
# CLAUDE.md — Project Directive for DarkForge Linux
|
||||||
|
|
||||||
|
> **This file is the single source of truth for all AI-assisted work on this project.**
|
||||||
|
> Every session must begin by reading this file in full. No exceptions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Identity & Philosophy](#identity--philosophy)
|
||||||
|
2. [Ground Rules — Non-Negotiable](#ground-rules--non-negotiable)
|
||||||
|
3. [Project Overview](#project-overview)
|
||||||
|
4. [Hardware Target](#hardware-target)
|
||||||
|
5. [Architecture Decisions](#architecture-decisions)
|
||||||
|
6. [Project Structure](#project-structure)
|
||||||
|
7. [Phase Breakdown & Execution Order](#phase-breakdown--execution-order)
|
||||||
|
8. [dpack — The Package Manager](#dpack--the-package-manager)
|
||||||
|
9. [Kernel Configuration](#kernel-configuration)
|
||||||
|
10. [Init System](#init-system)
|
||||||
|
11. [ISO & Installer](#iso--installer)
|
||||||
|
12. [Target Package List](#target-package-list)
|
||||||
|
13. [Reference Material](#reference-material)
|
||||||
|
14. [Changelog Protocol](#changelog-protocol)
|
||||||
|
15. [Session Protocol](#session-protocol)
|
||||||
|
16. [Known Pitfalls & Guardrails](#known-pitfalls--guardrails)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Identity & Philosophy
|
||||||
|
|
||||||
|
**Project codename:** DarkForge Linux
|
||||||
|
**Purpose:** A custom, from-scratch Linux distribution built for one machine, one user, optimized ruthlessly for gaming and development. This is an internal-use tool — not a production distribution. Security theater (package signing, encrypted disk, etc.) is explicitly deprioritized. Raw performance and correctness are the goals.
|
||||||
|
|
||||||
|
**Philosophy:** Do not guess. Do not assume. If anything is ambiguous, stop and ask. Precision over speed. Every decision must be justifiable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ground Rules — Non-Negotiable
|
||||||
|
|
||||||
|
These rules apply to every single session, every single change, no matter how small.
|
||||||
|
|
||||||
|
### 1. Never Guess
|
||||||
|
If a requirement, version number, configuration flag, dependency, or design decision is unclear within this document or the codebase, **stop and ask the user**. After receiving clarification, update the relevant section of this document or the appropriate project docs so the ambiguity never recurs.
|
||||||
|
|
||||||
|
### 2. Changelog Is Mandatory
|
||||||
|
Every change to code, scripts, configs, or documentation **must** be logged in `docs/CHANGELOG.md` before the session ends. The format is strict and defined in the [Changelog Protocol](#changelog-protocol) section below. No change is considered complete until the changelog entry exists.
|
||||||
|
|
||||||
|
### 3. Latest Versions Always
|
||||||
|
LFS, BLFS, CRUX, and Gentoo reference material often pins older package versions. **Always research and use the latest stable release** of every package, library, and toolchain component unless there is a documented, specific incompatibility. When a version is chosen, record it explicitly in the relevant package definition or build script with a comment explaining why that version was selected.
|
||||||
|
|
||||||
|
### 4. One Thing at a Time
|
||||||
|
This project has many interconnected subsystems. Work on them in the order defined by the [Phase Breakdown](#phase-breakdown--execution-order). Do not jump ahead. Do not start Phase N+1 until Phase N is complete or explicitly paused by the user.
|
||||||
|
|
||||||
|
### 5. Atomic, Testable Changes
|
||||||
|
Every change should be small enough to reason about in isolation. If a change touches more than one subsystem, split it. Every script and build step should be independently testable where possible.
|
||||||
|
|
||||||
|
### 6. Comments and Documentation
|
||||||
|
All shell scripts must have a header comment block explaining purpose, inputs, outputs, and assumptions. All Rust code must have doc comments on public items. All kernel config choices must have inline comments explaining why they were set.
|
||||||
|
|
||||||
|
### 7. File Placement
|
||||||
|
Never create files outside the defined [Project Structure](#project-structure) without explicitly discussing it first. Temporary or scratch files go in `/tmp` or a clearly marked `build/` directory and must be cleaned up.
|
||||||
|
|
||||||
|
### 8. No Systemd — Anywhere
|
||||||
|
This is a hard constraint. No systemd, no logind, no systemd-boot, no udev (use eudev instead), no anything from the systemd ecosystem. If a package has a hard systemd dependency, find an alternative or patch it out. Document the decision.
|
||||||
|
|
||||||
|
### 9. No Bootloader
|
||||||
|
The kernel boots via UEFI Stub (EFISTUB). No GRUB, no systemd-boot, no rEFInd. The kernel is the bootloader. This affects how the kernel is compiled and how the EFI partition is structured.
|
||||||
|
|
||||||
|
### 10. No Display Manager
|
||||||
|
No SDDM, no GDM, no LightDM. The system boots directly into a TTY, auto-logs in the user, and launches the Wayland compositor (dwl) from the shell profile. This must be seamless and instant.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Build a complete Linux distribution comprising four major deliverables, each living in its own subdirectory under `src/` and eventually becoming its own git repository:
|
||||||
|
|
||||||
|
1. **dpack** (`src/dpack`) — A custom package manager written in Rust, positioned between CRUX's `pkgutils` and Gentoo's `emerge` in complexity and capability.
|
||||||
|
2. **ISO builder** (`src/iso`) — Tooling to produce a bootable live CD/USB image of the distribution.
|
||||||
|
3. **Installer** (`src/install`) — A CRUX-style interactive installer that runs from the live environment and walks the user through disk selection, user creation, locale, timezone, and keyboard setup.
|
||||||
|
4. **Package repository** (`src/repos`) — The package definitions (recipes/ports) that dpack consumes to build and install software.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hardware Target
|
||||||
|
|
||||||
|
This distribution targets exactly one machine. Every optimization decision — kernel config, compiler flags, scheduler tuning — is made for this specific hardware and nothing else.
|
||||||
|
|
||||||
|
| Component | Model |
|
||||||
|
|---------------|--------------------------------------------------------------|
|
||||||
|
| Motherboard | ASUS ROG CROSSHAIR X870E HERO |
|
||||||
|
| CPU | AMD Ryzen 9 9950X3D (Zen 5, 16C/32T, 3D V-Cache) |
|
||||||
|
| RAM | Corsair Vengeance DDR5-6000 96GB CL30 (Dual Channel, 2×48GB) |
|
||||||
|
| Storage | Samsung 9100 PRO 2TB NVMe (PCIe 5.0 x4) |
|
||||||
|
| GPU | ASUS GeForce RTX 5090 ROG Astral LC OC 32GB GDDR7 |
|
||||||
|
|
||||||
|
### Compiler Flags (Global)
|
||||||
|
|
||||||
|
These flags should be set as the default `CFLAGS`/`CXXFLAGS` for the entire toolchain and all package builds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Targeting Zen 5 (znver5) — if GCC/LLVM version doesn't yet support znver5, use znver4
|
||||||
|
# and leave a TODO to revisit when compiler support lands.
|
||||||
|
export CFLAGS="-march=znver5 -O2 -pipe -fomit-frame-pointer"
|
||||||
|
export CXXFLAGS="${CFLAGS}"
|
||||||
|
export MAKEFLAGS="-j32" # 16 cores, 32 threads
|
||||||
|
export LDFLAGS="-Wl,-O1,--as-needed"
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `-O2` is chosen over `-O3` as the sane default. `-O3` can be enabled per-package where benchmarks show measurable improvement (e.g., mesa, wine). Document any per-package flag overrides in the package definition.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Decisions
|
||||||
|
|
||||||
|
These are locked-in decisions. Do not revisit them unless the user explicitly asks.
|
||||||
|
|
||||||
|
| Decision | Choice | Rationale |
|
||||||
|
|---------------------------|--------------------------------|--------------------------------------------------------------|
|
||||||
|
| Init system | SysVinit + custom rc scripts | Matches CRUX model. Simple, transparent, fast. |
|
||||||
|
| Service management | rc.conf + rc.d/ scripts | No daemons managing daemons. Direct control. |
|
||||||
|
| Device manager | eudev | udev replacement, no systemd dependency. |
|
||||||
|
| Boot method | EFISTUB (kernel as EFI binary) | No bootloader. Kernel boots directly via UEFI. |
|
||||||
|
| Display protocol | Wayland (via dwl compositor) | Modern, performant, required for latest Nvidia support. |
|
||||||
|
| X compatibility | XWayland | For legacy apps (Steam, some games). |
|
||||||
|
| Package manager | dpack (custom, Rust) | Core deliverable of this project. |
|
||||||
|
| Shell | bash (build) / zsh (user) | Bash for build scripts, zsh as user's interactive shell. |
|
||||||
|
| Filesystem | ext4 | Simpler, faster, battle-tested. User confirmed. |
|
||||||
|
| Partition scheme | GPT + EFI System Partition | Required for EFISTUB boot on UEFI systems. |
|
||||||
|
| C library | glibc | Broadest compatibility, required by Steam/Wine/Proton. |
|
||||||
|
| Privilege escalation | polkit + lxqt-policykit | Qt-based password agent. Lightweight, minimal deps. |
|
||||||
|
| Network management | dhcpcd | Ethernet only. Minimal, no WiFi needed. |
|
||||||
|
| Audio | PipeWire | Modern replacement for PulseAudio + ALSA, best game compat. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
project-root/
|
||||||
|
├── CLAUDE.md # THIS FILE — project directive
|
||||||
|
├── docs/
|
||||||
|
│ └── CHANGELOG.md # Mandatory changelog (see protocol below)
|
||||||
|
├── src/
|
||||||
|
│ ├── dpack/ # The custom package manager (Rust)
|
||||||
|
│ │ ├── Cargo.toml
|
||||||
|
│ │ ├── src/
|
||||||
|
│ │ │ ├── main.rs
|
||||||
|
│ │ │ ├── lib.rs
|
||||||
|
│ │ │ ├── config/ # Configuration parsing and management
|
||||||
|
│ │ │ ├── resolver/ # Dependency resolution engine
|
||||||
|
│ │ │ ├── sandbox/ # Build sandboxing (namespaces/bubblewrap)
|
||||||
|
│ │ │ ├── converter/ # Gentoo ebuild & CRUX Pkgfile converters
|
||||||
|
│ │ │ ├── db/ # Installed package database (file-based)
|
||||||
|
│ │ │ └── build/ # Package build orchestration
|
||||||
|
│ │ └── tests/
|
||||||
|
│ ├── iso/ # Live CD/USB creation tooling
|
||||||
|
│ │ ├── build-iso.sh # Main ISO build script
|
||||||
|
│ │ ├── overlay/ # Files overlaid onto the live filesystem
|
||||||
|
│ │ └── configs/ # ISO-specific configs (mkinitramfs, etc.)
|
||||||
|
│ ├── install/ # Installer scripts (runs from live env)
|
||||||
|
│ │ ├── install.sh # Main installer entry point
|
||||||
|
│ │ ├── modules/ # Modular installer steps
|
||||||
|
│ │ │ ├── disk.sh # Disk selection and partitioning
|
||||||
|
│ │ │ ├── user.sh # User/password creation
|
||||||
|
│ │ │ ├── locale.sh # Locale, timezone, keyboard
|
||||||
|
│ │ │ └── packages.sh # Base system package installation
|
||||||
|
│ │ └── configs/ # Template configs deployed during install
|
||||||
|
│ └── repos/ # Package repository (dpack format)
|
||||||
|
│ ├── core/ # Essential system packages (toolchain, kernel, coreutils, etc.)
|
||||||
|
│ ├── extra/ # Non-essential but common packages
|
||||||
|
│ ├── gaming/ # Steam, Proton, Wine, launchers
|
||||||
|
│ └── desktop/ # dwl, Wayland, fonts, themes, GUI apps
|
||||||
|
├── kernel/
|
||||||
|
│ └── config # The kernel .config file, fully commented
|
||||||
|
├── configs/
|
||||||
|
│ ├── rc.conf # Init system configuration
|
||||||
|
│ ├── rc.d/ # Service scripts
|
||||||
|
│ ├── fstab.template # Template fstab
|
||||||
|
│ └── dwl/ # dwl configuration and patches
|
||||||
|
└── reference/ # Symlinks or notes pointing to LFS/BLFS/CRUX/Gentoo material
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Rule:** If you need to create a directory or file not shown above, discuss it first unless it's clearly a subdirectory of an existing defined location.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase Breakdown & Execution Order
|
||||||
|
|
||||||
|
This project is broken into sequential phases. Each phase has clear entry criteria, deliverables, and exit criteria. **Work phases in order.** Phases may have sub-phases for very large work items.
|
||||||
|
|
||||||
|
### Phase 0 — Foundation & Toolchain Bootstrap
|
||||||
|
**Goal:** Establish a cross-compilation toolchain and minimal chroot environment capable of building everything else. This follows the LFS book chapters 4-8 conceptually, but with our hardware-specific flags and latest package versions.
|
||||||
|
|
||||||
|
**Deliverables:**
|
||||||
|
- Toolchain build scripts (binutils, gcc, glibc, etc.) targeting znver5
|
||||||
|
- A functional chroot with a working compiler and core utilities
|
||||||
|
- All scripts in `src/iso/` or a dedicated `toolchain/` directory
|
||||||
|
- Version manifest documenting every package version used
|
||||||
|
|
||||||
|
**Exit criteria:** Can compile and link a "Hello World" C program inside the chroot with correct `-march=znver5` targeting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 1 — dpack Core (Minimum Viable Package Manager)
|
||||||
|
**Goal:** Build the core of dpack to the point where it can parse a package definition, resolve dependencies from a local repo, and build a package in a sandbox.
|
||||||
|
|
||||||
|
**Sub-phases:**
|
||||||
|
1. **1a — Package format definition:** Design and document the `.toml` package definition format (the recipe file). Must be expressive enough to handle the range from simple `make install` packages to complex multi-step builds like the kernel or mesa.
|
||||||
|
2. **1b — Dependency resolver:** Implement the dependency resolution engine. Must handle: direct deps, build deps, optional deps, version constraints, and circular dependency detection.
|
||||||
|
3. **1c — Sandbox:** Implement build sandboxing using Linux namespaces (mount, PID, network) or bubblewrap. Packages build in an isolated root with only their declared dependencies visible.
|
||||||
|
4. **1d — Database:** Implement the installed-package database. File-based (e.g., TOML or custom format in `/var/lib/dpack/`). Tracks: package name, version, installed files, dependencies with exact versions, whether deps are shared or static.
|
||||||
|
5. **1e — Build & install orchestration:** Wire it all together. `dpack install <package>` should resolve deps → sandbox build → install to system → update database.
|
||||||
|
|
||||||
|
**Exit criteria:** Can `dpack install` a simple package (e.g., `zlib`) from a local repo definition, building it in a sandbox, and track it in the database.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2 — dpack Advanced Features
|
||||||
|
**Goal:** Implement the converters and smart dependency management.
|
||||||
|
|
||||||
|
**Sub-phases:**
|
||||||
|
1. **2a — CRUX Pkgfile converter:** Parse CRUX `Pkgfile` format and emit a `.toml` definition. Handle the common patterns (source download, build(), install steps).
|
||||||
|
2. **2b — Gentoo ebuild converter:** Parse Gentoo `.ebuild` files and emit `.toml` definitions. This is significantly more complex — handle USE flags by mapping them to dpack's optional dependency system, handle eclasses by inlining or converting common ones.
|
||||||
|
3. **2c — Shared library conflict detection:** Implement the smart dependency resolution described in the spec: detect when a shared library version is needed by multiple packages, check if an update exists that satisfies all consumers, and if not, prompt the user about static compilation.
|
||||||
|
4. **2d — Package upgrade/remove:** `dpack upgrade`, `dpack remove` with proper dependency reverse-tracking.
|
||||||
|
|
||||||
|
**Exit criteria:** Can convert a real CRUX Pkgfile and a real Gentoo ebuild into dpack format and build them successfully. Shared lib conflict detection works on a constructed test case.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 3 — Base System Packages
|
||||||
|
**Goal:** Write dpack package definitions for the complete base system (everything LFS builds, plus eudev, PipeWire, networking, etc.). Build and install them all using dpack.
|
||||||
|
|
||||||
|
**Deliverables:**
|
||||||
|
- Complete `src/repos/core/` with all base system packages
|
||||||
|
- A bootable (in a VM or chroot) minimal system with: kernel, init, shell, coreutils, networking, eudev
|
||||||
|
|
||||||
|
**Exit criteria:** System boots in a QEMU VM via EFISTUB, drops to a shell, has networking.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 4 — Kernel Configuration
|
||||||
|
**Goal:** Produce a fully optimized, hardware-specific kernel config for the target machine.
|
||||||
|
|
||||||
|
**Key decisions (all must be commented in the config):**
|
||||||
|
- Scheduler: EEVDF (default in 6.19+) or BORE patchset — research and decide
|
||||||
|
- CPU governor: schedutil (Zen 5 optimized)
|
||||||
|
- Preemption model: PREEMPT (full preemption for gaming latency)
|
||||||
|
- NVMe: PCIe 5.0, Samsung 9100 PRO specific optimizations
|
||||||
|
- GPU: Nvidia DRM/KMS, nvidia-open module support (RTX 5090)
|
||||||
|
- USB: XHCI for USB 3.x/4.0 on X870E
|
||||||
|
- Audio: snd-hda-intel for onboard, plus USB audio class
|
||||||
|
- Networking: Realtek RTL8125BN (2.5GbE on X870E Hero), no WiFi
|
||||||
|
- IOMMU: AMD-Vi enabled (for potential GPU passthrough later)
|
||||||
|
- Filesystem: ext4/btrfs built-in (not module) for root
|
||||||
|
- Disable everything not needed: no Bluetooth (unless user wants it), no legacy ISA, no ancient network drivers, no PCMCIA, no infrared, etc.
|
||||||
|
|
||||||
|
**Exit criteria:** Kernel compiles, boots via EFISTUB on QEMU (and eventually on real hardware), detects all target hardware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 5 — Init System & Service Scripts
|
||||||
|
**Goal:** Implement the SysVinit + rc.d service management layer.
|
||||||
|
|
||||||
|
**Deliverables:**
|
||||||
|
- `rc.conf` — system-wide configuration (hostname, timezone, locale, keymap, network, daemons array)
|
||||||
|
- `rc.d/` scripts for: networking, eudev, syslog, PipeWire, dbus (needed for polkit)
|
||||||
|
- `/etc/inittab` configured for auto-login on tty1
|
||||||
|
- Shell profile that auto-starts dwl on login to tty1
|
||||||
|
|
||||||
|
**Exit criteria:** System boots → auto-logs in → launches dwl compositor, all without user interaction.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 6 — Desktop Environment (dwl + Wayland Stack)
|
||||||
|
**Goal:** Build and configure dwl with patches, plus the full Wayland stack.
|
||||||
|
|
||||||
|
**Deliverables:**
|
||||||
|
- dpack definitions for: wayland, wayland-protocols, wlroots, dwl (with patches), xwayland, foot/wezterm, dmenu/fuzzel/tofi (launcher)
|
||||||
|
- dwl patches applied: bar (status bar), gaps (window gaps), plus any others useful for gaming/dev (vanitygaps, autostart, etc.)
|
||||||
|
- dwl config.h tailored for the user's workflow
|
||||||
|
- Nvidia-specific Wayland env vars (`WLR_NO_HARDWARE_CURSORS`, etc.)
|
||||||
|
|
||||||
|
**Exit criteria:** dwl launches with bar and gaps, can open a terminal, can launch Firefox.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 7 — Nvidia Driver Stack
|
||||||
|
**Goal:** Build and install the proprietary Nvidia driver (or nvidia-open) for the RTX 5090.
|
||||||
|
|
||||||
|
**Key concerns:**
|
||||||
|
- RTX 5090 (Blackwell) requires very recent driver branches — likely 570.x+ or newer
|
||||||
|
- Must build DKMS or manual kernel module against our custom kernel
|
||||||
|
- Needs: nvidia-drm, nvidia-modeset, nvidia-uvm
|
||||||
|
- Vulkan ICD, OpenGL, EGL, GBM support
|
||||||
|
- 32-bit compatibility libs for Wine/Proton
|
||||||
|
|
||||||
|
**Exit criteria:** `nvidia-smi` works, Vulkan `vkcube` renders, OpenGL `glxgears` renders via XWayland.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 8 — Gaming Stack
|
||||||
|
**Goal:** Install Steam, Proton, Wine, and game launchers.
|
||||||
|
|
||||||
|
**Deliverables:**
|
||||||
|
- dpack definitions for: Steam (native Linux), Wine (latest stable + staging), Proton-GE or official Proton, protontricks, winetricks, gamemode, mangohud
|
||||||
|
- Ubisoft Connect via Wine/Proton prefix
|
||||||
|
- PrismLauncher (Minecraft launcher) — requires Java (OpenJDK)
|
||||||
|
- 32-bit multilib support as needed by Steam/Wine
|
||||||
|
|
||||||
|
**Exit criteria:** Steam launches, can download and run a Proton game. PrismLauncher launches Minecraft.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 9 — Application Stack
|
||||||
|
**Goal:** Install remaining user applications.
|
||||||
|
|
||||||
|
**Deliverables:**
|
||||||
|
- dpack definitions for: Firefox, WezTerm, FreeCAD, polkit + polkit-agent (lxqt-policykit or polkit-gnome-agent for password prompts)
|
||||||
|
- AMD microcode package (for CPU microcode updates via early initramfs loading)
|
||||||
|
|
||||||
|
**Exit criteria:** All listed applications launch and function correctly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 10 — ISO Builder
|
||||||
|
**Goal:** Build tooling that produces a bootable live USB/CD image containing the installer and a minimal live environment.
|
||||||
|
|
||||||
|
**Deliverables:**
|
||||||
|
- `src/iso/build-iso.sh` — orchestrates the full ISO build
|
||||||
|
- Live environment contains: base system, dpack, installer scripts, basic shell tools
|
||||||
|
- Boots via EFISTUB (ISO uses El Torito + ESP for UEFI boot)
|
||||||
|
- squashfs compressed root filesystem for the live environment
|
||||||
|
|
||||||
|
**Exit criteria:** ISO boots in QEMU, drops into a live shell, installer can be launched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 11 — Installer
|
||||||
|
**Goal:** Build the CRUX-style interactive installer.
|
||||||
|
|
||||||
|
**Installer flow:**
|
||||||
|
1. Welcome screen — brief info about the distro
|
||||||
|
2. Disk selection — list available disks, let user choose, offer auto-partition (GPT: ESP + root + optional swap) or manual
|
||||||
|
3. Filesystem — format partitions (ext4/btrfs)
|
||||||
|
4. Base install — extract/install base system packages via dpack
|
||||||
|
5. Kernel install — copy kernel to ESP
|
||||||
|
6. User setup — create user account, set password, set root password
|
||||||
|
7. Locale/timezone/keymap — interactive selection
|
||||||
|
8. Bootloader (EFISTUB) — set up EFI boot entry via `efibootmgr`
|
||||||
|
9. Post-install package selection — offer to install desktop/gaming/dev packages
|
||||||
|
10. Finalize — generate fstab, set hostname, unmount, reboot
|
||||||
|
|
||||||
|
**Exit criteria:** Full install from live ISO to bootable system in QEMU.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 12 — Integration Testing & Polish
|
||||||
|
**Goal:** End-to-end testing of the complete flow: ISO → install → boot → desktop → gaming.
|
||||||
|
|
||||||
|
**Deliverables:**
|
||||||
|
- Test checklist covering every phase's exit criteria
|
||||||
|
- Bug fixes and polish
|
||||||
|
- Final CHANGELOG update
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## dpack — The Package Manager
|
||||||
|
|
||||||
|
### Package Definition Format (`.toml`)
|
||||||
|
|
||||||
|
The format is a TOML file. Here's the canonical structure:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "zlib"
|
||||||
|
version = "1.3.1"
|
||||||
|
description = "Compression library"
|
||||||
|
url = "https://zlib.net/"
|
||||||
|
license = "zlib"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://zlib.net/zlib-${version}.tar.xz"
|
||||||
|
sha256 = "abc123..." # Always verify sources
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Runtime dependencies
|
||||||
|
run = []
|
||||||
|
|
||||||
|
# Build-time only dependencies
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
# Optional features (inspired by Gentoo USE flags)
|
||||||
|
[dependencies.optional]
|
||||||
|
static = { description = "Build static library", default = true }
|
||||||
|
minizip = { description = "Build minizip utility", deps = [] }
|
||||||
|
|
||||||
|
[build]
|
||||||
|
# Build steps — executed in sandbox
|
||||||
|
configure = "./configure --prefix=/usr"
|
||||||
|
make = "make"
|
||||||
|
install = "make DESTDIR=${PKG} install"
|
||||||
|
|
||||||
|
# Per-package flag overrides (optional)
|
||||||
|
[build.flags]
|
||||||
|
cflags = "" # Empty = use global defaults. Set explicitly to override.
|
||||||
|
ldflags = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
> **This format is a starting point.** It will evolve during Phase 1. Any changes must be documented.
|
||||||
|
|
||||||
|
### Database Location and Format
|
||||||
|
|
||||||
|
Installed package database lives at `/var/lib/dpack/db/`. One TOML file per installed package:
|
||||||
|
|
||||||
|
```
|
||||||
|
/var/lib/dpack/db/
|
||||||
|
├── zlib.toml # Tracks version, files, deps
|
||||||
|
├── gcc.toml
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sandbox Implementation
|
||||||
|
|
||||||
|
The build sandbox must:
|
||||||
|
- Use Linux namespaces (mount, PID, optionally network) or wrap bubblewrap (`bwrap`)
|
||||||
|
- Mount only the declared dependencies into the sandbox's `/usr`, `/lib`, etc.
|
||||||
|
- Use a fresh tmpfs or overlay for the build directory
|
||||||
|
- Prevent network access during build (optional: configurable, some packages need to download at build time)
|
||||||
|
- Capture the installed files via `DESTDIR` to a staging area before committing to the real filesystem
|
||||||
|
|
||||||
|
### Converter Architecture
|
||||||
|
|
||||||
|
Converters are separate modules that parse foreign package formats and emit `.toml` TOML:
|
||||||
|
|
||||||
|
- **CRUX converter:** Parses `Pkgfile` (bash-like syntax with `source=()`, `build()` function). Relatively straightforward.
|
||||||
|
- **Gentoo converter:** Parses `.ebuild` (bash + eclasses + complex variable expansion). Best-effort conversion — flag anything that can't be cleanly converted and require manual review. Map common USE flags to dpack optional deps. Do not try to replicate the full Portage system.
|
||||||
|
|
||||||
|
### Shared Library Conflict Resolution
|
||||||
|
|
||||||
|
When `dpack install foo` would update a shared library that `bar` also depends on:
|
||||||
|
1. Check if `bar` has an update that works with the new library version.
|
||||||
|
2. If yes, offer to update `bar` as well.
|
||||||
|
3. If no, warn the user and offer options: (a) compile `foo` with the library statically linked, (b) hold back the library update, (c) force it (user accepts the risk).
|
||||||
|
4. Track the decision in the database.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kernel Configuration
|
||||||
|
|
||||||
|
**Kernel version:** 6.19.8 (or latest 6.19.x stable at time of build — verify before building)
|
||||||
|
|
||||||
|
### Critical Config Flags
|
||||||
|
|
||||||
|
```
|
||||||
|
# CPU — AMD Zen 5 (9950X3D)
|
||||||
|
CONFIG_MZEN5=y # If available, otherwise MZEN4
|
||||||
|
CONFIG_X86_X2APIC=y
|
||||||
|
CONFIG_AMD_MEM_ENCRYPT=y # SME support
|
||||||
|
CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y
|
||||||
|
CONFIG_X86_AMD_PSTATE=y # AMD P-State driver (preferred over acpi-cpufreq for Zen 5)
|
||||||
|
CONFIG_AMD_PMC=y # Platform Management Controller
|
||||||
|
|
||||||
|
# Scheduler
|
||||||
|
CONFIG_PREEMPT=y # Full preemption for low-latency gaming
|
||||||
|
CONFIG_HZ_1000=y # 1000Hz tick for responsiveness
|
||||||
|
CONFIG_NO_HZ_FULL=y # Tickless for idle cores
|
||||||
|
|
||||||
|
# NVMe — Samsung 9100 PRO (PCIe 5.0)
|
||||||
|
CONFIG_BLK_DEV_NVME=y # Built-in (not module) — root is on NVMe
|
||||||
|
CONFIG_NVME_MULTIPATH=n # Single disk, not needed
|
||||||
|
|
||||||
|
# GPU — NVIDIA RTX 5090 (Blackwell)
|
||||||
|
CONFIG_DRM=y
|
||||||
|
CONFIG_DRM_NOUVEAU=n # Disable nouveau — proprietary driver only
|
||||||
|
CONFIG_DRM_SIMPLEDRM=y # Fallback framebuffer before nvidia loads
|
||||||
|
|
||||||
|
# Network — Realtek RTL8125BN (2.5GbE on X870E Hero)
|
||||||
|
CONFIG_R8169=y # Realtek 8125/8169 family driver
|
||||||
|
CONFIG_NET_VENDOR_REALTEK=y
|
||||||
|
|
||||||
|
# USB/Thunderbolt — X870E has USB4
|
||||||
|
CONFIG_USB_XHCI_HCD=y
|
||||||
|
CONFIG_USB4=y # Unified USB4/Thunderbolt support
|
||||||
|
CONFIG_THUNDERBOLT=y
|
||||||
|
|
||||||
|
# IOMMU — AMD-Vi
|
||||||
|
CONFIG_AMD_IOMMU=y
|
||||||
|
CONFIG_AMD_IOMMU_V2=y
|
||||||
|
CONFIG_IOMMU_DEFAULT_DMA_LAZY=y
|
||||||
|
|
||||||
|
# Sound — Onboard ALC4082 (or similar on X870E Hero)
|
||||||
|
CONFIG_SND_HDA_INTEL=y
|
||||||
|
CONFIG_SND_HDA_CODEC_REALTEK=y
|
||||||
|
|
||||||
|
# Filesystem
|
||||||
|
CONFIG_EXT4_FS=y # Or BTRFS — whichever is chosen
|
||||||
|
CONFIG_TMPFS=y
|
||||||
|
CONFIG_VFAT_FS=y # For EFI partition
|
||||||
|
CONFIG_EFI_PARTITION=y
|
||||||
|
CONFIG_EFIVAR_FS=y
|
||||||
|
CONFIG_EFI_STUB=y # THIS IS CRITICAL — enables direct UEFI boot
|
||||||
|
|
||||||
|
# Disable bloat
|
||||||
|
CONFIG_BLUETOOTH=n # Enable if user wants it — ASK
|
||||||
|
CONFIG_WIRELESS=n # Enable if WiFi needed — ASK
|
||||||
|
CONFIG_PCMCIA=n
|
||||||
|
CONFIG_INFINIBAND=n
|
||||||
|
CONFIG_ISDN=n
|
||||||
|
CONFIG_INPUT_JOYSTICK=y # Keep for gaming controllers!
|
||||||
|
CONFIG_INPUT_TOUCHSCREEN=n
|
||||||
|
CONFIG_SOUND_OSS_CORE=n # ALSA only, no OSS emulation
|
||||||
|
```
|
||||||
|
|
||||||
|
> **These are starting points.** The full `.config` will be generated during Phase 4 with `make menuconfig` as the base, then these overrides applied and verified. Every non-default choice must have a comment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Init System
|
||||||
|
|
||||||
|
### /etc/inittab (Skeleton)
|
||||||
|
|
||||||
|
```
|
||||||
|
# DarkForge Linux inittab
|
||||||
|
id:3:initdefault:
|
||||||
|
|
||||||
|
si::sysinit:/etc/rc.d/rc.sysinit
|
||||||
|
l3:3:wait:/etc/rc.d/rc.multi
|
||||||
|
|
||||||
|
# Auto-login on tty1 — no password prompt
|
||||||
|
1:2345:respawn:/sbin/agetty --autologin danny --noclear 38400 tty1 linux
|
||||||
|
2:2345:respawn:/sbin/agetty 38400 tty2 linux
|
||||||
|
|
||||||
|
ca::ctrlaltdel:/sbin/shutdown -r now
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shell Profile Auto-Start (~/.bash_profile or ~/.profile)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Auto-start dwl on tty1 if not already running
|
||||||
|
if [ -z "${WAYLAND_DISPLAY}" ] && [ "$(tty)" = "/dev/tty1" ]; then
|
||||||
|
exec dwl
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
### rc.conf
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# DarkForge Linux System Configuration
|
||||||
|
|
||||||
|
HOSTNAME="darkforge"
|
||||||
|
TIMEZONE="America/New_York" # Set during install — placeholder, confirm with user
|
||||||
|
KEYMAP="us" # Set during install — placeholder, confirm with user
|
||||||
|
LOCALE="en_US.UTF-8" # Set during install — placeholder, confirm with user
|
||||||
|
|
||||||
|
# Daemons to start at boot (order matters)
|
||||||
|
DAEMONS=(eudev syslog dbus dhcpcd pipewire)
|
||||||
|
|
||||||
|
# Kernel modules to load at boot (if any aren't autoloaded)
|
||||||
|
MODULES=(nvidia nvidia-modeset nvidia-drm nvidia-uvm)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ISO & Installer
|
||||||
|
|
||||||
|
### ISO Build Requirements
|
||||||
|
|
||||||
|
- `squashfs-tools` — compress the live root filesystem
|
||||||
|
- `xorriso` or `genisoimage` — create the ISO9660 image
|
||||||
|
- `mtools` — manipulate the EFI boot image within the ISO
|
||||||
|
- The ISO must support UEFI boot (El Torito with EFI System Partition image)
|
||||||
|
- No legacy BIOS boot support needed (target hardware is UEFI only)
|
||||||
|
|
||||||
|
### Installer Requirements
|
||||||
|
|
||||||
|
- Interactive, text-based (dialog/whiptail or plain shell prompts)
|
||||||
|
- Must ask for: target disk, partition scheme (auto/manual), filesystem, username, user password, root password, timezone, locale, keymap
|
||||||
|
- Must setup: partitions, install base packages via dpack, install kernel to ESP, configure fstab/rc.conf/inittab, create EFI boot entry via `efibootmgr`
|
||||||
|
- Must be idempotent where possible (re-running a step doesn't break things)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Target Package List
|
||||||
|
|
||||||
|
These packages must have dpack definitions and be installable by the end of the project. Organized by repo category:
|
||||||
|
|
||||||
|
### core/
|
||||||
|
Base system (toolchain, kernel, coreutils, util-linux, bash, glibc, binutils, gcc, make, etc.), eudev, sysvinit, dbus, dhcpcd, openssl, curl, git, tar, xz, zstd, bzip2, gzip, pkg-config, meson, ninja, cmake, python, perl
|
||||||
|
|
||||||
|
### extra/
|
||||||
|
PipeWire, wireplumber, polkit, lxqt-policykit (password agent), mesa, vulkan-loader, vulkan-tools, libdrm, fontconfig, freetype, harfbuzz, pango, cairo, glib, gtk3, gtk4, qt6-base
|
||||||
|
|
||||||
|
### desktop/
|
||||||
|
wayland, wayland-protocols, wlroots, dwl (with patches), xwayland, wezterm, firefox, freecad, foot (backup terminal), fuzzel or tofi (launcher), grim + slurp (screenshots), wl-clipboard
|
||||||
|
|
||||||
|
### gaming/
|
||||||
|
steam, wine (latest stable), proton-ge (or valve proton), protontricks, winetricks, gamemode, mangohud, lib32 compatibility packages, dxvk, vkd3d-proton, prismlauncher, openjdk
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reference Material
|
||||||
|
|
||||||
|
The following reference documents are available in the project root and should be consulted during the relevant phases:
|
||||||
|
|
||||||
|
| Material | Location | Used In |
|
||||||
|
|-----------------------------------|---------------------------------------|------------------------|
|
||||||
|
| LFS Book 13.0 | `LFS-BOOK-r13.0-4-NOCHUNKS.html` | Phase 0, 3 |
|
||||||
|
| BLFS Book 12.4 | `BLFS-BOOK-12.4-nochunks.html` | Phase 3, 6-9 |
|
||||||
|
| GLFS (Gaming LFS) | `glfs-13.0/` | Phase 7, 8 |
|
||||||
|
| MLFS (Musl LFS) | `mlfs/` | Reference only |
|
||||||
|
| SLFS | `slfs-13.0/` | Reference only |
|
||||||
|
| CRUX ISO build scripts | `crux_iso/` | Phase 10 |
|
||||||
|
| CRUX ports (all repos) | `crux_ports/` | Phase 2a, 3 |
|
||||||
|
| Gentoo ebuilds | `gentoo/` | Phase 2b, 3 |
|
||||||
|
| dwl source | `dwl/` | Phase 6 |
|
||||||
|
| dwl patches | `dwl-patches/` | Phase 6 |
|
||||||
|
|
||||||
|
> **Always cross-reference** multiple sources when writing package definitions. LFS gives build instructions, CRUX gives packaging patterns, Gentoo gives dependency information and USE flag mappings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changelog Protocol
|
||||||
|
|
||||||
|
**Location:** `docs/CHANGELOG.md`
|
||||||
|
|
||||||
|
**Every change must produce an entry.** No exceptions. The format is:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## V<N> <YYYY-MM-DD HH:MM:SS>
|
||||||
|
|
||||||
|
**<Short description — one line, imperative mood>**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- <What was added, modified, or removed. Be specific. Reference file paths.>
|
||||||
|
- <Another change>
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- <Any changes to the plan, scope, or architecture decisions. "None" if none.>
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- <Known issues, TODOs, rough edges left behind. "None" if none.>
|
||||||
|
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- `<N>` is an auto-incrementing integer starting at 1. Never reuse a number.
|
||||||
|
- Timestamps are in UTC.
|
||||||
|
- The short description uses imperative mood: "Add zlib package definition" not "Added zlib package definition".
|
||||||
|
- If a session produces multiple logical changes, each gets its own entry.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Protocol
|
||||||
|
|
||||||
|
At the start of every session:
|
||||||
|
|
||||||
|
1. **Read this file in full.** Non-negotiable.
|
||||||
|
2. **Read `docs/CHANGELOG.md`** to understand where the project left off.
|
||||||
|
3. **Ask the user:** "Where should we pick up?" or propose the next logical step based on the phase plan.
|
||||||
|
4. **Confirm the plan** for this session before writing any code.
|
||||||
|
|
||||||
|
At the end of every session:
|
||||||
|
|
||||||
|
1. **Write changelog entries** for everything done.
|
||||||
|
2. **Summarize** what was accomplished and what the next step is.
|
||||||
|
3. **Flag any open questions** or decisions needed from the user.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known Pitfalls & Guardrails
|
||||||
|
|
||||||
|
These are lessons learned and things to watch out for. Add to this section as the project progresses.
|
||||||
|
|
||||||
|
1. **znver5 compiler support:** As of early 2025, GCC 14 and LLVM 18 may not fully support `-march=znver5`. If not, use `-march=znver4` and leave a `TODO` to revisit. Check `gcc -march=native -Q --help=target` on the target hardware if possible.
|
||||||
|
|
||||||
|
2. **RTX 5090 driver maturity:** The RTX 5090 (Blackwell) is very new. The open-source `nvidia-open` kernel module may not fully support it yet. Be prepared to use the proprietary blob. Track the driver version requirement carefully.
|
||||||
|
|
||||||
|
3. **Steam 32-bit dependencies:** Steam requires a large set of 32-bit libraries (multilib). This significantly complicates the build. Plan for it early — the toolchain may need multilib GCC, and many packages need 32-bit builds.
|
||||||
|
|
||||||
|
4. **Wine/Proton 32-bit:** Same as Steam. Wine needs a multilib build environment.
|
||||||
|
|
||||||
|
5. **dwl patch conflicts:** dwl patches are written against specific commits. Applying multiple patches will almost certainly produce conflicts. Plan to apply them one at a time, resolve conflicts, and test after each one.
|
||||||
|
|
||||||
|
6. **EFISTUB quirks:** Some UEFI firmware implementations are picky about the kernel binary location on the ESP. Standard location is `/EFI/Linux/vmlinuz.efi` or `/EFI/BOOT/BOOTX64.EFI`. Test with the target motherboard's firmware.
|
||||||
|
|
||||||
|
7. **PipeWire without systemd:** PipeWire is designed to work with systemd user sessions. Running it without systemd requires manually starting it (and WirePlumber) from the shell profile or an rc.d script. This is doable but requires careful setup.
|
||||||
|
|
||||||
|
8. **Polkit without systemd:** Similar to PipeWire — polkit's default backend uses systemd. You'll need the `polkit-duktape` or `polkit-mozjs` backend with an elogind or seatd session. Investigate seatd as the minimal seat manager.
|
||||||
|
|
||||||
|
9. **Wayland + Nvidia:** Nvidia Wayland support has improved dramatically but still requires specific env vars and careful driver setup. `WLR_NO_HARDWARE_CURSORS=1` is often still needed. wlroots must be built with the Nvidia backend.
|
||||||
|
|
||||||
|
10. **Gentoo ebuild conversion is best-effort.** Ebuilds can be extraordinarily complex (eclasses, conditional deps, slot dependencies, multi-phase builds). The converter should handle 80% of cases and flag the rest for manual review. Do not try to build a full Portage reimplementation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resolved Questions
|
||||||
|
|
||||||
|
All open questions have been answered (2026-03-19). These decisions are now locked in.
|
||||||
|
|
||||||
|
1. **Filesystem:** ext4 — simpler, faster, battle-tested.
|
||||||
|
2. **Bluetooth:** Disabled (`CONFIG_BLUETOOTH=n`).
|
||||||
|
3. **WiFi:** Ethernet only. No WiFi drivers, no iwd.
|
||||||
|
4. **Network manager:** dhcpcd (minimal, ethernet-only).
|
||||||
|
5. **Default shell:** zsh for the user. Bash remains the build script shell.
|
||||||
|
6. **Ubisoft launcher:** Skipped for now. Can add later.
|
||||||
|
7. **Password agent:** lxqt-policykit (Qt-based, lightweight).
|
||||||
|
8. **Swap:** 96GB swap partition (matching RAM size) to enable hibernation/sleep.
|
||||||
|
9. **Hostname:** `darkforge`
|
||||||
|
10. **Username:** `danny`
|
||||||
159
README.md
Normal file
159
README.md
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
# DarkForge Linux
|
||||||
|
|
||||||
|
A custom, from-scratch Linux distribution built for one machine, one user — optimized ruthlessly for gaming and development.
|
||||||
|
|
||||||
|
## Target Hardware
|
||||||
|
|
||||||
|
| Component | Model |
|
||||||
|
|-----------|-------|
|
||||||
|
| CPU | AMD Ryzen 9 9950X3D (Zen 5, 16C/32T, 3D V-Cache) |
|
||||||
|
| RAM | Corsair Vengeance DDR5-6000 96GB CL30 (2×48GB) |
|
||||||
|
| Storage | Samsung 9100 PRO 2TB NVMe (PCIe 5.0 x4) |
|
||||||
|
| GPU | ASUS GeForce RTX 5090 ROG Astral LC OC 32GB GDDR7 |
|
||||||
|
| Motherboard | ASUS ROG CROSSHAIR X870E HERO |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
DarkForge uses a minimal, transparent stack with no systemd:
|
||||||
|
|
||||||
|
- **Init:** SysVinit + custom rc.d scripts
|
||||||
|
- **Device manager:** eudev (no systemd dependency)
|
||||||
|
- **Boot:** EFISTUB — the kernel is the bootloader, no GRUB
|
||||||
|
- **Display:** Wayland via dwl compositor (dwm-like)
|
||||||
|
- **Audio:** PipeWire (started as user service)
|
||||||
|
- **Networking:** dhcpcd (ethernet only)
|
||||||
|
- **Package manager:** dpack (custom, written in Rust)
|
||||||
|
- **Shell:** zsh (user) / bash (build scripts)
|
||||||
|
- **Filesystem:** ext4
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
darkforge/
|
||||||
|
├── CLAUDE.md # Project directive (AI assistant context)
|
||||||
|
├── README.md # This file
|
||||||
|
├── docs/
|
||||||
|
│ └── CHANGELOG.md # Mandatory changelog for every change
|
||||||
|
├── src/
|
||||||
|
│ ├── dpack/ # Custom package manager (Rust)
|
||||||
|
│ ├── iso/ # Live ISO builder
|
||||||
|
│ ├── install/ # Interactive installer
|
||||||
|
│ └── repos/ # Package definitions (124 packages)
|
||||||
|
│ ├── core/ # 67 base system packages
|
||||||
|
│ ├── extra/ # 26 libraries and frameworks
|
||||||
|
│ ├── desktop/ # 19 Wayland/desktop packages
|
||||||
|
│ └── gaming/ # 12 gaming packages
|
||||||
|
├── toolchain/ # Cross-compilation bootstrap scripts
|
||||||
|
│ ├── scripts/ # 34 build scripts (LFS Ch. 5-7)
|
||||||
|
│ ├── sources/ # Downloaded source tarballs
|
||||||
|
│ └── VERSION_MANIFEST.md
|
||||||
|
├── kernel/
|
||||||
|
│ └── config # Hardware-specific kernel .config
|
||||||
|
├── configs/
|
||||||
|
│ ├── rc.conf # System configuration
|
||||||
|
│ ├── inittab # SysVinit config
|
||||||
|
│ ├── rc.d/ # Service scripts
|
||||||
|
│ ├── zprofile # User shell profile (auto-starts dwl)
|
||||||
|
│ └── fstab.template # Filesystem table template
|
||||||
|
└── reference/ # LFS, BLFS, CRUX, Gentoo reference docs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Phases
|
||||||
|
|
||||||
|
The project is built in sequential phases. Each phase has clear deliverables and exit criteria.
|
||||||
|
|
||||||
|
| Phase | Description | Status |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| 0 | Toolchain bootstrap (cross-compiler) | Scripts written |
|
||||||
|
| 1 | dpack core (parser, resolver, sandbox, db, build) | Implemented |
|
||||||
|
| 2 | dpack advanced (converters, solib, upgrade/remove) | Implemented |
|
||||||
|
| 3 | Base system packages (66 core/ definitions) | Complete |
|
||||||
|
| 4 | Kernel configuration | Complete |
|
||||||
|
| 5 | Init system and service scripts | Complete |
|
||||||
|
| 6 | Desktop environment (Wayland/dwl) | Definitions written |
|
||||||
|
| 7 | NVIDIA driver stack | Definition written |
|
||||||
|
| 8 | Gaming stack (Steam, Wine, Proton) | Definitions written |
|
||||||
|
| 9 | Application stack (WezTerm, FreeCAD) | Definitions written |
|
||||||
|
| 10 | ISO builder | Script written |
|
||||||
|
| 11 | Interactive installer | Scripts written |
|
||||||
|
| 12 | Integration testing | Pending |
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- A Linux host system for cross-compilation (or the target machine itself)
|
||||||
|
- GCC 15.2.0+ (for `-march=znver5` support)
|
||||||
|
- Rust toolchain (for building dpack)
|
||||||
|
- ~50GB disk space for the build
|
||||||
|
|
||||||
|
### 1. Build dpack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/dpack
|
||||||
|
cargo build --release
|
||||||
|
sudo install -m755 target/release/dpack /usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Bootstrap the toolchain
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LFS=/mnt/darkforge
|
||||||
|
# Create and mount your target partition, then:
|
||||||
|
bash toolchain/scripts/000-env-setup.sh
|
||||||
|
bash toolchain/scripts/000a-download-sources.sh
|
||||||
|
bash toolchain/scripts/build-all.sh cross
|
||||||
|
bash toolchain/scripts/build-all.sh temp
|
||||||
|
# Enter chroot and run Phase 7 scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Build the kernel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp kernel/config /usr/src/linux-6.19.8/.config
|
||||||
|
cd /usr/src/linux-6.19.8
|
||||||
|
make olddefconfig
|
||||||
|
make -j32
|
||||||
|
make modules_install
|
||||||
|
cp arch/x86/boot/bzImage /boot/vmlinuz
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Build the ISO
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash src/iso/build-iso.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Install
|
||||||
|
|
||||||
|
Boot the ISO on the target machine and run `install`.
|
||||||
|
|
||||||
|
## Compiler Flags
|
||||||
|
|
||||||
|
All packages are built with Zen 5 optimizations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CFLAGS="-march=znver5 -O2 -pipe -fomit-frame-pointer"
|
||||||
|
CXXFLAGS="${CFLAGS}"
|
||||||
|
MAKEFLAGS="-j32"
|
||||||
|
LDFLAGS="-Wl,-O1,--as-needed"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
- **No systemd** — SysVinit + rc.d for transparency and speed
|
||||||
|
- **No bootloader** — EFISTUB direct kernel boot
|
||||||
|
- **No display manager** — auto-login to tty1, dwl launched from shell profile
|
||||||
|
- **ext4** — simpler and faster than btrfs for this use case
|
||||||
|
- **96GB swap** — matches RAM size for hibernation support
|
||||||
|
- **Single target** — every optimization targets exactly this hardware
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The DarkForge project itself is MIT licensed. Individual packages retain their upstream licenses as documented in their `.toml` definitions.
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
|
||||||
|
```
|
||||||
|
git@git.dannyhaslund.dk:danny8632/darkforge.git
|
||||||
|
```
|
||||||
20
configs/fstab.template
Normal file
20
configs/fstab.template
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — /etc/fstab
|
||||||
|
# ============================================================================
|
||||||
|
# Filesystem table. Populated by the installer with actual UUIDs.
|
||||||
|
# Partition scheme: GPT with ESP + root (ext4) + swap (96GB)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# <device> <mount> <type> <options> <dump> <pass>
|
||||||
|
|
||||||
|
# Root filesystem — ext4 on NVMe
|
||||||
|
UUID=__ROOT_UUID__ / ext4 defaults,noatime 0 1
|
||||||
|
|
||||||
|
# EFI System Partition — kernel lives here
|
||||||
|
UUID=__ESP_UUID__ /boot/efi vfat defaults,noatime 0 2
|
||||||
|
|
||||||
|
# Swap partition — 96GB for hibernation support
|
||||||
|
UUID=__SWAP_UUID__ none swap defaults 0 0
|
||||||
|
|
||||||
|
# Pseudo-filesystems (mounted by rc.sysinit, listed here for completeness)
|
||||||
|
tmpfs /tmp tmpfs defaults,nosuid,nodev 0 0
|
||||||
34
configs/inittab
Normal file
34
configs/inittab
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — /etc/inittab
|
||||||
|
# ============================================================================
|
||||||
|
# SysVinit configuration. Defines runlevels and getty spawning.
|
||||||
|
# Runlevel 3 = multi-user with networking (our default).
|
||||||
|
# No display manager — tty1 auto-logs in 'danny' and starts dwl.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Default runlevel
|
||||||
|
id:3:initdefault:
|
||||||
|
|
||||||
|
# System initialization script (runs once at boot)
|
||||||
|
si::sysinit:/etc/rc.d/rc.sysinit
|
||||||
|
|
||||||
|
# Runlevel scripts
|
||||||
|
l0:0:wait:/etc/rc.d/rc.shutdown
|
||||||
|
l3:3:wait:/etc/rc.d/rc.multi
|
||||||
|
l6:6:wait:/etc/rc.d/rc.reboot
|
||||||
|
|
||||||
|
# --- Virtual consoles -------------------------------------------------------
|
||||||
|
# tty1: Auto-login danny — no password prompt, launches dwl via .zprofile
|
||||||
|
1:2345:respawn:/sbin/agetty --autologin danny --noclear 38400 tty1 linux
|
||||||
|
|
||||||
|
# tty2-4: Standard login prompts (for emergency access)
|
||||||
|
2:2345:respawn:/sbin/agetty 38400 tty2 linux
|
||||||
|
3:2345:respawn:/sbin/agetty 38400 tty3 linux
|
||||||
|
4:2345:respawn:/sbin/agetty 38400 tty4 linux
|
||||||
|
|
||||||
|
# --- Special keys -----------------------------------------------------------
|
||||||
|
# Ctrl+Alt+Del triggers a clean reboot
|
||||||
|
ca::ctrlaltdel:/sbin/shutdown -r now
|
||||||
|
|
||||||
|
# Power key triggers a clean shutdown (if ACPI sends it)
|
||||||
|
pf::powerfail:/sbin/shutdown -h +0 "Power failure — shutting down"
|
||||||
69
configs/rc.conf
Normal file
69
configs/rc.conf
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — System Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# /etc/rc.conf — sourced by all rc.d scripts and the init system.
|
||||||
|
# This is the single place to configure hostname, locale, timezone,
|
||||||
|
# network, daemons, and kernel modules.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# --- System identity --------------------------------------------------------
|
||||||
|
HOSTNAME="darkforge"
|
||||||
|
|
||||||
|
# --- Locale and language ----------------------------------------------------
|
||||||
|
LOCALE="en_US.UTF-8"
|
||||||
|
KEYMAP="us"
|
||||||
|
TIMEZONE="America/New_York"
|
||||||
|
# These are set during installation and can be changed here post-install.
|
||||||
|
|
||||||
|
# --- Console font -----------------------------------------------------------
|
||||||
|
FONT="ter-v18n"
|
||||||
|
# Terminus font at 18px — crisp on high-DPI displays. Requires kbd package.
|
||||||
|
# Set to "" to use the kernel default.
|
||||||
|
|
||||||
|
# --- Daemons to start at boot ----------------------------------------------
|
||||||
|
# Order matters. Each name corresponds to a script in /etc/rc.d/
|
||||||
|
# Scripts are started in listed order at boot, stopped in reverse at shutdown.
|
||||||
|
DAEMONS=(
|
||||||
|
eudev # Device manager — must be first for hardware detection
|
||||||
|
syslog # System logging
|
||||||
|
dbus # D-Bus message bus — needed by polkit, PipeWire
|
||||||
|
dhcpcd # DHCP client for ethernet
|
||||||
|
pipewire # Audio server (replaces PulseAudio)
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Kernel modules to load at boot ----------------------------------------
|
||||||
|
# Modules not auto-loaded by eudev that we need explicitly.
|
||||||
|
MODULES=(
|
||||||
|
nvidia
|
||||||
|
nvidia-modeset
|
||||||
|
nvidia-drm
|
||||||
|
nvidia-uvm
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Module parameters ------------------------------------------------------
|
||||||
|
# Pass parameters to kernel modules when loading.
|
||||||
|
# Format: "module_name parameter=value"
|
||||||
|
MODULE_PARAMS=(
|
||||||
|
"nvidia-drm modeset=1"
|
||||||
|
# nvidia-drm modeset=1 — required for Wayland DRM/KMS support
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Network ----------------------------------------------------------------
|
||||||
|
NETWORK_INTERFACE="enp6s0"
|
||||||
|
# The primary ethernet interface. Detected by eudev.
|
||||||
|
# Verify with: ip link show
|
||||||
|
# X870E Hero Realtek 2.5GbE is typically enp6s0 or similar.
|
||||||
|
|
||||||
|
NETWORK_DHCP=yes
|
||||||
|
# Use DHCP for automatic IP configuration.
|
||||||
|
# Set to "no" for static IP and configure NETWORK_IP/MASK/GATEWAY below.
|
||||||
|
|
||||||
|
#NETWORK_IP="192.168.1.100"
|
||||||
|
#NETWORK_MASK="255.255.255.0"
|
||||||
|
#NETWORK_GATEWAY="192.168.1.1"
|
||||||
|
#NETWORK_DNS="1.1.1.1 8.8.8.8"
|
||||||
|
|
||||||
|
# --- Miscellaneous ----------------------------------------------------------
|
||||||
|
HARDWARECLOCK="UTC"
|
||||||
|
# The hardware clock is set to UTC. localtime is computed from TIMEZONE.
|
||||||
35
configs/rc.d/dbus
Executable file
35
configs/rc.d/dbus
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — D-Bus service
|
||||||
|
# ============================================================================
|
||||||
|
# D-Bus message bus — required by polkit, PipeWire, and many desktop apps.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
DAEMON="/usr/bin/dbus-daemon"
|
||||||
|
PIDFILE="/run/dbus/pid"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
echo " Starting dbus..."
|
||||||
|
mkdir -p /run/dbus
|
||||||
|
dbus-uuidgen --ensure
|
||||||
|
${DAEMON} --system && echo " dbus started"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
echo " Stopping dbus..."
|
||||||
|
if [ -f ${PIDFILE} ]; then
|
||||||
|
kill $(cat ${PIDFILE}) 2>/dev/null
|
||||||
|
rm -f ${PIDFILE}
|
||||||
|
fi
|
||||||
|
echo " dbus stopped"
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
$0 stop
|
||||||
|
sleep 1
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
48
configs/rc.d/dhcpcd
Executable file
48
configs/rc.d/dhcpcd
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — dhcpcd service
|
||||||
|
# ============================================================================
|
||||||
|
# DHCP client daemon for ethernet. Uses interface from rc.conf.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
. /etc/rc.conf
|
||||||
|
|
||||||
|
DAEMON="/usr/sbin/dhcpcd"
|
||||||
|
PIDFILE="/run/dhcpcd-${NETWORK_INTERFACE}.pid"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
echo " Starting dhcpcd on ${NETWORK_INTERFACE}..."
|
||||||
|
if [ "${NETWORK_DHCP}" = "yes" ]; then
|
||||||
|
${DAEMON} -q "${NETWORK_INTERFACE}" && echo " dhcpcd started"
|
||||||
|
else
|
||||||
|
# Static IP configuration
|
||||||
|
ip addr add "${NETWORK_IP}/${NETWORK_MASK}" dev "${NETWORK_INTERFACE}"
|
||||||
|
ip link set "${NETWORK_INTERFACE}" up
|
||||||
|
ip route add default via "${NETWORK_GATEWAY}"
|
||||||
|
if [ -n "${NETWORK_DNS}" ]; then
|
||||||
|
echo "# Generated by rc.d/dhcpcd" > /etc/resolv.conf
|
||||||
|
for dns in ${NETWORK_DNS}; do
|
||||||
|
echo "nameserver ${dns}" >> /etc/resolv.conf
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo " Static IP configured: ${NETWORK_IP}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
echo " Stopping dhcpcd..."
|
||||||
|
if [ -f "${PIDFILE}" ]; then
|
||||||
|
${DAEMON} -x "${NETWORK_INTERFACE}" 2>/dev/null
|
||||||
|
fi
|
||||||
|
echo " dhcpcd stopped"
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
$0 stop
|
||||||
|
sleep 2
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
32
configs/rc.d/eudev
Executable file
32
configs/rc.d/eudev
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — eudev service
|
||||||
|
# ============================================================================
|
||||||
|
# Device manager — must be the first daemon started.
|
||||||
|
# Populates /dev with device nodes and triggers udev rules.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
echo " Starting eudev..."
|
||||||
|
/sbin/udevd --daemon
|
||||||
|
udevadm trigger --action=add --type=subsystems
|
||||||
|
udevadm trigger --action=add --type=devices
|
||||||
|
udevadm settle
|
||||||
|
echo " eudev started"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
echo " Stopping eudev..."
|
||||||
|
udevadm control --exit 2>/dev/null
|
||||||
|
echo " eudev stopped"
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
$0 stop
|
||||||
|
sleep 1
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
34
configs/rc.d/pipewire
Executable file
34
configs/rc.d/pipewire
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — PipeWire service
|
||||||
|
# ============================================================================
|
||||||
|
# PipeWire audio server + WirePlumber session manager.
|
||||||
|
# NOTE: PipeWire is designed to run as a user service, not system-wide.
|
||||||
|
# This script starts it for the auto-login user (danny) on tty1.
|
||||||
|
# For the system-level boot, we just ensure the prerequisites are ready.
|
||||||
|
# The actual PipeWire startup is handled in the user's shell profile.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
echo " PipeWire: ready (will start with user session)"
|
||||||
|
# Ensure runtime directory exists for the user
|
||||||
|
mkdir -p /run/user/1000
|
||||||
|
chown danny:danny /run/user/1000
|
||||||
|
chmod 700 /run/user/1000
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
echo " Stopping PipeWire..."
|
||||||
|
killall pipewire wireplumber pipewire-pulse 2>/dev/null
|
||||||
|
echo " PipeWire stopped"
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
$0 stop
|
||||||
|
sleep 1
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
21
configs/rc.d/rc.multi
Executable file
21
configs/rc.d/rc.multi
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — Multi-User Startup
|
||||||
|
# ============================================================================
|
||||||
|
# /etc/rc.d/rc.multi — starts all daemons listed in rc.conf DAEMONS array.
|
||||||
|
# Called by init when entering runlevel 3.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
. /etc/rc.conf
|
||||||
|
|
||||||
|
echo ":: Starting daemons..."
|
||||||
|
|
||||||
|
for daemon in "${DAEMONS[@]}"; do
|
||||||
|
if [ -x "/etc/rc.d/${daemon}" ]; then
|
||||||
|
"/etc/rc.d/${daemon}" start
|
||||||
|
else
|
||||||
|
echo "!! Daemon script not found: /etc/rc.d/${daemon}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ":: All daemons started"
|
||||||
9
configs/rc.d/rc.reboot
Executable file
9
configs/rc.d/rc.reboot
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — System Reboot
|
||||||
|
# ============================================================================
|
||||||
|
# /etc/rc.d/rc.reboot — runs shutdown then reboots.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
/etc/rc.d/rc.shutdown
|
||||||
|
reboot -f
|
||||||
40
configs/rc.d/rc.shutdown
Executable file
40
configs/rc.d/rc.shutdown
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — System Shutdown
|
||||||
|
# ============================================================================
|
||||||
|
# /etc/rc.d/rc.shutdown — stops daemons and cleans up before halt/reboot.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
. /etc/rc.conf
|
||||||
|
|
||||||
|
echo ":: Shutting down..."
|
||||||
|
|
||||||
|
# --- Stop daemons in reverse order ------------------------------------------
|
||||||
|
REVERSED=()
|
||||||
|
for daemon in "${DAEMONS[@]}"; do
|
||||||
|
REVERSED=("${daemon}" "${REVERSED[@]}")
|
||||||
|
done
|
||||||
|
|
||||||
|
for daemon in "${REVERSED[@]}"; do
|
||||||
|
if [ -x "/etc/rc.d/${daemon}" ]; then
|
||||||
|
"/etc/rc.d/${daemon}" stop
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Save random seed -------------------------------------------------------
|
||||||
|
dd if=/dev/urandom of=/var/lib/random-seed count=1 bs=512 2>/dev/null
|
||||||
|
|
||||||
|
# --- Write wtmp shutdown entry ----------------------------------------------
|
||||||
|
halt -w
|
||||||
|
|
||||||
|
# --- Deactivate swap --------------------------------------------------------
|
||||||
|
swapoff -a
|
||||||
|
|
||||||
|
# --- Unmount filesystems ----------------------------------------------------
|
||||||
|
echo ":: Unmounting filesystems..."
|
||||||
|
umount -a -r 2>/dev/null
|
||||||
|
|
||||||
|
# --- Remount root read-only -------------------------------------------------
|
||||||
|
mount -o remount,ro /
|
||||||
|
|
||||||
|
echo ":: Shutdown complete"
|
||||||
104
configs/rc.d/rc.sysinit
Executable file
104
configs/rc.d/rc.sysinit
Executable file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — System Initialization
|
||||||
|
# ============================================================================
|
||||||
|
# /etc/rc.d/rc.sysinit — runs once at boot before daemons start.
|
||||||
|
# Sets up: hostname, clock, filesystems, kernel modules, swap, sysctl.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
. /etc/rc.conf
|
||||||
|
|
||||||
|
echo "DarkForge Linux — booting..."
|
||||||
|
|
||||||
|
# --- Mount virtual filesystems (if not already by kernel) -------------------
|
||||||
|
mountpoint -q /proc || mount -t proc proc /proc
|
||||||
|
mountpoint -q /sys || mount -t sysfs sysfs /sys
|
||||||
|
mountpoint -q /run || mount -t tmpfs tmpfs /run
|
||||||
|
mountpoint -q /dev || mount -t devtmpfs devtmpfs /dev
|
||||||
|
mkdir -p /dev/pts /dev/shm /run/lock
|
||||||
|
mountpoint -q /dev/pts || mount -t devpts devpts /dev/pts
|
||||||
|
mountpoint -q /dev/shm || mount -t tmpfs tmpfs /dev/shm
|
||||||
|
|
||||||
|
# --- Set hostname -----------------------------------------------------------
|
||||||
|
echo "${HOSTNAME}" > /proc/sys/kernel/hostname
|
||||||
|
echo ":: Hostname set to ${HOSTNAME}"
|
||||||
|
|
||||||
|
# --- Set hardware clock -----------------------------------------------------
|
||||||
|
if [ "${HARDWARECLOCK}" = "UTC" ]; then
|
||||||
|
hwclock --systohc --utc 2>/dev/null
|
||||||
|
else
|
||||||
|
hwclock --systohc --localtime 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Set timezone -----------------------------------------------------------
|
||||||
|
if [ -f "/usr/share/zoneinfo/${TIMEZONE}" ]; then
|
||||||
|
ln -sf "/usr/share/zoneinfo/${TIMEZONE}" /etc/localtime
|
||||||
|
echo ":: Timezone set to ${TIMEZONE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Set console keymap -----------------------------------------------------
|
||||||
|
if [ -n "${KEYMAP}" ]; then
|
||||||
|
loadkeys "${KEYMAP}" 2>/dev/null && echo ":: Keymap set to ${KEYMAP}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Set console font -------------------------------------------------------
|
||||||
|
if [ -n "${FONT}" ]; then
|
||||||
|
setfont "${FONT}" 2>/dev/null && echo ":: Console font set to ${FONT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Filesystem check -------------------------------------------------------
|
||||||
|
echo ":: Checking filesystems..."
|
||||||
|
fsck -A -T -C -a
|
||||||
|
if [ $? -gt 1 ]; then
|
||||||
|
echo "!! Filesystem errors detected. Dropping to emergency shell."
|
||||||
|
echo "!! Run 'fsck' manually, then 'exit' to continue boot."
|
||||||
|
/bin/bash
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Mount all filesystems from fstab ---------------------------------------
|
||||||
|
echo ":: Mounting filesystems..."
|
||||||
|
mount -a
|
||||||
|
mount -o remount,rw /
|
||||||
|
|
||||||
|
# --- Activate swap ----------------------------------------------------------
|
||||||
|
echo ":: Activating swap..."
|
||||||
|
swapon -a
|
||||||
|
|
||||||
|
# --- Load kernel modules from rc.conf --------------------------------------
|
||||||
|
echo ":: Loading kernel modules..."
|
||||||
|
for mod in "${MODULES[@]}"; do
|
||||||
|
modprobe "${mod}" && echo " Loaded: ${mod}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Apply module parameters ------------------------------------------------
|
||||||
|
for param in "${MODULE_PARAMS[@]}"; do
|
||||||
|
mod=$(echo "${param}" | awk '{print $1}')
|
||||||
|
args=$(echo "${param}" | cut -d' ' -f2-)
|
||||||
|
# Module params are passed at load time, but if already loaded,
|
||||||
|
# try writing to sysfs
|
||||||
|
if [ -d "/sys/module/${mod}/parameters" ]; then
|
||||||
|
key=$(echo "${args}" | cut -d'=' -f1)
|
||||||
|
val=$(echo "${args}" | cut -d'=' -f2)
|
||||||
|
echo "${val}" > "/sys/module/${mod}/parameters/${key}" 2>/dev/null
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Apply sysctl settings --------------------------------------------------
|
||||||
|
if [ -f /etc/sysctl.conf ]; then
|
||||||
|
sysctl -p /etc/sysctl.conf >/dev/null 2>&1
|
||||||
|
echo ":: Applied sysctl settings"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Set up /tmp ------------------------------------------------------------
|
||||||
|
chmod 1777 /tmp
|
||||||
|
|
||||||
|
# --- Seed random number generator ------------------------------------------
|
||||||
|
if [ -f /var/lib/random-seed ]; then
|
||||||
|
cat /var/lib/random-seed > /dev/urandom
|
||||||
|
fi
|
||||||
|
dd if=/dev/urandom of=/var/lib/random-seed count=1 bs=512 2>/dev/null
|
||||||
|
|
||||||
|
# --- Clear old PID files and locks ------------------------------------------
|
||||||
|
rm -f /run/*.pid /var/lock/* 2>/dev/null
|
||||||
|
|
||||||
|
echo ":: System initialization complete"
|
||||||
32
configs/rc.d/syslog
Executable file
32
configs/rc.d/syslog
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — syslog service
|
||||||
|
# ============================================================================
|
||||||
|
# System logging daemon. Uses sysklogd or syslog-ng.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
DAEMON="/usr/sbin/syslogd"
|
||||||
|
PIDFILE="/run/syslogd.pid"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
echo " Starting syslog..."
|
||||||
|
${DAEMON} -m 0 && echo " syslog started"
|
||||||
|
/usr/sbin/klogd && echo " klogd started"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
echo " Stopping syslog..."
|
||||||
|
killall syslogd klogd 2>/dev/null
|
||||||
|
rm -f ${PIDFILE}
|
||||||
|
echo " syslog stopped"
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
$0 stop
|
||||||
|
sleep 1
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
48
configs/zprofile
Normal file
48
configs/zprofile
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — User Shell Profile (~/.zprofile)
|
||||||
|
# ============================================================================
|
||||||
|
# Sourced on login to zsh. Auto-starts PipeWire and dwl on tty1.
|
||||||
|
# This file is installed to /home/danny/.zprofile during system installation.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# --- Environment variables for Wayland + NVIDIA ----------------------------
|
||||||
|
export XDG_SESSION_TYPE=wayland
|
||||||
|
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
|
||||||
|
export XDG_CONFIG_HOME="${HOME}/.config"
|
||||||
|
export XDG_CACHE_HOME="${HOME}/.cache"
|
||||||
|
export XDG_DATA_HOME="${HOME}/.local/share"
|
||||||
|
export XDG_STATE_HOME="${HOME}/.local/state"
|
||||||
|
|
||||||
|
# NVIDIA Wayland-specific environment
|
||||||
|
export GBM_BACKEND=nvidia-drm
|
||||||
|
export __GLX_VENDOR_LIBRARY_NAME=nvidia
|
||||||
|
export WLR_NO_HARDWARE_CURSORS=1
|
||||||
|
# WLR_NO_HARDWARE_CURSORS may be needed for wlroots + nvidia
|
||||||
|
# Remove if hardware cursors work correctly
|
||||||
|
|
||||||
|
export MOZ_ENABLE_WAYLAND=1
|
||||||
|
# Firefox: use Wayland backend
|
||||||
|
|
||||||
|
export QT_QPA_PLATFORM=wayland
|
||||||
|
# Qt applications: use Wayland backend
|
||||||
|
|
||||||
|
export SDL_VIDEODRIVER=wayland
|
||||||
|
# SDL2 games: prefer Wayland (falls back to X11 via XWayland)
|
||||||
|
|
||||||
|
# --- Ensure XDG runtime directory exists ------------------------------------
|
||||||
|
if [ ! -d "${XDG_RUNTIME_DIR}" ]; then
|
||||||
|
mkdir -p "${XDG_RUNTIME_DIR}"
|
||||||
|
chmod 700 "${XDG_RUNTIME_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Auto-start Wayland compositor on tty1 ----------------------------------
|
||||||
|
if [ -z "${WAYLAND_DISPLAY}" ] && [ "$(tty)" = "/dev/tty1" ]; then
|
||||||
|
# Start PipeWire audio stack (runs as user, not system service)
|
||||||
|
pipewire &
|
||||||
|
pipewire-pulse &
|
||||||
|
wireplumber &
|
||||||
|
|
||||||
|
# Start the dwl Wayland compositor
|
||||||
|
# dwl will set WAYLAND_DISPLAY and become the session leader
|
||||||
|
exec dwl -s "foot" 2>/dev/null
|
||||||
|
fi
|
||||||
661
docs/CHANGELOG.md
Normal file
661
docs/CHANGELOG.md
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
# DarkForge Linux — Changelog
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V25 2026-03-19 13:20:00
|
||||||
|
|
||||||
|
**Initialize git repository with documentation and remotes**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `.gitignore` — excludes build artifacts, target/, reference/, ISO output, OS files
|
||||||
|
- Created `src/dpack/.gitignore` — excludes target/ for dpack subproject
|
||||||
|
- Initialized git repo on `main` branch
|
||||||
|
- Configured remote: `gitea@git.dannyhaslund.dk:danny8632/darkforge.git`
|
||||||
|
- 205 files staged for initial commit
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- Single monorepo for now (CLAUDE.md mentions dpack eventually becoming its own repo)
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Initial commit not yet pushed (user needs to push from their machine)
|
||||||
|
- dpack submodule extraction deferred to later
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V24 2026-03-19 13:10:00
|
||||||
|
|
||||||
|
**Write README.md files and Phase 12 test checklist**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `README.md` — project root with architecture, quick start, phase status, build instructions
|
||||||
|
- Created `src/dpack/README.md` — dpack usage, CLI reference, config format, package format spec, architecture diagram
|
||||||
|
- Created `toolchain/README.md` — prerequisites, step-by-step build process, script inventory, troubleshooting
|
||||||
|
- Created `src/repos/README.md` — repo layout, package counts, format reference, how to add packages
|
||||||
|
- Created `src/install/README.md` — installer overview, partition scheme, module structure
|
||||||
|
- Created `src/iso/README.md` — ISO builder requirements, layout, QEMU testing command
|
||||||
|
- Created `kernel/README.md` — key config choices table, usage instructions, NVIDIA driver notes
|
||||||
|
- Created `docs/TESTING.md` — Phase 12 integration test checklist covering all 12 phases with specific pass/fail criteria
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- None
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V23 2026-03-19 13:00:00
|
||||||
|
|
||||||
|
**Implement interactive installer (Phase 11)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `src/install/install.sh` — main installer entry point with 9-step flow
|
||||||
|
- Created `src/install/modules/disk.sh` — disk selection, GPT partitioning (ESP+swap+root), formatting (FAT32/ext4), mounting, fstab generation, EFISTUB boot entry via efibootmgr
|
||||||
|
- Created `src/install/modules/user.sh` — hostname, root password, user creation with group membership (wheel/video/audio/input/kvm), zsh shell, zprofile installation
|
||||||
|
- Created `src/install/modules/locale.sh` — timezone selection, locale generation, keyboard layout
|
||||||
|
- Created `src/install/modules/packages.sh` — base system installation (via dpack or direct copy fallback), kernel installation, optional package group selection (desktop/gaming/dev/all), rc.conf generation with install-time values
|
||||||
|
- All installer scripts made executable
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Manual partitioning option not implemented (auto-partition only)
|
||||||
|
- No dialog/whiptail TUI — uses plain shell prompts (simpler, fewer deps)
|
||||||
|
- Installer not tested end-to-end (requires live ISO environment)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V22 2026-03-19 12:55:00
|
||||||
|
|
||||||
|
**Implement ISO builder (Phase 10)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `src/iso/build-iso.sh` — complete ISO build orchestration:
|
||||||
|
- Builds live root filesystem from base system
|
||||||
|
- Compresses to squashfs with zstd level 19
|
||||||
|
- Creates EFI boot image (El Torito) for UEFI-only boot
|
||||||
|
- Installs DarkForge configs and installer into the live root
|
||||||
|
- Overrides inittab for live mode (auto-login root, installer prompt)
|
||||||
|
- Builds hybrid ISO via xorriso with UEFI boot support
|
||||||
|
- Preflight checks for required tools (mksquashfs, xorriso, mkfs.fat, mcopy)
|
||||||
|
- Script made executable
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Requires a completed base system in build/base-system/ to create a functional ISO
|
||||||
|
- Kernel must be pre-built and placed at kernel/vmlinuz
|
||||||
|
- No legacy BIOS boot support (UEFI only, as specified)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V21 2026-03-19 12:50:00
|
||||||
|
|
||||||
|
**Write application packages and Rust toolchain definition (Phase 9)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created application packages: wezterm-20240203, freecad-1.0.0, amd-microcode, rust-1.86.0
|
||||||
|
- Rust package enables building wezterm and other Rust-based tools on the target system
|
||||||
|
- AMD microcode package creates early-load initramfs image for CPU microcode updates
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- WezTerm build requires Rust — circular dependency if Rust isn't bootstrapped first
|
||||||
|
- FreeCAD has many deps (opencascade, boost, xerces-c) not yet packaged
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V20 2026-03-19 12:45:00
|
||||||
|
|
||||||
|
**Write gaming stack package definitions (Phase 8)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created 10 gaming packages in `src/repos/gaming/`:
|
||||||
|
- steam-1.0.0.82, wine-10.11, dxvk-2.5.3, vkd3d-proton-2.14.1
|
||||||
|
- proton-ge-9-27, protontricks-1.12.0, winetricks-20250110
|
||||||
|
- prismlauncher-9.2, openjdk-21.0.6, sdl2-2.32.4
|
||||||
|
- Created 6 supporting packages in `src/repos/extra/`:
|
||||||
|
- gnutls-3.8.9, nettle-3.10.1, libtasn1-4.19.0, p11-kit-0.25.5
|
||||||
|
- qt6-base-6.8.3, lxqt-policykit-2.1.0
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- 32-bit multilib builds for Wine/Steam not yet addressed
|
||||||
|
- Steam native runtime may need additional 32-bit deps
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V19 2026-03-19 12:40:00
|
||||||
|
|
||||||
|
**Write NVIDIA driver package definition (Phase 7)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `src/repos/extra/nvidia-open/nvidia-open.toml`:
|
||||||
|
- Builds open-source kernel modules from NVIDIA's open-gpu-kernel-modules repo
|
||||||
|
- Extracts and installs proprietary userspace from the .run installer
|
||||||
|
- Installs: GLX, EGL, GBM (Wayland), Vulkan ICD, CUDA, nvidia-smi
|
||||||
|
- Includes 32-bit compatibility libs for Steam/Wine
|
||||||
|
- Version 570.133.07 (minimum for RTX 5090)
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- Using nvidia-open (MIT/GPL-2.0) instead of the fully proprietary blob
|
||||||
|
as RTX 5090 Blackwell requires the open modules
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Exact library list may need tuning for specific driver version
|
||||||
|
- DKMS support not implemented (manual kernel module rebuild required on kernel update)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V18 2026-03-19 12:35:00
|
||||||
|
|
||||||
|
**Fix all remaining dpack compilation warnings**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Added `#![allow(dead_code)]` to `src/dpack/src/main.rs` and `src/dpack/src/lib.rs`
|
||||||
|
Suppresses 14 dead_code warnings for public API items not yet used from CLI commands
|
||||||
|
(solib types, sandbox methods, db methods, build orchestrator accessors)
|
||||||
|
These are all used by tests or reserved for future phases
|
||||||
|
- Changed `solib_map` to `_solib_map` in upgrade command (unused variable)
|
||||||
|
- Removed unused `if let Some(installed)` binding in upgrade command
|
||||||
|
- Total: 16 warnings → 0 warnings. `cargo build --release` is clean.
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- None — all warnings resolved
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V17 2026-03-19 12:30:00
|
||||||
|
|
||||||
|
**Write extra/, desktop/, and gaming/ package definitions (Phase 6)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created 18 packages in `src/repos/extra/`:
|
||||||
|
- PipeWire 1.4.3, WirePlumber 0.5.8, Mesa 25.3.3 (Vulkan/OpenGL)
|
||||||
|
- Vulkan stack: vulkan-headers/loader/tools 1.4.320
|
||||||
|
- libdrm 2.4.124, polkit 125, duktape 2.7.0, seatd 0.9.1, lua 5.4.7
|
||||||
|
- Font stack: fontconfig 2.16.0, freetype 2.13.3, harfbuzz 10.4.0, libpng 1.6.47
|
||||||
|
- Layout: pango 1.56.3, cairo 1.18.4, pixman 0.44.2
|
||||||
|
- Created 17 packages in `src/repos/desktop/`:
|
||||||
|
- Wayland stack: wayland 1.23.1, wayland-protocols 1.41, wlroots 0.18.2
|
||||||
|
- Compositor: dwl 0.7 (dynamic window manager for Wayland)
|
||||||
|
- XWayland 24.1.6, libinput 1.28.1, libevdev 1.13.3, mtdev 1.1.7
|
||||||
|
- Keyboard: libxkbcommon 1.7.0, xkeyboard-config 2.43
|
||||||
|
- Apps: foot 1.21.1 (terminal), fuzzel 1.12.0 (launcher), firefox 137.0, zsh 5.9.1
|
||||||
|
- Utilities: wl-clipboard 2.2.1, grim 1.4.1, slurp 1.5.0
|
||||||
|
- Created 2 packages in `src/repos/gaming/`:
|
||||||
|
- gamemode 1.8.2, mangohud 0.7.3
|
||||||
|
- Total across all repos: 103 packages
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Steam, Wine, Proton, PrismLauncher not yet defined (Phase 8 gaming stack)
|
||||||
|
- NVIDIA driver package not yet defined (Phase 7)
|
||||||
|
- Firefox build is extremely complex — may need dedicated build script
|
||||||
|
- Some extra/desktop deps reference packages not yet in any repo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V16 2026-03-19 12:20:00
|
||||||
|
|
||||||
|
**Implement init system and service scripts (Phase 5)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `configs/rc.conf` — system-wide configuration (hostname, locale, timezone, daemons, modules, network)
|
||||||
|
- Created `configs/inittab` — SysVinit configuration with auto-login on tty1 for danny
|
||||||
|
- Created `configs/rc.d/rc.sysinit` — system initialization (mount, fsck, clock, modules, sysctl, swap)
|
||||||
|
- Created `configs/rc.d/rc.multi` — daemon startup (iterates DAEMONS array)
|
||||||
|
- Created `configs/rc.d/rc.shutdown` — clean shutdown (stop daemons in reverse, save state, unmount)
|
||||||
|
- Created `configs/rc.d/rc.reboot` — reboot wrapper
|
||||||
|
- Created daemon scripts: `configs/rc.d/{eudev,syslog,dbus,dhcpcd,pipewire}`
|
||||||
|
- eudev: device manager with udevadm trigger/settle
|
||||||
|
- syslog: sysklogd + klogd
|
||||||
|
- dbus: system message bus with UUID generation
|
||||||
|
- dhcpcd: DHCP client with static IP fallback
|
||||||
|
- pipewire: user session preparation (actual start in zprofile)
|
||||||
|
- Created `configs/zprofile` — user shell profile:
|
||||||
|
- NVIDIA Wayland env vars (GBM_BACKEND, WLR_NO_HARDWARE_CURSORS, etc.)
|
||||||
|
- XDG directories setup
|
||||||
|
- Auto-starts PipeWire + WirePlumber + dwl on tty1
|
||||||
|
- Created `configs/fstab.template` — partition table template with UUID placeholders
|
||||||
|
- All rc.d scripts made executable
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- PipeWire runs as user session (via zprofile) rather than system daemon
|
||||||
|
This matches PipeWire's design intent without systemd user sessions
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- seatd daemon script not written (needed for wlroots/dwl seat management)
|
||||||
|
- Network interface name (enp6s0) is a guess — verify on actual hardware
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V15 2026-03-19 12:10:00
|
||||||
|
|
||||||
|
**Write hardware-specific kernel configuration (Phase 4)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `kernel/config` — comprehensive Linux 6.19.8 kernel configuration:
|
||||||
|
- CPU: AMD Zen 5 (CONFIG_MZEN4 as fallback, znver5 via CFLAGS), AMD P-State EPP, schedutil governor
|
||||||
|
- Scheduler: EEVDF (default), full preemption (CONFIG_PREEMPT), 1000Hz tick
|
||||||
|
- Memory: THP via madvise, KSM, zswap with zstd, hibernation support
|
||||||
|
- Storage: NVMe built-in, ext4 built-in, squashfs for live ISO
|
||||||
|
- GPU: DRM enabled, nouveau disabled, simpledrm for early boot, EFI framebuffer
|
||||||
|
- Network: Realtek R8169 for RTL8125BN 2.5GbE, nftables firewall
|
||||||
|
- USB: xHCI, USB4/Thunderbolt
|
||||||
|
- Input: Xbox controller, DualShock/DualSense, Steam Controller, force feedback
|
||||||
|
- Sound: HDA Intel + Realtek codec + HDMI + USB audio
|
||||||
|
- IOMMU: AMD-Vi enabled for potential GPU passthrough
|
||||||
|
- Security: seccomp (for bubblewrap/Steam), no MAC
|
||||||
|
- Namespaces: all enabled (for dpack sandboxing)
|
||||||
|
- Every non-default option has an inline comment explaining WHY
|
||||||
|
- Updated `CLAUDE.md` — corrected hardware errors discovered during research:
|
||||||
|
- Network: Realtek RTL8125BN (NOT Intel I226-V) — CONFIG_R8169 replaces CONFIG_IGB
|
||||||
|
- Added CONFIG_USB4 for USB4 support
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- Using CONFIG_MZEN4 instead of CONFIG_MZEN5 — znver5 kernel config symbol may not exist in 6.19
|
||||||
|
Actual Zen 5 optimization comes from GCC -march=znver5 in CFLAGS
|
||||||
|
- Network controller corrected: Realtek RTL8125BN, not Intel I226-V
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Full .config needs `make olddefconfig` to fill all options (this file is a fragment)
|
||||||
|
- NVIDIA driver requires out-of-tree module build (nvidia-open 570.86.16+)
|
||||||
|
- BORE scheduler patch not included (can be added later if benchmarks warrant it)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V14 2026-03-19 12:05:00
|
||||||
|
|
||||||
|
**Fix dpack compilation errors and warnings**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Fixed lifetime errors in `src/dpack/src/config/global.rs`:
|
||||||
|
- Added explicit lifetime parameters to `effective_cflags()` and `effective_ldflags()`
|
||||||
|
- Both now use `<'a>` to tie input and output lifetimes correctly
|
||||||
|
- Fixed warnings in `src/dpack/src/converter/crux.rs`:
|
||||||
|
- Removed unused `Context` import
|
||||||
|
- Renamed `maintainer` to `_maintainer` (assigned but unused — kept for future use)
|
||||||
|
- Changed `source_urls` to `let` binding (overwritten immediately after init)
|
||||||
|
- Fixed warnings in `src/dpack/src/converter/gentoo.rs`:
|
||||||
|
- Removed unused `Context` import
|
||||||
|
- Renamed `build_system` param to `_build_system` (reserved for future use)
|
||||||
|
- All 2 errors and 6 warnings resolved — `cargo build` should now compile cleanly
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Full `cargo test` run needed on host machine to verify all 20+ unit tests pass
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V13 2026-03-19 12:00:00
|
||||||
|
|
||||||
|
**Create complete base system package repository (Phase 3)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created 66 dpack `.toml` package definitions in `src/repos/core/`:
|
||||||
|
- **Toolchain (7):** gcc-15.2.0, glibc-2.43, binutils-2.46, gmp-6.3.0, mpfr-4.2.2, mpc-1.3.1, linux-6.19.8
|
||||||
|
- **Utilities (17):** coreutils-9.6, util-linux-2.42, bash-5.3, ncurses-6.5, readline-8.3, sed-4.9, grep-3.14, gawk-5.4.0, findutils-4.10.0, diffutils-3.10, tar-1.35, gzip-1.14, xz-5.8.1, zstd-1.5.7, bzip2-1.0.8, file-5.47, less-692
|
||||||
|
- **System (11):** eudev-3.2.14, sysvinit-3.15, dbus-1.16.2, dhcpcd-10.3.0, shadow-4.14, procps-ng-4.0.6, e2fsprogs-1.47.4, kmod-34.2, iproute2-6.19.0, kbd-2.6.4, bc-7.0.3
|
||||||
|
- **Dev tools (14):** cmake-4.2.3, meson-1.10.2, ninja-1.13.0, python-3.13.3, perl-5.40.2, autoconf-2.72, automake-1.18, libtool-2.5.4, bison-3.8.2, flex-2.6.4, gettext-0.23.1, texinfo-7.3, m4-1.4.20, make-4.4.1, patch-2.8, pkg-config-1.8.0, gperf-3.1
|
||||||
|
- **Libraries (12):** openssl-3.6.1, curl-8.19.0, git-2.53.0, zlib-1.3.1, expat-2.7.4, libffi-3.5.2, libxml2-2.15.2, pcre2-10.45, glib-2.84.1, libmnl-1.0.5, libpipeline-1.5.8
|
||||||
|
- **Docs (3):** groff-1.24.1, man-db-2.13.1, man-pages-6.16
|
||||||
|
- All 66 packages have their dependencies fully resolvable within core/
|
||||||
|
- Researched latest stable versions for all packages (March 2026)
|
||||||
|
- SHA256 checksums are placeholders (will be populated when downloading sources)
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- Added 6 packages not in original CLAUDE.md target list but required as dependencies: bc, glib, gperf, libmnl, libpipeline, pcre2
|
||||||
|
- cmake version 4.2.3 (major version bump from 3.x to 4.x happened in 2026)
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- SHA256 checksums are all placeholders (need real downloads to compute)
|
||||||
|
- Some configure commands may need tuning during actual builds
|
||||||
|
- multilib (32-bit) variants not yet defined (needed for Phase 8: Gaming)
|
||||||
|
- zsh package not yet in core/ (will add for user shell in Phase 5)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V12 2026-03-19 11:30:00
|
||||||
|
|
||||||
|
**Wire all Phase 2 features into CLI and fix compilation**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Updated `src/dpack/src/main.rs`:
|
||||||
|
- `convert` command now calls converter module (auto-detects Pkgfile vs .ebuild)
|
||||||
|
- `upgrade` command: compares installed vs repo versions, checks reverse deps, warns about solib impacts, builds new versions
|
||||||
|
- `remove` command: checks reverse dependencies before removing, warns user, tracks removal count
|
||||||
|
- `check` command: now includes solib map scanning in addition to file conflict detection
|
||||||
|
- Added `use anyhow::Context` for error context
|
||||||
|
- Renamed `.dpack` → `.toml` references in CLAUDE.md (5 occurrences)
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Upgrade doesn't unregister old version before installing new (relies on overwrite)
|
||||||
|
- No interactive confirmation prompts yet
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V11 2026-03-19 11:20:00
|
||||||
|
|
||||||
|
**Implement shared library conflict detection (Phase 2c)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `src/dpack/src/resolver/solib.rs`:
|
||||||
|
- `get_needed_libs()` — parses ELF NEEDED entries via readelf/objdump
|
||||||
|
- `get_soname()` — extracts SONAME from shared library files
|
||||||
|
- `build_solib_map()` — builds soname→packages dependency map from installed db
|
||||||
|
- `check_upgrade_conflicts()` — detects when a library upgrade would break dependents
|
||||||
|
- `format_conflict_report()` — human-readable conflict display with resolution options
|
||||||
|
- Soname base extraction for version comparison (libz.so.1 → libz.so)
|
||||||
|
- 4 unit tests
|
||||||
|
- Updated `src/dpack/src/resolver/mod.rs` — added `pub mod solib;`
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Conflict resolution is informational only (no automated static recompilation)
|
||||||
|
- Needs real ELF binaries to test solib scanning
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V10 2026-03-19 11:10:00
|
||||||
|
|
||||||
|
**Implement Gentoo ebuild converter (Phase 2b)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `src/dpack/src/converter/gentoo.rs` (570 lines):
|
||||||
|
- Parses ebuild filename for name/version (`curl-8.19.0.ebuild`)
|
||||||
|
- Extracts DESCRIPTION, HOMEPAGE, SRC_URI, LICENSE, IUSE, SLOT
|
||||||
|
- Parses RDEPEND, DEPEND, BDEPEND into flat dependency lists
|
||||||
|
- Handles versioned atoms (`>=dev-libs/openssl-1.0.2`), slot deps (`:=`), conditional deps
|
||||||
|
- Converts IUSE USE flags to dpack optional dependencies (filters internal flags)
|
||||||
|
- Extracts phase functions (src_configure, src_compile, src_install, src_prepare, src_test)
|
||||||
|
- Converts Gentoo helpers to plain shell: econf→./configure, emake→make, ${ED}→${PKG}
|
||||||
|
- Handles mirror:// URL expansion (sourceforge, gnu, gentoo)
|
||||||
|
- Detects eclasses requiring manual review: multilib-minimal, cargo, git-r3
|
||||||
|
- Generates ConversionWarnings for REQUIRED_USE, multilib deps, complex dep logic
|
||||||
|
- 5 unit tests (filename parsing, simple ebuild, multiline vars, dep atoms, USE flags, phase conversion)
|
||||||
|
- Studied real Gentoo ebuilds from reference/gentoo/ (zlib, curl, openssl, mesa)
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- Ebuild converter is best-effort (per CLAUDE.md §dpack): handles ~80% of cases, flags rest for manual review
|
||||||
|
- Complex eclasses (multilib-minimal, llvm-r1) not fully supported — generates warnings
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- No eclass expansion (would need to ship eclass definitions)
|
||||||
|
- Slot dependency semantics not preserved in dpack format
|
||||||
|
- REQUIRED_USE validation not enforced at install time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V9 2026-03-19 11:00:00
|
||||||
|
|
||||||
|
**Implement CRUX Pkgfile converter (Phase 2a)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `src/dpack/src/converter/crux.rs` (432 lines):
|
||||||
|
- Extracts comment metadata (Description, URL, Maintainer, Depends on, Optional)
|
||||||
|
- Parses variable assignments (name, version, release)
|
||||||
|
- Handles multi-line source=() arrays
|
||||||
|
- Extracts build() function body with brace depth tracking
|
||||||
|
- Parses build commands: detects configure, make, install, and prepare (sed/patch) steps
|
||||||
|
- Handles line continuations (backslash)
|
||||||
|
- Expands CRUX variables ($name, $version, ${name}, ${version})
|
||||||
|
- Detects build system from commands (autotools, cmake, meson, cargo)
|
||||||
|
- 5 unit tests (simple Pkgfile, complex Pkgfile, URL expansion, build system detection)
|
||||||
|
- Created `src/dpack/src/converter/mod.rs` — format auto-detection (Pkgfile vs .ebuild)
|
||||||
|
- Studied real CRUX ports from reference/crux_ports/ (zlib, curl, openssl, mesa)
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- SHA256 checksums are placeholder "FIXME" (would need actual download to compute)
|
||||||
|
- Cannot parse arbitrary bash logic in build() (just extracts common patterns)
|
||||||
|
- License field not available from CRUX Pkgfiles
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V8 2026-03-19 10:50:00
|
||||||
|
|
||||||
|
**Implement complete dpack build orchestration pipeline (Phase 1e)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Implemented `src/dpack/src/build/mod.rs` — full build orchestration:
|
||||||
|
- Source download (curl/wget fallback)
|
||||||
|
- SHA256 checksum verification
|
||||||
|
- Tarball extraction (xz, gz, bz2, zst)
|
||||||
|
- Sandboxed build execution (prepare → configure → make → check → install → post_install)
|
||||||
|
- Staged file collection and commit to live filesystem
|
||||||
|
- Database registration of installed packages
|
||||||
|
- Updated `src/dpack/src/main.rs` — wired all commands to real implementations:
|
||||||
|
- `install`: full pipeline via BuildOrchestrator
|
||||||
|
- `remove`: unregisters from db, deletes installed files
|
||||||
|
- `search`: searches all repos by name/description
|
||||||
|
- `info`: shows installed or repo package details
|
||||||
|
- `list`: shows all installed packages with sizes
|
||||||
|
- `check`: reports file conflicts between packages
|
||||||
|
- Fixed broken format string in commit_staged_files
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- Using curl/wget subprocess for downloads instead of reqwest — simpler for bootstrap
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- `upgrade` and `convert` commands remain stubs (Phase 2)
|
||||||
|
- No interactive confirmation before installing
|
||||||
|
- Repo tracking in db records is hardcoded to "core"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V7 2026-03-19 10:45:00
|
||||||
|
|
||||||
|
**Implement installed-package database (Phase 1d)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Implemented `src/dpack/src/db/mod.rs` — file-based TOML package database:
|
||||||
|
- register/unregister packages
|
||||||
|
- persistence to `/var/lib/dpack/db/`
|
||||||
|
- who_owns file lookup
|
||||||
|
- file conflict detection
|
||||||
|
- total size tracking
|
||||||
|
- Comprehensive test suite: register, unregister, persistence, file ownership, conflicts, sorted listing
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- No file locking for concurrent access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V6 2026-03-19 10:40:00
|
||||||
|
|
||||||
|
**Implement build sandbox with bubblewrap backend (Phase 1c)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Implemented `src/dpack/src/sandbox/mod.rs`:
|
||||||
|
- Two backends: Bubblewrap (isolated) and Direct (fallback)
|
||||||
|
- PID namespace isolation, optional network blocking
|
||||||
|
- Read-only bind mounts for dependencies
|
||||||
|
- Environment variable injection (CFLAGS, LDFLAGS, MAKEFLAGS, PKG)
|
||||||
|
- Full build sequence execution (prepare → configure → make → check → install → post_install)
|
||||||
|
- Staged file collection utility
|
||||||
|
- Auto-fallback to Direct when bwrap not available
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- bubblewrap backend untested (requires bwrap binary)
|
||||||
|
- No overlay filesystem support yet
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V5 2026-03-19 10:35:00
|
||||||
|
|
||||||
|
**Implement package definition parser and dependency resolver (Phase 1a + 1b)**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Renamed all `.dpack` references to `.toml` in CLAUDE.md and project files
|
||||||
|
- Implemented `src/dpack/src/config/package.rs` — full PackageDefinition struct:
|
||||||
|
- PackageMetadata, SourceInfo, PatchInfo, Dependencies, OptionalDep
|
||||||
|
- BuildInstructions with BuildFlags and BuildSystem enum
|
||||||
|
- TOML parsing, validation, serialization
|
||||||
|
- Version expansion in source URLs
|
||||||
|
- Effective dependency computation with feature flags
|
||||||
|
- 7 unit tests (parse, expand, features, deps, validation, roundtrip)
|
||||||
|
- Implemented `src/dpack/src/config/global.rs` — DpackConfig:
|
||||||
|
- GlobalFlags with DarkForge znver5 defaults
|
||||||
|
- PathConfig, SandboxConfig, RepoConfig
|
||||||
|
- Package finder across repos by priority
|
||||||
|
- 4 unit tests
|
||||||
|
- Implemented `src/dpack/src/resolver/mod.rs` — dependency resolver:
|
||||||
|
- DependencyGraph with topological sort via DFS
|
||||||
|
- Circular dependency detection
|
||||||
|
- Already-installed package skipping
|
||||||
|
- Feature-aware dependency expansion
|
||||||
|
- Reverse dependency lookup
|
||||||
|
- 5 unit tests (simple, circular, installed, missing, diamond)
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- Package definitions use `.toml` extension instead of `.dpack` (user requested)
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Version constraint parsing not yet implemented (basic string equality only)
|
||||||
|
- No version comparison logic (newer/older detection)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V4 2026-03-19 10:25:00
|
||||||
|
|
||||||
|
**Scaffold dpack Rust project with CLI structure and module stubs**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `src/dpack/Cargo.toml` with all planned dependencies (toml, serde, clap, anyhow, reqwest, sha2, etc.)
|
||||||
|
- Created `src/dpack/src/main.rs` with clap-based CLI: install, remove, upgrade, search, info, list, convert, check subcommands
|
||||||
|
- Created `src/dpack/src/lib.rs` re-exporting all modules
|
||||||
|
- Created module stubs: `config/mod.rs`, `resolver/mod.rs`, `sandbox/mod.rs`, `converter/mod.rs`, `db/mod.rs`, `build/mod.rs`
|
||||||
|
- Created first sample package definition: `src/repos/core/zlib/zlib.dpack`
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- All module implementations are stubs (Phase 1 work)
|
||||||
|
- Cargo.toml dependencies may need version tuning when building on the target system
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V3 2026-03-19 10:20:00
|
||||||
|
|
||||||
|
**Write complete Phase 0 chroot setup and temporary tool build scripts**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `toolchain/scripts/023-chroot-setup.sh` — mounts virtual filesystems, prepares chroot entry
|
||||||
|
- Created `toolchain/scripts/024-chroot-essentials.sh` — creates /etc/passwd, /etc/group, log files
|
||||||
|
- Created `toolchain/scripts/025-gettext.sh` through `toolchain/scripts/030-util-linux.sh` — chroot package builds
|
||||||
|
- Created `toolchain/scripts/031-cleanup.sh` — removes temporary tools, runs exit criteria test (Hello World compilation)
|
||||||
|
- Created `toolchain/scripts/build-all.sh` — master build runner with logging, color output, phase selection
|
||||||
|
- All scripts made executable
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- None
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Scripts are untested against real hardware (need actual LFS partition)
|
||||||
|
- Some package versions deviate from LFS 13.0 where newer stable releases exist (documented in VERSION_MANIFEST.md)
|
||||||
|
- glibc-fhs-1.patch needs to be verified for glibc-2.43 compatibility
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V2 2026-03-19 10:15:00
|
||||||
|
|
||||||
|
**Resolve all open questions and build Phase 0 cross-toolchain scripts**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Updated `CLAUDE.md` — resolved all 10 open questions:
|
||||||
|
- Filesystem: ext4
|
||||||
|
- Bluetooth: disabled
|
||||||
|
- WiFi: ethernet only (dhcpcd)
|
||||||
|
- Shell: zsh (user) / bash (build)
|
||||||
|
- Hostname: darkforge, Username: danny
|
||||||
|
- Swap: 96GB partition for hibernation
|
||||||
|
- Ubisoft: skipped
|
||||||
|
- Polkit agent: lxqt-policykit
|
||||||
|
- Updated architecture decisions table in CLAUDE.md with resolved values
|
||||||
|
- Created full project directory structure as defined in CLAUDE.md
|
||||||
|
- Created `toolchain/VERSION_MANIFEST.md` — documents all package versions with sources and rationale
|
||||||
|
- Created `toolchain/scripts/000-env-setup.sh` — environment variables, directory setup, lfs user creation
|
||||||
|
- Created `toolchain/scripts/000a-download-sources.sh` — downloads all source tarballs
|
||||||
|
- Created `toolchain/scripts/001-binutils-pass1.sh` through `005-libstdcxx.sh` — Chapter 5 cross-toolchain
|
||||||
|
- Created `toolchain/scripts/006-m4.sh` through `022-gcc-pass2.sh` — Chapter 6 temporary tools
|
||||||
|
- Researched and confirmed GCC 15.2.0 supports `-march=znver5` (since GCC 14.1)
|
||||||
|
- Used custom target triplet: `x86_64-darkforge-linux-gnu`
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- Using `x86_64-darkforge-linux-gnu` as target triplet instead of LFS default `x86_64-lfs-linux-gnu`
|
||||||
|
- Some package versions are newer than LFS 13.0 defaults (per CLAUDE.md rule §3 "Latest Versions Always"):
|
||||||
|
- m4: 1.4.20 (LFS uses 1.4.19)
|
||||||
|
- gzip: 1.14 (LFS uses 1.13)
|
||||||
|
- patch: 2.8 (LFS uses 2.7.6)
|
||||||
|
- xz: 5.8.1 (LFS uses 5.6.1)
|
||||||
|
- 96GB swap partition added to partition scheme (for hibernation support)
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Scripts not yet tested on real hardware
|
||||||
|
- SHA256 checksums needed for all source tarballs (only zlib has one in the sample .dpack)
|
||||||
|
- Multilib support not yet addressed (needed for Steam/Wine in later phases)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## V1 2026-03-18 00:00:00
|
||||||
|
|
||||||
|
**Create CLAUDE.md project directive from initial requirements**
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Created `CLAUDE.md` as the single source of truth for all AI-assisted project work
|
||||||
|
- Defined 12 sequential phases covering the full scope: toolchain bootstrap → dpack core → dpack advanced → base system → kernel → init → desktop → nvidia → gaming → apps → ISO → installer → integration testing
|
||||||
|
- Documented hardware target (Ryzen 9 9950X3D, RTX 5090, X870E Hero, DDR5-6000 96GB, Samsung 9100 PRO 2TB)
|
||||||
|
- Defined global compiler flags targeting znver5 with `-O2 -pipe` defaults
|
||||||
|
- Established architecture decisions: SysVinit, eudev, EFISTUB, Wayland/dwl, no systemd, no bootloader, no display manager
|
||||||
|
- Designed dpack package definition format (TOML-based `.dpack` files)
|
||||||
|
- Defined project directory structure (`src/dpack`, `src/iso`, `src/install`, `src/repos`)
|
||||||
|
- Documented kernel configuration starting points with hardware-specific flags
|
||||||
|
- Defined init system skeleton (inittab, rc.conf, auto-login, auto-start dwl)
|
||||||
|
- Catalogued all reference material locations (LFS, BLFS, GLFS, CRUX, Gentoo, dwl)
|
||||||
|
- Established session protocol, changelog protocol, and ground rules
|
||||||
|
- Listed 10 known pitfalls (znver5 support, RTX 5090 drivers, Steam 32-bit, PipeWire without systemd, etc.)
|
||||||
|
- Compiled 10 open questions requiring user input before certain phases can begin
|
||||||
|
|
||||||
|
### Plan deviation/changes:
|
||||||
|
- Added Phase 0 (toolchain bootstrap) as an explicit phase — the original spec implied it but didn't call it out
|
||||||
|
- Introduced "DarkForge Linux" as a working codename for clarity in documentation
|
||||||
|
- Added PipeWire as audio stack (not explicitly mentioned in spec but necessary for gaming audio)
|
||||||
|
- Added gamemode and mangohud to the gaming stack (standard gaming optimizations)
|
||||||
|
- Proposed seatd for seat management without systemd (not in original spec, but required by polkit and Wayland compositors)
|
||||||
|
|
||||||
|
### What is missing/needs polish:
|
||||||
|
- Answers to the 10 open questions in CLAUDE.md (filesystem choice, bluetooth, wifi, shell, hostname, etc.)
|
||||||
|
- Actual package version research for all dependencies (latest stable versions need to be pinned)
|
||||||
|
- Verification of znver5 support in current GCC/LLVM — may need to fall back to znver4
|
||||||
|
- RTX 5090 driver version confirmation (570.x+ branch)
|
||||||
|
- dwl patch compatibility assessment (which patches work together on latest dwl)
|
||||||
|
|
||||||
|
---
|
||||||
126
docs/TESTING.md
Normal file
126
docs/TESTING.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# DarkForge Linux — Phase 12 Integration Test Checklist
|
||||||
|
|
||||||
|
This checklist covers end-to-end testing of every phase's deliverables.
|
||||||
|
|
||||||
|
## Pre-Test Requirements
|
||||||
|
|
||||||
|
- [ ] QEMU with OVMF (UEFI firmware) installed on the host
|
||||||
|
- [ ] At least 100GB free disk space for QEMU images
|
||||||
|
- [ ] DarkForge ISO built and bootable
|
||||||
|
|
||||||
|
## Phase 0 — Toolchain Bootstrap
|
||||||
|
|
||||||
|
- [ ] All source tarballs download successfully
|
||||||
|
- [ ] Binutils pass 1 compiles without errors
|
||||||
|
- [ ] GCC pass 1 compiles without errors
|
||||||
|
- [ ] Linux headers install to `$LFS/usr/include`
|
||||||
|
- [ ] Glibc compiles and passes all 6 sanity checks
|
||||||
|
- [ ] Libstdc++ compiles
|
||||||
|
- [ ] All Chapter 6 temporary tools build
|
||||||
|
- [ ] GCC pass 2 produces a working `cc` compiler
|
||||||
|
- [ ] "Hello World" test compiles and runs inside chroot
|
||||||
|
- [ ] Exit: `echo 'int main(){}' | cc -x c - && ./a.out` succeeds
|
||||||
|
|
||||||
|
## Phase 1 — dpack Core
|
||||||
|
|
||||||
|
- [ ] `cargo build --release` compiles with 0 errors and 0 warnings
|
||||||
|
- [ ] `cargo test` — all unit tests pass
|
||||||
|
- [ ] `dpack --version` prints version info
|
||||||
|
- [ ] `dpack install zlib` — resolves deps, downloads, builds, installs, registers in db
|
||||||
|
- [ ] `dpack list` — shows zlib as installed
|
||||||
|
- [ ] `dpack info zlib` — shows correct metadata
|
||||||
|
- [ ] `dpack remove zlib` — removes files, unregisters
|
||||||
|
- [ ] `dpack search compression` — finds zlib
|
||||||
|
|
||||||
|
## Phase 2 — dpack Advanced
|
||||||
|
|
||||||
|
- [ ] `dpack convert Pkgfile` — converts a real CRUX Pkgfile to valid TOML
|
||||||
|
- [ ] `dpack convert *.ebuild` — converts a real Gentoo ebuild to valid TOML
|
||||||
|
- [ ] Converted TOML parses without errors when loaded by dpack
|
||||||
|
- [ ] `dpack check` — reports file conflicts and scans solibs
|
||||||
|
- [ ] `dpack upgrade` — compares versions and offers upgrade plan
|
||||||
|
|
||||||
|
## Phase 3 — Base System Packages
|
||||||
|
|
||||||
|
- [ ] All 67 core/ packages have valid TOML definitions
|
||||||
|
- [ ] All dependencies resolve within core/ (no missing deps)
|
||||||
|
- [ ] At least 5 representative packages build successfully: zlib, ncurses, bash, openssl, python
|
||||||
|
- [ ] Exit: minimal system boots to shell in QEMU
|
||||||
|
|
||||||
|
## Phase 4 — Kernel
|
||||||
|
|
||||||
|
- [ ] `make olddefconfig` succeeds on the config file
|
||||||
|
- [ ] Kernel compiles with `make -j32`
|
||||||
|
- [ ] Kernel boots via EFISTUB in QEMU (using OVMF)
|
||||||
|
- [ ] `dmesg` shows: AMD Zen CPU detected, NVMe detected, R8169 NIC detected
|
||||||
|
- [ ] `nvidia-smi` works after installing nvidia-open modules
|
||||||
|
|
||||||
|
## Phase 5 — Init System
|
||||||
|
|
||||||
|
- [ ] System boots through rc.sysinit without errors
|
||||||
|
- [ ] All daemons in rc.conf start successfully (eudev, syslog, dbus, dhcpcd, pipewire)
|
||||||
|
- [ ] Hostname is set correctly
|
||||||
|
- [ ] Timezone is applied
|
||||||
|
- [ ] Network comes up via dhcpcd
|
||||||
|
- [ ] Auto-login on tty1 works (no password prompt)
|
||||||
|
- [ ] `rc.shutdown` stops daemons and unmounts cleanly
|
||||||
|
|
||||||
|
## Phase 6 — Desktop
|
||||||
|
|
||||||
|
- [ ] dwl compositor launches on tty1 login
|
||||||
|
- [ ] foot terminal opens inside dwl
|
||||||
|
- [ ] fuzzel launcher works (Mod+d or configured key)
|
||||||
|
- [ ] Firefox launches and renders a web page
|
||||||
|
- [ ] XWayland works (test with `xterm` or `glxgears`)
|
||||||
|
- [ ] wl-clipboard copy/paste works
|
||||||
|
- [ ] grim screenshot works
|
||||||
|
- [ ] PipeWire audio plays sound (test with `pw-play`)
|
||||||
|
|
||||||
|
## Phase 7 — NVIDIA
|
||||||
|
|
||||||
|
- [ ] nvidia-open kernel modules load (lsmod shows nvidia, nvidia-drm)
|
||||||
|
- [ ] `nvidia-smi` shows RTX 5090 with driver version
|
||||||
|
- [ ] Vulkan works: `vulkaninfo` shows NVIDIA GPU
|
||||||
|
- [ ] `vkcube` renders spinning cube
|
||||||
|
- [ ] OpenGL works: `glxgears` renders via XWayland
|
||||||
|
- [ ] GBM backend works for Wayland (dwl uses nvidia-drm)
|
||||||
|
|
||||||
|
## Phase 8 — Gaming
|
||||||
|
|
||||||
|
- [ ] Steam launches and logs in
|
||||||
|
- [ ] Steam can download and run a Proton game
|
||||||
|
- [ ] Wine runs a Windows executable
|
||||||
|
- [ ] mangohud overlay shows FPS in a Vulkan game
|
||||||
|
- [ ] gamemode activates when a game launches
|
||||||
|
- [ ] PrismLauncher launches and can start Minecraft
|
||||||
|
|
||||||
|
## Phase 9 — Applications
|
||||||
|
|
||||||
|
- [ ] WezTerm launches and is usable
|
||||||
|
- [ ] FreeCAD launches (if built)
|
||||||
|
- [ ] AMD microcode loaded at boot (`dmesg | grep microcode`)
|
||||||
|
|
||||||
|
## Phase 10 — ISO
|
||||||
|
|
||||||
|
- [ ] `build-iso.sh` completes without errors
|
||||||
|
- [ ] ISO boots in QEMU with OVMF
|
||||||
|
- [ ] Live environment drops to shell
|
||||||
|
- [ ] Installer is accessible via `install` command
|
||||||
|
|
||||||
|
## Phase 11 — Installer
|
||||||
|
|
||||||
|
- [ ] Disk selection lists available drives
|
||||||
|
- [ ] Auto-partition creates ESP + swap + root
|
||||||
|
- [ ] Filesystems format correctly
|
||||||
|
- [ ] Base system installs to target
|
||||||
|
- [ ] User account is created with correct groups
|
||||||
|
- [ ] Locale and timezone are applied
|
||||||
|
- [ ] EFISTUB boot entry is created
|
||||||
|
- [ ] System reboots into installed DarkForge
|
||||||
|
|
||||||
|
## End-to-End Flow
|
||||||
|
|
||||||
|
- [ ] Fresh ISO → install → reboot → auto-login → dwl → Firefox → Steam game
|
||||||
|
- [ ] `dpack install` and `dpack remove` work on the installed system
|
||||||
|
- [ ] System hibernates and resumes (96GB swap)
|
||||||
|
- [ ] Clean shutdown via `shutdown -h now`
|
||||||
64
kernel/README.md
Normal file
64
kernel/README.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# DarkForge Kernel Configuration
|
||||||
|
|
||||||
|
Hardware-specific Linux 6.19.8 kernel configuration for the target machine.
|
||||||
|
|
||||||
|
## Target Hardware
|
||||||
|
|
||||||
|
- **CPU:** AMD Ryzen 9 9950X3D (Zen 5, 16C/32T)
|
||||||
|
- **GPU:** NVIDIA RTX 5090 (Blackwell) — out-of-tree nvidia-open modules
|
||||||
|
- **NIC:** Realtek RTL8125BN 2.5GbE (R8169 driver)
|
||||||
|
- **NVMe:** Samsung 9100 PRO (PCIe 5.0)
|
||||||
|
- **Motherboard:** ASUS ROG CROSSHAIR X870E HERO
|
||||||
|
|
||||||
|
## Key Choices
|
||||||
|
|
||||||
|
| Feature | Config | Why |
|
||||||
|
|---------|--------|-----|
|
||||||
|
| CPU optimization | `CONFIG_MZEN4=y` | Closest kernel config symbol; real znver5 from CFLAGS |
|
||||||
|
| Scheduler | EEVDF (default) | Modern, built-in since 6.6 |
|
||||||
|
| Preemption | `CONFIG_PREEMPT=y` | Full preemption for gaming latency |
|
||||||
|
| Timer | `CONFIG_HZ_1000=y` | Lowest latency tick rate |
|
||||||
|
| CPU governor | `CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y` | P-State EPP integration |
|
||||||
|
| NVMe | `CONFIG_BLK_DEV_NVME=y` | Built-in (root is on NVMe) |
|
||||||
|
| GPU | nouveau disabled | nvidia-open kernel modules used instead |
|
||||||
|
| Boot | `CONFIG_EFI_STUB=y` | Direct UEFI boot, no bootloader |
|
||||||
|
| Network | `CONFIG_R8169=y` | Realtek 2.5GbE |
|
||||||
|
| Hibernation | `CONFIG_HIBERNATION=y` | 96GB swap partition |
|
||||||
|
| Bluetooth | `CONFIG_BLUETOOTH=n` | Disabled |
|
||||||
|
| WiFi | `CONFIG_WIRELESS=n` | Ethernet only |
|
||||||
|
|
||||||
|
Every non-default option in `config` has an inline comment explaining the rationale.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp config /usr/src/linux-6.19.8/.config
|
||||||
|
cd /usr/src/linux-6.19.8
|
||||||
|
make olddefconfig # fill new options with defaults
|
||||||
|
make -j32 # build with 32 threads
|
||||||
|
make modules_install
|
||||||
|
cp arch/x86/boot/bzImage /boot/vmlinuz
|
||||||
|
```
|
||||||
|
|
||||||
|
## NVIDIA Driver
|
||||||
|
|
||||||
|
The RTX 5090 requires nvidia-open kernel modules (570.86.16+). These are built out-of-tree after the kernel:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /usr/src/nvidia-open-570.133.07
|
||||||
|
make -j32 modules KERNEL_UNAME=$(uname -r)
|
||||||
|
make modules_install
|
||||||
|
```
|
||||||
|
|
||||||
|
The modules are loaded at boot via `/etc/rc.conf`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MODULES=(nvidia nvidia-modeset nvidia-drm nvidia-uvm)
|
||||||
|
MODULE_PARAMS=("nvidia-drm modeset=1")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
|
||||||
|
```
|
||||||
|
git@git.dannyhaslund.dk:danny8632/darkforge.git
|
||||||
|
```
|
||||||
445
kernel/config
Normal file
445
kernel/config
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
#
|
||||||
|
# DarkForge Linux — Kernel Configuration
|
||||||
|
# Target: Linux 6.19.8
|
||||||
|
# Hardware: AMD Ryzen 9 9950X3D / ASUS ROG CROSSHAIR X870E HERO / RTX 5090
|
||||||
|
# Generated: 2026-03-19
|
||||||
|
#
|
||||||
|
# Every non-default choice has a comment explaining WHY it was set.
|
||||||
|
# This config is hardware-specific — it targets exactly one machine.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# cp config /path/to/linux-6.19.8/.config
|
||||||
|
# cd /path/to/linux-6.19.8
|
||||||
|
# make olddefconfig # fill in any new options with defaults
|
||||||
|
# make -j32
|
||||||
|
#
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# GENERAL SETUP
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_LOCALVERSION="-darkforge"
|
||||||
|
# Tag the kernel so we can identify our build in uname -r
|
||||||
|
|
||||||
|
CONFIG_DEFAULT_HOSTNAME="darkforge"
|
||||||
|
# Matches the system hostname from rc.conf
|
||||||
|
|
||||||
|
CONFIG_SYSVIPC=y
|
||||||
|
# Required by many applications (wine, steam, etc.)
|
||||||
|
|
||||||
|
CONFIG_POSIX_MQUEUE=y
|
||||||
|
# POSIX message queues — needed by some build systems and dbus
|
||||||
|
|
||||||
|
CONFIG_AUDIT=n
|
||||||
|
# Disable audit subsystem — not needed for a single-user gaming/dev machine
|
||||||
|
|
||||||
|
CONFIG_CGROUPS=y
|
||||||
|
# Control groups — needed by gamemode, containers, and some system tools
|
||||||
|
|
||||||
|
CONFIG_NAMESPACES=y
|
||||||
|
CONFIG_USER_NS=y
|
||||||
|
CONFIG_PID_NS=y
|
||||||
|
CONFIG_NET_NS=y
|
||||||
|
CONFIG_UTS_NS=y
|
||||||
|
CONFIG_IPC_NS=y
|
||||||
|
# Namespaces — required by dpack build sandboxing (bubblewrap)
|
||||||
|
|
||||||
|
CONFIG_CHECKPOINT_RESTORE=n
|
||||||
|
# CRIU not needed
|
||||||
|
|
||||||
|
CONFIG_BPF_SYSCALL=y
|
||||||
|
# Extended BPF — useful for performance tools (perf, bpftrace)
|
||||||
|
|
||||||
|
CONFIG_IKCONFIG=y
|
||||||
|
CONFIG_IKCONFIG_PROC=y
|
||||||
|
# Embed the .config in the kernel and expose via /proc/config.gz
|
||||||
|
# Invaluable for debugging and rebuilding
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CPU — AMD Ryzen 9 9950X3D (Zen 5, 16C/32T, 3D V-Cache)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_PROCESSOR_SELECT=y
|
||||||
|
CONFIG_CPU_SUP_AMD=y
|
||||||
|
CONFIG_CPU_SUP_INTEL=n
|
||||||
|
# Only AMD support needed — no Intel CPUs on this system
|
||||||
|
|
||||||
|
# CONFIG_MZEN5 if available in 6.19, otherwise fall back to MZEN4
|
||||||
|
# TODO: Verify CONFIG_MZEN5 exists in 6.19 kernel headers.
|
||||||
|
# GCC 15.2.0 supports znver5 but kernel CONFIG symbol may still be MZEN4.
|
||||||
|
CONFIG_MZEN4=y
|
||||||
|
# Zen 4 is the closest available optimization level in most 6.19 builds.
|
||||||
|
# The kernel's internal CPU model selection; compiler flags (-march=znver5)
|
||||||
|
# are set separately in CFLAGS and provide the real Zen 5 optimization.
|
||||||
|
|
||||||
|
CONFIG_X86_X2APIC=y
|
||||||
|
# Extended APIC — required for >8 core AMD systems
|
||||||
|
|
||||||
|
CONFIG_NR_CPUS=64
|
||||||
|
# Support up to 64 logical CPUs (9950X3D has 32 threads)
|
||||||
|
# Slightly over-provisioned for future flexibility
|
||||||
|
|
||||||
|
CONFIG_SMP=y
|
||||||
|
# Symmetric Multi-Processing — obviously needed for 16 cores
|
||||||
|
|
||||||
|
CONFIG_SCHED_MC=y
|
||||||
|
# Multi-core scheduler support — aware of CCD/CCX topology
|
||||||
|
|
||||||
|
CONFIG_X86_MCE=y
|
||||||
|
CONFIG_X86_MCE_AMD=y
|
||||||
|
# Machine Check Exception — hardware error reporting
|
||||||
|
|
||||||
|
CONFIG_MICROCODE=y
|
||||||
|
CONFIG_MICROCODE_AMD=y
|
||||||
|
# AMD CPU microcode loading — critical for stability and security patches
|
||||||
|
# Loaded early via initramfs
|
||||||
|
|
||||||
|
CONFIG_AMD_MEM_ENCRYPT=y
|
||||||
|
# Secure Memory Encryption (SME) — available on Zen 5
|
||||||
|
|
||||||
|
CONFIG_AMD_PMC=y
|
||||||
|
# Platform Management Controller — power state management
|
||||||
|
|
||||||
|
CONFIG_X86_AMD_PSTATE=y
|
||||||
|
CONFIG_X86_AMD_PSTATE_DEFAULT_MODE=3
|
||||||
|
# AMD P-State EPP driver — preferred over acpi-cpufreq for Zen 5
|
||||||
|
# Mode 3 = guided autonomous mode (best for gaming — lets firmware optimize)
|
||||||
|
|
||||||
|
CONFIG_CPU_FREQ=y
|
||||||
|
CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y
|
||||||
|
CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
|
||||||
|
CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
|
||||||
|
CONFIG_CPU_FREQ_GOV_POWERSAVE=y
|
||||||
|
# CPU frequency scaling — schedutil is the default (integrates with scheduler)
|
||||||
|
# Performance governor available for benchmarks, powersave for idle
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SCHEDULER
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# EEVDF is the default scheduler in 6.19 (replaced CFS in 6.6)
|
||||||
|
# No CONFIG option needed — it's the only option unless BORE patch is applied
|
||||||
|
# If BORE is desired later, apply the patch and rebuild
|
||||||
|
|
||||||
|
CONFIG_PREEMPT=y
|
||||||
|
# Full kernel preemption — critical for low-latency gaming
|
||||||
|
# Trades throughput for responsiveness, which is exactly what we want
|
||||||
|
|
||||||
|
CONFIG_HZ_1000=y
|
||||||
|
# 1000Hz timer tick — lowest latency scheduler tick rate
|
||||||
|
# Important for gaming input responsiveness
|
||||||
|
|
||||||
|
CONFIG_NO_HZ_FULL=y
|
||||||
|
# Full tickless mode — no timer interrupts on idle cores
|
||||||
|
# Reduces power and latency when cores aren't busy
|
||||||
|
|
||||||
|
CONFIG_HIGH_RES_TIMERS=y
|
||||||
|
# High-resolution timers — sub-millisecond precision for game loops
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MEMORY
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_TRANSPARENT_HUGEPAGE=y
|
||||||
|
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
|
||||||
|
# THP via madvise only — let applications opt in (games often benefit)
|
||||||
|
# Not always-on to avoid fragmentation issues
|
||||||
|
|
||||||
|
CONFIG_KSM=y
|
||||||
|
# Kernel Same-page Merging — useful for VMs and wine prefixes
|
||||||
|
|
||||||
|
CONFIG_ZSWAP=y
|
||||||
|
CONFIG_ZSWAP_DEFAULT_ON=y
|
||||||
|
CONFIG_ZSWAP_COMPRESSOR_DEFAULT_ZSTD=y
|
||||||
|
# Compressed swap cache — reduces swap I/O for the 96GB swap partition
|
||||||
|
# Zstd gives best compression ratio for swap data
|
||||||
|
|
||||||
|
CONFIG_HIBERNATION=y
|
||||||
|
# Hibernate support — requires the 96GB swap partition >= RAM size
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# STORAGE — Samsung 9100 PRO NVMe (PCIe 5.0 x4)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_BLK_DEV_NVME=y
|
||||||
|
# NVMe driver built-in (not module) — root filesystem is on NVMe
|
||||||
|
# Must be built-in for EFISTUB boot without initramfs
|
||||||
|
|
||||||
|
CONFIG_NVME_MULTIPATH=n
|
||||||
|
# Single NVMe drive — no multipath needed
|
||||||
|
|
||||||
|
CONFIG_BLK_DEV_LOOP=y
|
||||||
|
# Loop devices — needed for ISO mounting and squashfs
|
||||||
|
|
||||||
|
CONFIG_BLK_DEV_DM=n
|
||||||
|
# Device mapper — not needed (no LVM, no LUKS)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# FILESYSTEM
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_EXT4_FS=y
|
||||||
|
# ext4 built-in — root filesystem, must not be a module
|
||||||
|
|
||||||
|
CONFIG_EXT4_FS_POSIX_ACL=y
|
||||||
|
# POSIX ACLs on ext4 — needed by some applications
|
||||||
|
|
||||||
|
CONFIG_TMPFS=y
|
||||||
|
CONFIG_TMPFS_POSIX_ACL=y
|
||||||
|
# tmpfs for /tmp, /run, and build sandboxes
|
||||||
|
|
||||||
|
CONFIG_VFAT_FS=y
|
||||||
|
CONFIG_FAT_DEFAULT_UTF8=y
|
||||||
|
# FAT/VFAT for the EFI System Partition
|
||||||
|
|
||||||
|
CONFIG_FUSE_FS=y
|
||||||
|
# FUSE — needed for NTFS3, appimage, and user-space filesystems
|
||||||
|
|
||||||
|
CONFIG_SQUASHFS=y
|
||||||
|
CONFIG_SQUASHFS_XZ=y
|
||||||
|
CONFIG_SQUASHFS_ZSTD=y
|
||||||
|
# Squashfs for the live ISO root filesystem
|
||||||
|
|
||||||
|
CONFIG_PROC_FS=y
|
||||||
|
CONFIG_SYSFS=y
|
||||||
|
CONFIG_DEVTMPFS=y
|
||||||
|
CONFIG_DEVTMPFS_MOUNT=y
|
||||||
|
# Essential virtual filesystems — devtmpfs auto-mount required for eudev
|
||||||
|
|
||||||
|
CONFIG_EFI_PARTITION=y
|
||||||
|
CONFIG_EFIVAR_FS=y
|
||||||
|
CONFIG_EFI_STUB=y
|
||||||
|
# *** CRITICAL *** — EFI stub enables the kernel to boot directly via UEFI
|
||||||
|
# This is how DarkForge boots: no bootloader, kernel IS the EFI binary
|
||||||
|
|
||||||
|
CONFIG_EFI=y
|
||||||
|
CONFIG_EFI_MIXED=n
|
||||||
|
# Pure 64-bit UEFI — no 32-bit EFI compatibility needed
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# GPU — NVIDIA RTX 5090 (Blackwell, GB202)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_DRM=y
|
||||||
|
CONFIG_DRM_KMS_HELPER=y
|
||||||
|
# Direct Rendering Manager — base graphics subsystem
|
||||||
|
|
||||||
|
CONFIG_DRM_NOUVEAU=n
|
||||||
|
# Disable nouveau — we use nvidia-open kernel modules exclusively
|
||||||
|
# Nouveau doesn't support RTX 5090 and would conflict
|
||||||
|
|
||||||
|
CONFIG_DRM_SIMPLEDRM=y
|
||||||
|
# Simple framebuffer driver — provides console output before nvidia loads
|
||||||
|
# This gives us a visible boot console via UEFI framebuffer
|
||||||
|
|
||||||
|
CONFIG_FB=y
|
||||||
|
CONFIG_FB_EFI=y
|
||||||
|
# EFI framebuffer — early boot display before nvidia-drm takes over
|
||||||
|
|
||||||
|
# nvidia-open modules (nvidia, nvidia-modeset, nvidia-drm, nvidia-uvm)
|
||||||
|
# are built out-of-tree and loaded via /etc/rc.conf MODULES array
|
||||||
|
# Minimum driver version: 570.86.16+ for RTX 5090
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# NETWORK — Realtek RTL8125BN (2.5GbE on X870E Hero)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_NETDEVICES=y
|
||||||
|
CONFIG_ETHERNET=y
|
||||||
|
CONFIG_NET_VENDOR_REALTEK=y
|
||||||
|
CONFIG_R8169=y
|
||||||
|
# Realtek RTL8125BN 2.5GbE controller — the only NIC on this board
|
||||||
|
# R8169 driver handles the entire RTL8125/8169 family
|
||||||
|
|
||||||
|
CONFIG_INET=y
|
||||||
|
CONFIG_IPV6=y
|
||||||
|
CONFIG_PACKET=y
|
||||||
|
CONFIG_UNIX=y
|
||||||
|
# Basic networking stack — IPv4, IPv6, raw sockets, Unix domain sockets
|
||||||
|
|
||||||
|
CONFIG_NETFILTER=y
|
||||||
|
CONFIG_NF_CONNTRACK=y
|
||||||
|
CONFIG_NF_TABLES=y
|
||||||
|
CONFIG_NFT_NAT=y
|
||||||
|
CONFIG_NFT_MASQ=y
|
||||||
|
# Netfilter with nftables — basic firewall capabilities
|
||||||
|
# Needed for NAT if running VMs or containers
|
||||||
|
|
||||||
|
CONFIG_WIRELESS=n
|
||||||
|
CONFIG_WLAN=n
|
||||||
|
# No WiFi — ethernet only (per user decision)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# USB — X870E has USB 3.2 Gen2x2 and USB4
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_USB=y
|
||||||
|
CONFIG_USB_XHCI_HCD=y
|
||||||
|
CONFIG_USB_XHCI_PCI=y
|
||||||
|
# xHCI for USB 3.x/4.x host controller
|
||||||
|
|
||||||
|
CONFIG_USB4=y
|
||||||
|
CONFIG_THUNDERBOLT=y
|
||||||
|
# USB4/Thunderbolt support — X870E has USB4 ports
|
||||||
|
|
||||||
|
CONFIG_USB_STORAGE=y
|
||||||
|
# USB mass storage — for USB drives and the live installer
|
||||||
|
|
||||||
|
CONFIG_USB_HID=y
|
||||||
|
CONFIG_HID=y
|
||||||
|
CONFIG_HID_GENERIC=y
|
||||||
|
# HID for USB keyboards, mice, and game controllers
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# INPUT — Gaming controllers
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_INPUT_JOYSTICK=y
|
||||||
|
CONFIG_JOYSTICK_XPAD=y
|
||||||
|
CONFIG_JOYSTICK_XPAD_FF=y
|
||||||
|
CONFIG_JOYSTICK_XPAD_LEDS=y
|
||||||
|
# Xbox controller support with force feedback and LED control
|
||||||
|
|
||||||
|
CONFIG_HID_SONY=y
|
||||||
|
# PlayStation controller support (DualShock/DualSense)
|
||||||
|
|
||||||
|
CONFIG_HID_STEAM=y
|
||||||
|
# Steam controller support
|
||||||
|
|
||||||
|
CONFIG_INPUT_FF_MEMLESS=y
|
||||||
|
CONFIG_INPUT_JOYDEV=y
|
||||||
|
CONFIG_INPUT_EVDEV=y
|
||||||
|
# Force feedback, joydev, and evdev — required by SDL and gaming
|
||||||
|
|
||||||
|
CONFIG_INPUT_TOUCHSCREEN=n
|
||||||
|
# No touchscreen
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SOUND — Onboard Realtek ALC4082
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_SOUND=y
|
||||||
|
CONFIG_SND=y
|
||||||
|
CONFIG_SND_HDA_INTEL=y
|
||||||
|
CONFIG_SND_HDA_CODEC_REALTEK=y
|
||||||
|
CONFIG_SND_HDA_CODEC_HDMI=y
|
||||||
|
# HD Audio for onboard Realtek codec and HDMI audio out via GPU
|
||||||
|
# PipeWire handles the userspace audio routing
|
||||||
|
|
||||||
|
CONFIG_SND_USB_AUDIO=y
|
||||||
|
# USB audio class — for USB DACs, headsets, microphones
|
||||||
|
|
||||||
|
CONFIG_SOUND_OSS_CORE=n
|
||||||
|
# No OSS emulation — ALSA only, PipeWire handles the rest
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# IOMMU — AMD-Vi (for potential GPU passthrough)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_AMD_IOMMU=y
|
||||||
|
CONFIG_AMD_IOMMU_V2=y
|
||||||
|
CONFIG_IOMMU_DEFAULT_DMA_LAZY=y
|
||||||
|
# AMD IOMMU enabled for DMA remapping
|
||||||
|
# Lazy mode is fine for single-GPU, no passthrough currently
|
||||||
|
# Enables future VFIO/GPU passthrough if needed
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# VIRTUALIZATION (minimal — for potential future VM use)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_KVM=y
|
||||||
|
CONFIG_KVM_AMD=y
|
||||||
|
CONFIG_KVM_AMD_SEV=n
|
||||||
|
# KVM for AMD — enables running VMs if needed
|
||||||
|
# SEV not needed for a gaming workstation
|
||||||
|
|
||||||
|
CONFIG_VFIO=y
|
||||||
|
CONFIG_VFIO_PCI=y
|
||||||
|
# VFIO for potential GPU passthrough
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# SECURITY (minimal — single-user system)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_SECURITY=y
|
||||||
|
CONFIG_SECCOMP=y
|
||||||
|
CONFIG_SECCOMP_FILTER=y
|
||||||
|
# Seccomp — required by bubblewrap (dpack sandbox), Chrome, Steam
|
||||||
|
|
||||||
|
CONFIG_SECURITY_SELINUX=n
|
||||||
|
CONFIG_SECURITY_APPARMOR=n
|
||||||
|
CONFIG_SECURITY_TOMOYO=n
|
||||||
|
# No MAC — unnecessary overhead for a single-user system
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DISABLE — Things we definitely don't need
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_BLUETOOTH=n
|
||||||
|
# Disabled per user decision
|
||||||
|
|
||||||
|
CONFIG_PCMCIA=n
|
||||||
|
CONFIG_ISDN=n
|
||||||
|
CONFIG_INFINIBAND=n
|
||||||
|
CONFIG_PARPORT=n
|
||||||
|
CONFIG_I2O=n
|
||||||
|
CONFIG_TELEPHONY=n
|
||||||
|
CONFIG_HAMRADIO=n
|
||||||
|
CONFIG_CAN=n
|
||||||
|
CONFIG_ATALK=n
|
||||||
|
CONFIG_X25=n
|
||||||
|
CONFIG_DECNET=n
|
||||||
|
CONFIG_ECONET=n
|
||||||
|
CONFIG_WAN=n
|
||||||
|
CONFIG_FDDI=n
|
||||||
|
CONFIG_ATM=n
|
||||||
|
# Legacy subsystems — none of this hardware exists
|
||||||
|
|
||||||
|
CONFIG_DRM_RADEON=n
|
||||||
|
CONFIG_DRM_AMDGPU=n
|
||||||
|
CONFIG_DRM_I915=n
|
||||||
|
# Disable other GPU drivers — only NVIDIA (out-of-tree) on this system
|
||||||
|
|
||||||
|
CONFIG_IWLWIFI=n
|
||||||
|
CONFIG_NET_VENDOR_INTEL=n
|
||||||
|
CONFIG_NET_VENDOR_BROADCOM=n
|
||||||
|
CONFIG_NET_VENDOR_QUALCOMM=n
|
||||||
|
# Disable unused network drivers
|
||||||
|
|
||||||
|
CONFIG_INPUT_TOUCHSCREEN=n
|
||||||
|
CONFIG_INPUT_TABLET=n
|
||||||
|
CONFIG_INPUT_MISC=n
|
||||||
|
# Disable unused input devices
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MISC
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
CONFIG_PRINTK=y
|
||||||
|
CONFIG_MAGIC_SYSRQ=y
|
||||||
|
# SysRq for emergency debugging
|
||||||
|
|
||||||
|
CONFIG_MODULES=y
|
||||||
|
CONFIG_MODULE_UNLOAD=y
|
||||||
|
# Kernel modules — needed for nvidia-open and other out-of-tree modules
|
||||||
|
|
||||||
|
CONFIG_KALLSYMS=y
|
||||||
|
CONFIG_FUTEX=y
|
||||||
|
CONFIG_EPOLL=y
|
||||||
|
CONFIG_SIGNALFD=y
|
||||||
|
CONFIG_TIMERFD=y
|
||||||
|
CONFIG_EVENTFD=y
|
||||||
|
CONFIG_AIO=y
|
||||||
|
CONFIG_IO_URING=y
|
||||||
|
# Essential syscalls needed by modern applications
|
||||||
|
|
||||||
|
CONFIG_PERF_EVENTS=y
|
||||||
|
# Performance monitoring — useful for profiling games and builds
|
||||||
|
|
||||||
|
CONFIG_DNOTIFY=y
|
||||||
|
CONFIG_INOTIFY_USER=y
|
||||||
|
CONFIG_FANOTIFY=y
|
||||||
|
# Filesystem notifications — needed by many desktop applications
|
||||||
|
|
||||||
|
CONFIG_EXPERT=y
|
||||||
|
# Enable expert mode to access all configuration options
|
||||||
1
src/dpack/.gitignore
vendored
Normal file
1
src/dpack/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target/
|
||||||
2212
src/dpack/Cargo.lock
generated
Normal file
2212
src/dpack/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
src/dpack/Cargo.toml
Normal file
42
src/dpack/Cargo.toml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
[package]
|
||||||
|
name = "dpack"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "DarkForge Linux package manager — between CRUX pkgutils and Gentoo emerge"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["Danny"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# TOML parsing for package definitions and database
|
||||||
|
toml = "0.8"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
|
# CLI argument parsing
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|
||||||
|
# Error handling
|
||||||
|
anyhow = "1"
|
||||||
|
thiserror = "2"
|
||||||
|
|
||||||
|
# File operations and checksums
|
||||||
|
sha2 = "0.10"
|
||||||
|
walkdir = "2"
|
||||||
|
|
||||||
|
# HTTP for source downloads
|
||||||
|
reqwest = { version = "0.12", features = ["blocking", "rustls-tls"], default-features = false }
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.11"
|
||||||
|
|
||||||
|
# Colorized terminal output
|
||||||
|
colored = "2"
|
||||||
|
|
||||||
|
# Regex for converter modules
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3"
|
||||||
|
assert_cmd = "2"
|
||||||
|
predicates = "3"
|
||||||
198
src/dpack/README.md
Normal file
198
src/dpack/README.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# dpack — DarkForge Package Manager
|
||||||
|
|
||||||
|
A source-based package manager for DarkForge Linux, positioned between CRUX's `pkgutils` and Gentoo's `emerge` in complexity. Written in Rust.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **TOML package definitions** — clean, readable package recipes
|
||||||
|
- **Dependency resolution** — topological sort with circular dependency detection
|
||||||
|
- **Build sandboxing** — bubblewrap (bwrap) isolation with PID/network namespaces
|
||||||
|
- **Installed package database** — file-based TOML tracking in `/var/lib/dpack/db/`
|
||||||
|
- **Full build orchestration** — download → checksum → extract → sandbox build → stage → commit → register
|
||||||
|
- **CRUX Pkgfile converter** — convert CRUX ports to dpack format
|
||||||
|
- **Gentoo ebuild converter** — best-effort conversion of Gentoo ebuilds (handles ~80% of cases)
|
||||||
|
- **Shared library conflict detection** — ELF binary scanning via readelf/objdump
|
||||||
|
- **Reverse dependency tracking** — warns before removing packages that others depend on
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Rust 1.75+ (build)
|
||||||
|
- Linux (runtime — uses Linux namespaces for sandboxing)
|
||||||
|
- bubblewrap (`bwrap`) for sandboxed builds (optional, falls back to direct execution)
|
||||||
|
- `curl` or `wget` for source downloads
|
||||||
|
- `tar` for source extraction
|
||||||
|
- `readelf` or `objdump` for shared library scanning
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/dpack
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary is at `target/release/dpack`. Install it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo install -m755 target/release/dpack /usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install a package (resolves deps, builds in sandbox, installs, updates db)
|
||||||
|
dpack install zlib
|
||||||
|
|
||||||
|
# Install multiple packages
|
||||||
|
dpack install openssl curl git
|
||||||
|
|
||||||
|
# Remove a package (warns about reverse deps, removes files)
|
||||||
|
dpack remove zlib
|
||||||
|
|
||||||
|
# Upgrade packages (compares installed vs repo versions)
|
||||||
|
dpack upgrade # upgrade all outdated packages
|
||||||
|
dpack upgrade openssl git # upgrade specific packages
|
||||||
|
|
||||||
|
# Search for packages
|
||||||
|
dpack search compression
|
||||||
|
|
||||||
|
# Show package info
|
||||||
|
dpack info zlib
|
||||||
|
|
||||||
|
# List installed packages
|
||||||
|
dpack list
|
||||||
|
|
||||||
|
# Check for file conflicts and shared library issues
|
||||||
|
dpack check
|
||||||
|
|
||||||
|
# Convert foreign package formats
|
||||||
|
dpack convert /path/to/Pkgfile # CRUX → dpack TOML (stdout)
|
||||||
|
dpack convert /path/to/curl-8.19.0.ebuild -o curl.toml # Gentoo → dpack TOML (file)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
dpack reads its configuration from `/etc/dpack.conf` (TOML format). If the file doesn't exist, sensible defaults are used.
|
||||||
|
|
||||||
|
Example `/etc/dpack.conf`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[flags]
|
||||||
|
cflags = "-march=znver5 -O2 -pipe -fomit-frame-pointer"
|
||||||
|
cxxflags = "-march=znver5 -O2 -pipe -fomit-frame-pointer"
|
||||||
|
ldflags = "-Wl,-O1,--as-needed"
|
||||||
|
makeflags = "-j32"
|
||||||
|
|
||||||
|
[paths]
|
||||||
|
db_dir = "/var/lib/dpack/db"
|
||||||
|
repo_dir = "/var/lib/dpack/repos"
|
||||||
|
source_dir = "/var/cache/dpack/sources"
|
||||||
|
build_dir = "/var/tmp/dpack/build"
|
||||||
|
|
||||||
|
[sandbox]
|
||||||
|
enabled = true
|
||||||
|
allow_network = false
|
||||||
|
bwrap_path = "/usr/bin/bwrap"
|
||||||
|
|
||||||
|
[[repos]]
|
||||||
|
name = "core"
|
||||||
|
path = "/var/lib/dpack/repos/core"
|
||||||
|
priority = 0
|
||||||
|
|
||||||
|
[[repos]]
|
||||||
|
name = "extra"
|
||||||
|
path = "/var/lib/dpack/repos/extra"
|
||||||
|
priority = 10
|
||||||
|
|
||||||
|
[[repos]]
|
||||||
|
name = "desktop"
|
||||||
|
path = "/var/lib/dpack/repos/desktop"
|
||||||
|
priority = 20
|
||||||
|
|
||||||
|
[[repos]]
|
||||||
|
name = "gaming"
|
||||||
|
path = "/var/lib/dpack/repos/gaming"
|
||||||
|
priority = 30
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Definition Format
|
||||||
|
|
||||||
|
Package definitions are TOML files stored at `<repo>/<name>/<name>.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "zlib"
|
||||||
|
version = "1.3.1"
|
||||||
|
description = "Compression library implementing the deflate algorithm"
|
||||||
|
url = "https://zlib.net/"
|
||||||
|
license = "zlib"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://zlib.net/zlib-${version}.tar.xz"
|
||||||
|
sha256 = "38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = []
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[dependencies.optional]
|
||||||
|
static = { description = "Build static library", default = true }
|
||||||
|
minizip = { description = "Build minizip utility", deps = [] }
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = "./configure --prefix=/usr"
|
||||||
|
make = "make"
|
||||||
|
install = "make DESTDIR=${PKG} install"
|
||||||
|
|
||||||
|
[build.flags]
|
||||||
|
cflags = "" # empty = use global defaults
|
||||||
|
ldflags = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables available in build commands
|
||||||
|
|
||||||
|
- `${PKG}` — staging directory (DESTDIR)
|
||||||
|
- `${version}` — package version (expanded in source URL)
|
||||||
|
|
||||||
|
### Build systems
|
||||||
|
|
||||||
|
The `system` field is a hint: `autotools`, `cmake`, `meson`, `cargo`, or `custom`.
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests cover: TOML parsing, dependency resolution (simple, diamond, circular), database operations (register, unregister, persistence, file ownership, conflicts), and converter parsing.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main.rs # CLI (clap) — install, remove, upgrade, search, info, list, convert, check
|
||||||
|
├── lib.rs # Library re-exports
|
||||||
|
├── config/
|
||||||
|
│ ├── mod.rs # Module root
|
||||||
|
│ ├── package.rs # PackageDefinition TOML structs + parsing + validation
|
||||||
|
│ └── global.rs # DpackConfig (flags, paths, sandbox, repos)
|
||||||
|
├── resolver/
|
||||||
|
│ ├── mod.rs # DependencyGraph, topological sort, reverse deps
|
||||||
|
│ └── solib.rs # Shared library conflict detection (ELF scanning)
|
||||||
|
├── sandbox/
|
||||||
|
│ └── mod.rs # BuildSandbox (bubblewrap + direct backends)
|
||||||
|
├── converter/
|
||||||
|
│ ├── mod.rs # Format auto-detection
|
||||||
|
│ ├── crux.rs # CRUX Pkgfile parser
|
||||||
|
│ └── gentoo.rs # Gentoo ebuild parser
|
||||||
|
├── db/
|
||||||
|
│ └── mod.rs # PackageDb (file-based TOML, installed tracking)
|
||||||
|
└── build/
|
||||||
|
└── mod.rs # BuildOrchestrator (download → build → install pipeline)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
|
||||||
|
```
|
||||||
|
git@git.dannyhaslund.dk:danny8632/dpack.git
|
||||||
|
```
|
||||||
339
src/dpack/src/build/mod.rs
Normal file
339
src/dpack/src/build/mod.rs
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
//! Package build orchestration.
|
||||||
|
//!
|
||||||
|
//! Coordinates the full install pipeline:
|
||||||
|
//! 1. Resolve dependencies (via `resolver`)
|
||||||
|
//! 2. Download source tarball
|
||||||
|
//! 3. Verify SHA256 checksum
|
||||||
|
//! 4. Extract source
|
||||||
|
//! 5. Build in sandbox (via `sandbox`)
|
||||||
|
//! 6. Collect installed files from staging
|
||||||
|
//! 7. Commit files to the live filesystem
|
||||||
|
//! 8. Update the package database (via `db`)
|
||||||
|
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use crate::config::{DpackConfig, PackageDefinition};
|
||||||
|
use crate::db::{InstalledPackage, PackageDb};
|
||||||
|
use crate::resolver::{DependencyGraph, ResolvedPackage};
|
||||||
|
use crate::sandbox::{self, BuildSandbox};
|
||||||
|
|
||||||
|
/// Orchestrate the full install of one or more packages.
|
||||||
|
pub struct BuildOrchestrator {
|
||||||
|
config: DpackConfig,
|
||||||
|
db: PackageDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuildOrchestrator {
|
||||||
|
/// Create a new orchestrator with the given config and database.
|
||||||
|
pub fn new(config: DpackConfig, db: PackageDb) -> Self {
|
||||||
|
Self { config, db }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install packages by name. Resolves deps, builds, installs.
|
||||||
|
pub fn install(&mut self, package_names: &[String]) -> Result<()> {
|
||||||
|
log::info!("Resolving dependencies for: {:?}", package_names);
|
||||||
|
|
||||||
|
// Load all repos
|
||||||
|
let mut all_packages = std::collections::HashMap::new();
|
||||||
|
for repo in &self.config.repos {
|
||||||
|
let repo_pkgs = DependencyGraph::load_repo(&repo.path)?;
|
||||||
|
all_packages.extend(repo_pkgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
let installed_versions = self.db.installed_versions();
|
||||||
|
let graph = DependencyGraph::new(all_packages.clone(), installed_versions);
|
||||||
|
|
||||||
|
let plan = graph.resolve(
|
||||||
|
package_names,
|
||||||
|
&std::collections::HashMap::new(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if plan.build_order.is_empty() {
|
||||||
|
println!("All requested packages are already installed.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report the plan
|
||||||
|
if !plan.already_installed.is_empty() {
|
||||||
|
println!(
|
||||||
|
"Already installed: {}",
|
||||||
|
plan.already_installed.join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Build order ({} packages):", plan.build_order.len());
|
||||||
|
for (i, pkg) in plan.build_order.iter().enumerate() {
|
||||||
|
let marker = if pkg.build_only { " [build-only]" } else { "" };
|
||||||
|
println!(" {}. {}-{}{}", i + 1, pkg.name, pkg.version, marker);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Build each package in order
|
||||||
|
for resolved in &plan.build_order {
|
||||||
|
let pkg_def = all_packages.get(&resolved.name).with_context(|| {
|
||||||
|
format!("Package '{}' disappeared from repo", resolved.name)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.build_and_install(pkg_def, resolved)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("All packages installed successfully.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build and install a single package.
|
||||||
|
fn build_and_install(
|
||||||
|
&mut self,
|
||||||
|
pkg: &PackageDefinition,
|
||||||
|
resolved: &ResolvedPackage,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ident = pkg.ident();
|
||||||
|
println!(">>> Building {}", ident);
|
||||||
|
|
||||||
|
// Step 1: Download source
|
||||||
|
let source_path = self.download_source(pkg)?;
|
||||||
|
|
||||||
|
// Step 2: Verify checksum
|
||||||
|
self.verify_checksum(&source_path, &pkg.source.sha256)?;
|
||||||
|
|
||||||
|
// Step 3: Extract source
|
||||||
|
let build_dir = self.config.paths.build_dir.join(&ident);
|
||||||
|
let staging_dir = self.config.paths.build_dir.join(format!("{}-staging", ident));
|
||||||
|
|
||||||
|
// Clean any previous attempt
|
||||||
|
let _ = std::fs::remove_dir_all(&build_dir);
|
||||||
|
let _ = std::fs::remove_dir_all(&staging_dir);
|
||||||
|
|
||||||
|
self.extract_source(&source_path, &build_dir)?;
|
||||||
|
|
||||||
|
// Step 4: Apply patches
|
||||||
|
// Find the actual source directory (tarballs often have a top-level dir)
|
||||||
|
let actual_build_dir = find_source_dir(&build_dir)?;
|
||||||
|
|
||||||
|
// Step 5: Build in sandbox
|
||||||
|
let sandbox = BuildSandbox::new(
|
||||||
|
&self.config,
|
||||||
|
pkg,
|
||||||
|
&actual_build_dir,
|
||||||
|
&staging_dir,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
sandbox.run_build(pkg)?;
|
||||||
|
|
||||||
|
// Step 6: Collect installed files
|
||||||
|
let staged_files = sandbox::collect_staged_files(&staging_dir)?;
|
||||||
|
|
||||||
|
if staged_files.is_empty() {
|
||||||
|
log::warn!("No files were installed by {} — is the install step correct?", ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7: Commit files to the live filesystem
|
||||||
|
self.commit_staged_files(&staging_dir)?;
|
||||||
|
|
||||||
|
// Step 8: Update database
|
||||||
|
let size = calculate_dir_size(&staging_dir);
|
||||||
|
let record = InstalledPackage {
|
||||||
|
name: pkg.package.name.clone(),
|
||||||
|
version: pkg.package.version.clone(),
|
||||||
|
description: pkg.package.description.clone(),
|
||||||
|
run_deps: pkg.effective_run_deps(&resolved.features),
|
||||||
|
build_deps: pkg.effective_build_deps(&resolved.features),
|
||||||
|
features: resolved.features.clone(),
|
||||||
|
files: staged_files,
|
||||||
|
installed_at: SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs(),
|
||||||
|
repo: "core".to_string(), // TODO: track actual repo
|
||||||
|
size,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.db.register(record)?;
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let _ = std::fs::remove_dir_all(&build_dir);
|
||||||
|
let _ = std::fs::remove_dir_all(&staging_dir);
|
||||||
|
|
||||||
|
println!(">>> {} installed successfully", ident);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Download the source tarball to the source cache.
|
||||||
|
fn download_source(&self, pkg: &PackageDefinition) -> Result<PathBuf> {
|
||||||
|
let url = pkg.expanded_source_url();
|
||||||
|
let filename = url
|
||||||
|
.rsplit('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or("source.tar.gz");
|
||||||
|
let dest = self.config.paths.source_dir.join(filename);
|
||||||
|
|
||||||
|
std::fs::create_dir_all(&self.config.paths.source_dir)?;
|
||||||
|
|
||||||
|
if dest.exists() {
|
||||||
|
log::info!("Source already cached: {}", dest.display());
|
||||||
|
return Ok(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Downloading: {}", url);
|
||||||
|
|
||||||
|
// Use curl/wget via subprocess for now — avoids pulling in reqwest
|
||||||
|
// at build time for the bootstrap phase
|
||||||
|
let status = std::process::Command::new("curl")
|
||||||
|
.args(["-fLo", &dest.to_string_lossy(), &url])
|
||||||
|
.status()
|
||||||
|
.or_else(|_| {
|
||||||
|
std::process::Command::new("wget")
|
||||||
|
.args(["-O", &dest.to_string_lossy(), &url])
|
||||||
|
.status()
|
||||||
|
})
|
||||||
|
.context("Neither curl nor wget available for downloading")?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
bail!("Download failed for: {}", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify the SHA256 checksum of a file.
|
||||||
|
fn verify_checksum(&self, path: &Path, expected: &str) -> Result<()> {
|
||||||
|
log::info!("Verifying checksum: {}", path.display());
|
||||||
|
|
||||||
|
let mut file = std::fs::File::open(path)
|
||||||
|
.with_context(|| format!("Failed to open: {}", path.display()))?;
|
||||||
|
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
let mut buffer = [0u8; 8192];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let n = file.read(&mut buffer)?;
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hasher.update(&buffer[..n]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actual = format!("{:x}", hasher.finalize());
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
bail!(
|
||||||
|
"Checksum mismatch for {}: expected {}, got {}",
|
||||||
|
path.display(),
|
||||||
|
expected,
|
||||||
|
actual
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Checksum verified: {}", actual);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a source tarball into the build directory.
|
||||||
|
fn extract_source(&self, tarball: &Path, build_dir: &Path) -> Result<()> {
|
||||||
|
std::fs::create_dir_all(build_dir)?;
|
||||||
|
|
||||||
|
let tarball_str = tarball.to_string_lossy();
|
||||||
|
let build_str = build_dir.to_string_lossy();
|
||||||
|
|
||||||
|
// Determine tar flags based on extension
|
||||||
|
let tar_flags = if tarball_str.ends_with(".tar.xz") || tarball_str.ends_with(".txz") {
|
||||||
|
"-xJf"
|
||||||
|
} else if tarball_str.ends_with(".tar.gz") || tarball_str.ends_with(".tgz") {
|
||||||
|
"-xzf"
|
||||||
|
} else if tarball_str.ends_with(".tar.bz2") || tarball_str.ends_with(".tbz2") {
|
||||||
|
"-xjf"
|
||||||
|
} else if tarball_str.ends_with(".tar.zst") {
|
||||||
|
"--zstd -xf"
|
||||||
|
} else {
|
||||||
|
"-xf"
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = std::process::Command::new("tar")
|
||||||
|
.arg(tar_flags)
|
||||||
|
.arg(&*tarball_str)
|
||||||
|
.arg("-C")
|
||||||
|
.arg(&*build_str)
|
||||||
|
.status()
|
||||||
|
.context("Failed to run tar")?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
bail!("Failed to extract: {}", tarball.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy staged files from the staging directory to the live filesystem root.
|
||||||
|
fn commit_staged_files(&self, staging_dir: &Path) -> Result<()> {
|
||||||
|
if !staging_dir.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk the staging tree and copy each file to its target location
|
||||||
|
for entry in walkdir::WalkDir::new(staging_dir)
|
||||||
|
.min_depth(1)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
|
let rel = entry.path().strip_prefix(staging_dir)?;
|
||||||
|
let target = Path::new("/").join(rel);
|
||||||
|
|
||||||
|
if entry.file_type().is_dir() {
|
||||||
|
std::fs::create_dir_all(&target).ok();
|
||||||
|
} else if entry.file_type().is_file() {
|
||||||
|
if let Some(parent) = target.parent() {
|
||||||
|
std::fs::create_dir_all(parent).ok();
|
||||||
|
}
|
||||||
|
std::fs::copy(entry.path(), &target).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to install file: {} -> {}",
|
||||||
|
entry.path().display(),
|
||||||
|
target.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the database.
|
||||||
|
pub fn db(&self) -> &PackageDb {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the database.
|
||||||
|
pub fn db_mut(&mut self) -> &mut PackageDb {
|
||||||
|
&mut self.db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the actual source directory inside the extraction directory.
|
||||||
|
/// Tarballs usually contain a top-level directory (e.g., `zlib-1.3.1/`).
|
||||||
|
fn find_source_dir(build_dir: &Path) -> Result<PathBuf> {
|
||||||
|
let entries: Vec<_> = std::fs::read_dir(build_dir)?
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.file_type().map_or(false, |t| t.is_dir()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if entries.len() == 1 {
|
||||||
|
Ok(entries[0].path())
|
||||||
|
} else {
|
||||||
|
// No single top-level directory — use the build dir itself
|
||||||
|
Ok(build_dir.to_path_buf())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the total size of files in a directory.
|
||||||
|
fn calculate_dir_size(dir: &Path) -> u64 {
|
||||||
|
walkdir::WalkDir::new(dir)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.file_type().is_file())
|
||||||
|
.map(|e| e.metadata().map_or(0, |m| m.len()))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
265
src/dpack/src/config/global.rs
Normal file
265
src/dpack/src/config/global.rs
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
//! Global dpack configuration (`/etc/dpack.conf`).
|
||||||
|
//!
|
||||||
|
//! Controls compiler flags, repository paths, sandbox settings, and other
|
||||||
|
//! system-wide package manager behavior.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// Default configuration file location
|
||||||
|
pub const DEFAULT_CONFIG_PATH: &str = "/etc/dpack.conf";
|
||||||
|
|
||||||
|
/// Default package database location
|
||||||
|
pub const DEFAULT_DB_PATH: &str = "/var/lib/dpack/db";
|
||||||
|
|
||||||
|
/// Default repository root
|
||||||
|
pub const DEFAULT_REPO_PATH: &str = "/var/lib/dpack/repos";
|
||||||
|
|
||||||
|
/// Default source download cache
|
||||||
|
pub const DEFAULT_SOURCE_DIR: &str = "/var/cache/dpack/sources";
|
||||||
|
|
||||||
|
/// Default build directory
|
||||||
|
pub const DEFAULT_BUILD_DIR: &str = "/var/tmp/dpack/build";
|
||||||
|
|
||||||
|
/// Global dpack configuration.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DpackConfig {
|
||||||
|
/// Compiler and linker flags
|
||||||
|
#[serde(default)]
|
||||||
|
pub flags: GlobalFlags,
|
||||||
|
|
||||||
|
/// Paths for repos, database, sources, build directory
|
||||||
|
#[serde(default)]
|
||||||
|
pub paths: PathConfig,
|
||||||
|
|
||||||
|
/// Sandbox configuration
|
||||||
|
#[serde(default)]
|
||||||
|
pub sandbox: SandboxConfig,
|
||||||
|
|
||||||
|
/// Repository configuration
|
||||||
|
#[serde(default)]
|
||||||
|
pub repos: Vec<RepoConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Global compiler flags — applied to all packages unless overridden.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct GlobalFlags {
|
||||||
|
/// C compiler flags (e.g., "-march=znver5 -O2 -pipe -fomit-frame-pointer")
|
||||||
|
pub cflags: String,
|
||||||
|
|
||||||
|
/// C++ compiler flags (defaults to same as cflags)
|
||||||
|
pub cxxflags: String,
|
||||||
|
|
||||||
|
/// Linker flags (e.g., "-Wl,-O1,--as-needed")
|
||||||
|
pub ldflags: String,
|
||||||
|
|
||||||
|
/// Make flags (e.g., "-j32")
|
||||||
|
pub makeflags: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GlobalFlags {
|
||||||
|
fn default() -> Self {
|
||||||
|
// DarkForge defaults — Zen 5 optimized
|
||||||
|
Self {
|
||||||
|
cflags: "-march=znver5 -O2 -pipe -fomit-frame-pointer".to_string(),
|
||||||
|
cxxflags: "-march=znver5 -O2 -pipe -fomit-frame-pointer".to_string(),
|
||||||
|
ldflags: "-Wl,-O1,--as-needed".to_string(),
|
||||||
|
makeflags: "-j32".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// File system paths used by dpack.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PathConfig {
|
||||||
|
/// Path to the installed package database
|
||||||
|
pub db_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the package repository definitions
|
||||||
|
pub repo_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Path to cache downloaded source tarballs
|
||||||
|
pub source_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Path for build sandboxes / staging areas
|
||||||
|
pub build_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PathConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
db_dir: PathBuf::from(DEFAULT_DB_PATH),
|
||||||
|
repo_dir: PathBuf::from(DEFAULT_REPO_PATH),
|
||||||
|
source_dir: PathBuf::from(DEFAULT_SOURCE_DIR),
|
||||||
|
build_dir: PathBuf::from(DEFAULT_BUILD_DIR),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build sandbox configuration.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SandboxConfig {
|
||||||
|
/// Enable sandboxing (mount/PID/net namespaces via bubblewrap)
|
||||||
|
pub enabled: bool,
|
||||||
|
|
||||||
|
/// Allow network access during build (some packages need it)
|
||||||
|
pub allow_network: bool,
|
||||||
|
|
||||||
|
/// Path to bubblewrap binary
|
||||||
|
pub bwrap_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SandboxConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: true,
|
||||||
|
allow_network: false,
|
||||||
|
bwrap_path: PathBuf::from("/usr/bin/bwrap"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A package repository definition.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct RepoConfig {
|
||||||
|
/// Repository name (e.g., "core", "extra", "gaming")
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// Path to the repository directory containing package definitions
|
||||||
|
pub path: PathBuf,
|
||||||
|
|
||||||
|
/// Priority (lower = higher priority for conflict resolution)
|
||||||
|
#[serde(default)]
|
||||||
|
pub priority: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DpackConfig {
|
||||||
|
/// Load configuration from a TOML file.
|
||||||
|
pub fn from_file(path: &Path) -> Result<Self> {
|
||||||
|
let content = std::fs::read_to_string(path)
|
||||||
|
.with_context(|| format!("Failed to read config: {}", path.display()))?;
|
||||||
|
toml::from_str(&content).context("Failed to parse dpack configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load from the default location, or return defaults if not found.
|
||||||
|
pub fn load_default() -> Self {
|
||||||
|
let path = Path::new(DEFAULT_CONFIG_PATH);
|
||||||
|
if path.exists() {
|
||||||
|
Self::from_file(path).unwrap_or_else(|e| {
|
||||||
|
log::warn!("Failed to load {}: {}, using defaults", path.display(), e);
|
||||||
|
Self::default()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the effective CFLAGS for a package (package override or global default).
|
||||||
|
pub fn effective_cflags<'a>(&'a self, pkg_override: &'a str) -> &'a str {
|
||||||
|
if pkg_override.is_empty() {
|
||||||
|
&self.flags.cflags
|
||||||
|
} else {
|
||||||
|
pkg_override
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the effective LDFLAGS for a package.
|
||||||
|
pub fn effective_ldflags<'a>(&'a self, pkg_override: &'a str) -> &'a str {
|
||||||
|
if pkg_override.is_empty() {
|
||||||
|
&self.flags.ldflags
|
||||||
|
} else {
|
||||||
|
pkg_override
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a package definition across all configured repos.
|
||||||
|
/// Returns the first match by repo priority.
|
||||||
|
pub fn find_package(&self, name: &str) -> Option<PathBuf> {
|
||||||
|
let mut repos = self.repos.clone();
|
||||||
|
repos.sort_by_key(|r| r.priority);
|
||||||
|
|
||||||
|
for repo in &repos {
|
||||||
|
let pkg_path = repo.path.join(name).join(format!("{}.toml", name));
|
||||||
|
if pkg_path.exists() {
|
||||||
|
return Some(pkg_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DpackConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
flags: GlobalFlags::default(),
|
||||||
|
paths: PathConfig::default(),
|
||||||
|
sandbox: SandboxConfig::default(),
|
||||||
|
repos: vec![
|
||||||
|
RepoConfig {
|
||||||
|
name: "core".to_string(),
|
||||||
|
path: PathBuf::from("/var/lib/dpack/repos/core"),
|
||||||
|
priority: 0,
|
||||||
|
},
|
||||||
|
RepoConfig {
|
||||||
|
name: "extra".to_string(),
|
||||||
|
path: PathBuf::from("/var/lib/dpack/repos/extra"),
|
||||||
|
priority: 10,
|
||||||
|
},
|
||||||
|
RepoConfig {
|
||||||
|
name: "desktop".to_string(),
|
||||||
|
path: PathBuf::from("/var/lib/dpack/repos/desktop"),
|
||||||
|
priority: 20,
|
||||||
|
},
|
||||||
|
RepoConfig {
|
||||||
|
name: "gaming".to_string(),
|
||||||
|
path: PathBuf::from("/var/lib/dpack/repos/gaming"),
|
||||||
|
priority: 30,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_config() {
|
||||||
|
let config = DpackConfig::default();
|
||||||
|
assert_eq!(config.flags.cflags, "-march=znver5 -O2 -pipe -fomit-frame-pointer");
|
||||||
|
assert_eq!(config.flags.makeflags, "-j32");
|
||||||
|
assert!(config.sandbox.enabled);
|
||||||
|
assert!(!config.sandbox.allow_network);
|
||||||
|
assert_eq!(config.repos.len(), 4);
|
||||||
|
assert_eq!(config.repos[0].name, "core");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_effective_cflags_default() {
|
||||||
|
let config = DpackConfig::default();
|
||||||
|
assert_eq!(
|
||||||
|
config.effective_cflags(""),
|
||||||
|
"-march=znver5 -O2 -pipe -fomit-frame-pointer"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_effective_cflags_override() {
|
||||||
|
let config = DpackConfig::default();
|
||||||
|
assert_eq!(
|
||||||
|
config.effective_cflags("-march=znver5 -O3 -pipe"),
|
||||||
|
"-march=znver5 -O3 -pipe"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_roundtrip() {
|
||||||
|
let config = DpackConfig::default();
|
||||||
|
let toml_str = toml::to_string_pretty(&config).unwrap();
|
||||||
|
let reparsed: DpackConfig = toml::from_str(&toml_str).unwrap();
|
||||||
|
assert_eq!(reparsed.flags.cflags, config.flags.cflags);
|
||||||
|
assert_eq!(reparsed.repos.len(), config.repos.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/dpack/src/config/mod.rs
Normal file
19
src/dpack/src/config/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//! Configuration and package definition parsing.
|
||||||
|
//!
|
||||||
|
//! Handles reading `.toml` package definition files and the global dpack
|
||||||
|
//! configuration. The package definition format is documented in CLAUDE.md §dpack.
|
||||||
|
//!
|
||||||
|
//! # Package Definition Format
|
||||||
|
//!
|
||||||
|
//! Package definitions are TOML files with these sections:
|
||||||
|
//! - `[package]` — name, version, description, URL, license
|
||||||
|
//! - `[source]` — download URL and SHA256 checksum
|
||||||
|
//! - `[dependencies]` — runtime, build, and optional dependencies
|
||||||
|
//! - `[build]` — configure, make, and install commands
|
||||||
|
//! - `[build.flags]` — per-package compiler flag overrides
|
||||||
|
|
||||||
|
pub mod package;
|
||||||
|
pub mod global;
|
||||||
|
|
||||||
|
pub use package::PackageDefinition;
|
||||||
|
pub use global::DpackConfig;
|
||||||
364
src/dpack/src/config/package.rs
Normal file
364
src/dpack/src/config/package.rs
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
//! Package definition structs and TOML parsing.
|
||||||
|
//!
|
||||||
|
//! A `.toml` package definition describes how to download, build, and install
|
||||||
|
//! a single software package. This module defines the Rust structs that map
|
||||||
|
//! to the TOML schema, plus loading/validation logic.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Top-level package definition — the entire contents of a `.toml` file.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PackageDefinition {
|
||||||
|
pub package: PackageMetadata,
|
||||||
|
pub source: SourceInfo,
|
||||||
|
pub dependencies: Dependencies,
|
||||||
|
pub build: BuildInstructions,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `[package]` section — basic metadata.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PackageMetadata {
|
||||||
|
/// Package name (must be unique within a repository)
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// Package version (semver or upstream version string)
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
/// Short description of the package
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
/// Upstream project URL
|
||||||
|
pub url: String,
|
||||||
|
|
||||||
|
/// License identifier (SPDX preferred)
|
||||||
|
pub license: String,
|
||||||
|
|
||||||
|
/// Optional epoch for version comparison when upstream resets versions
|
||||||
|
#[serde(default)]
|
||||||
|
pub epoch: u32,
|
||||||
|
|
||||||
|
/// Package revision (for repackaging without upstream version change)
|
||||||
|
#[serde(default = "default_revision")]
|
||||||
|
pub revision: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_revision() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `[source]` section — where to get the source code.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SourceInfo {
|
||||||
|
/// Download URL. May contain `${version}` which is expanded at runtime.
|
||||||
|
pub url: String,
|
||||||
|
|
||||||
|
/// SHA256 checksum of the source tarball
|
||||||
|
pub sha256: String,
|
||||||
|
|
||||||
|
/// Optional: additional source files or patches to download
|
||||||
|
#[serde(default)]
|
||||||
|
pub patches: Vec<PatchInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A patch to apply to the source before building.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PatchInfo {
|
||||||
|
/// Download URL for the patch
|
||||||
|
pub url: String,
|
||||||
|
|
||||||
|
/// SHA256 checksum of the patch file
|
||||||
|
pub sha256: String,
|
||||||
|
|
||||||
|
/// Strip level for `patch -p<N>` (default: 1)
|
||||||
|
#[serde(default = "default_strip")]
|
||||||
|
pub strip: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_strip() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `[dependencies]` section — what this package needs.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Dependencies {
|
||||||
|
/// Runtime dependencies (must be installed for the package to function)
|
||||||
|
#[serde(default)]
|
||||||
|
pub run: Vec<String>,
|
||||||
|
|
||||||
|
/// Build-time dependencies (only needed during compilation)
|
||||||
|
#[serde(default)]
|
||||||
|
pub build: Vec<String>,
|
||||||
|
|
||||||
|
/// Optional features — maps feature name to its definition
|
||||||
|
#[serde(default)]
|
||||||
|
pub optional: HashMap<String, OptionalDep>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An optional dependency / feature flag (inspired by Gentoo USE flags).
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct OptionalDep {
|
||||||
|
/// Human-readable description of what this feature does
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
/// Whether this feature is enabled by default
|
||||||
|
#[serde(default)]
|
||||||
|
pub default: bool,
|
||||||
|
|
||||||
|
/// Additional runtime dependencies required by this feature
|
||||||
|
#[serde(default)]
|
||||||
|
pub deps: Vec<String>,
|
||||||
|
|
||||||
|
/// Additional build-time dependencies required by this feature
|
||||||
|
#[serde(default)]
|
||||||
|
pub build_deps: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `[build]` section — how to compile and install.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct BuildInstructions {
|
||||||
|
/// Configure command (e.g., `./configure --prefix=/usr`)
|
||||||
|
/// May be empty for packages that don't need configuration.
|
||||||
|
#[serde(default)]
|
||||||
|
pub configure: String,
|
||||||
|
|
||||||
|
/// Build command (e.g., `make`)
|
||||||
|
#[serde(default = "default_make")]
|
||||||
|
pub make: String,
|
||||||
|
|
||||||
|
/// Install command (e.g., `make DESTDIR=${PKG} install`)
|
||||||
|
pub install: String,
|
||||||
|
|
||||||
|
/// Optional: commands to run before configure (e.g., autoreconf, patching)
|
||||||
|
#[serde(default)]
|
||||||
|
pub prepare: String,
|
||||||
|
|
||||||
|
/// Optional: commands to run after install (e.g., cleanup, stripping)
|
||||||
|
#[serde(default)]
|
||||||
|
pub post_install: String,
|
||||||
|
|
||||||
|
/// Optional: custom test command
|
||||||
|
#[serde(default)]
|
||||||
|
pub check: String,
|
||||||
|
|
||||||
|
/// Per-package compiler flag overrides
|
||||||
|
#[serde(default)]
|
||||||
|
pub flags: BuildFlags,
|
||||||
|
|
||||||
|
/// Build system type hint (autotools, cmake, meson, cargo, custom)
|
||||||
|
#[serde(default)]
|
||||||
|
pub system: BuildSystem,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_make() -> String {
|
||||||
|
"make".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-package compiler flag overrides.
|
||||||
|
/// Empty strings mean "use global defaults from dpack.conf".
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct BuildFlags {
|
||||||
|
#[serde(default)]
|
||||||
|
pub cflags: String,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub cxxflags: String,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub ldflags: String,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub makeflags: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hint for the build system used by this package.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum BuildSystem {
|
||||||
|
#[default]
|
||||||
|
Autotools,
|
||||||
|
Cmake,
|
||||||
|
Meson,
|
||||||
|
Cargo,
|
||||||
|
Custom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageDefinition {
|
||||||
|
/// Load a package definition from a `.toml` file.
|
||||||
|
pub fn from_file(path: &Path) -> Result<Self> {
|
||||||
|
let content = std::fs::read_to_string(path)
|
||||||
|
.with_context(|| format!("Failed to read package file: {}", path.display()))?;
|
||||||
|
Self::from_str(&content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a package definition from a TOML string.
|
||||||
|
pub fn from_str(content: &str) -> Result<Self> {
|
||||||
|
let pkg: Self = toml::from_str(content)
|
||||||
|
.context("Failed to parse package definition TOML")?;
|
||||||
|
pkg.validate()?;
|
||||||
|
Ok(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize this definition back to TOML.
|
||||||
|
pub fn to_toml(&self) -> Result<String> {
|
||||||
|
toml::to_string_pretty(self).context("Failed to serialize package definition")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate the package definition for correctness.
|
||||||
|
fn validate(&self) -> Result<()> {
|
||||||
|
anyhow::ensure!(!self.package.name.is_empty(), "Package name cannot be empty");
|
||||||
|
anyhow::ensure!(!self.package.version.is_empty(), "Package version cannot be empty");
|
||||||
|
anyhow::ensure!(!self.source.url.is_empty(), "Source URL cannot be empty");
|
||||||
|
anyhow::ensure!(
|
||||||
|
self.source.sha256.len() == 64 && self.source.sha256.chars().all(|c| c.is_ascii_hexdigit()),
|
||||||
|
"SHA256 checksum must be exactly 64 hex characters, got: '{}'",
|
||||||
|
self.source.sha256
|
||||||
|
);
|
||||||
|
anyhow::ensure!(!self.build.install.is_empty(), "Install command cannot be empty");
|
||||||
|
|
||||||
|
// Validate optional dep names don't contain spaces or special chars
|
||||||
|
for name in self.dependencies.optional.keys() {
|
||||||
|
anyhow::ensure!(
|
||||||
|
name.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-'),
|
||||||
|
"Optional dependency name '{}' contains invalid characters", name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand `${version}` in the source URL.
|
||||||
|
pub fn expanded_source_url(&self) -> String {
|
||||||
|
self.source.url.replace("${version}", &self.package.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all runtime dependencies, including those from enabled optional features.
|
||||||
|
pub fn effective_run_deps(&self, enabled_features: &[String]) -> Vec<String> {
|
||||||
|
let mut deps = self.dependencies.run.clone();
|
||||||
|
for feature in enabled_features {
|
||||||
|
if let Some(opt) = self.dependencies.optional.get(feature) {
|
||||||
|
deps.extend(opt.deps.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deps
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all build dependencies, including those from enabled optional features.
|
||||||
|
pub fn effective_build_deps(&self, enabled_features: &[String]) -> Vec<String> {
|
||||||
|
let mut deps = self.dependencies.build.clone();
|
||||||
|
for feature in enabled_features {
|
||||||
|
if let Some(opt) = self.dependencies.optional.get(feature) {
|
||||||
|
deps.extend(opt.build_deps.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deps
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the list of default-enabled features.
|
||||||
|
pub fn default_features(&self) -> Vec<String> {
|
||||||
|
self.dependencies
|
||||||
|
.optional
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, v)| v.default)
|
||||||
|
.map(|(k, _)| k.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full identifier: "name-version"
|
||||||
|
pub fn ident(&self) -> String {
|
||||||
|
format!("{}-{}", self.package.name, self.package.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SAMPLE_TOML: &str = r#"
|
||||||
|
[package]
|
||||||
|
name = "zlib"
|
||||||
|
version = "1.3.1"
|
||||||
|
description = "Compression library implementing the deflate algorithm"
|
||||||
|
url = "https://zlib.net/"
|
||||||
|
license = "zlib"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://zlib.net/zlib-${version}.tar.xz"
|
||||||
|
sha256 = "38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = []
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[dependencies.optional]
|
||||||
|
static = { description = "Build static library", default = true }
|
||||||
|
minizip = { description = "Build minizip utility", deps = [] }
|
||||||
|
|
||||||
|
[build]
|
||||||
|
configure = "./configure --prefix=/usr"
|
||||||
|
make = "make"
|
||||||
|
install = "make DESTDIR=${PKG} install"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_zlib() {
|
||||||
|
let pkg = PackageDefinition::from_str(SAMPLE_TOML).unwrap();
|
||||||
|
assert_eq!(pkg.package.name, "zlib");
|
||||||
|
assert_eq!(pkg.package.version, "1.3.1");
|
||||||
|
assert_eq!(pkg.package.license, "zlib");
|
||||||
|
assert_eq!(pkg.dependencies.build, vec!["gcc", "make"]);
|
||||||
|
assert!(pkg.dependencies.optional.contains_key("static"));
|
||||||
|
assert!(pkg.dependencies.optional.contains_key("minizip"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_expanded_source_url() {
|
||||||
|
let pkg = PackageDefinition::from_str(SAMPLE_TOML).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
pkg.expanded_source_url(),
|
||||||
|
"https://zlib.net/zlib-1.3.1.tar.xz"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_features() {
|
||||||
|
let pkg = PackageDefinition::from_str(SAMPLE_TOML).unwrap();
|
||||||
|
let defaults = pkg.default_features();
|
||||||
|
assert!(defaults.contains(&"static".to_string()));
|
||||||
|
assert!(!defaults.contains(&"minizip".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_effective_deps() {
|
||||||
|
let pkg = PackageDefinition::from_str(SAMPLE_TOML).unwrap();
|
||||||
|
let run_deps = pkg.effective_run_deps(&["minizip".to_string()]);
|
||||||
|
// minizip has empty deps, so run_deps should still be empty
|
||||||
|
assert!(run_deps.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_sha256() {
|
||||||
|
let bad_toml = SAMPLE_TOML.replace(
|
||||||
|
"38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32",
|
||||||
|
"bad",
|
||||||
|
);
|
||||||
|
assert!(PackageDefinition::from_str(&bad_toml).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_name() {
|
||||||
|
let bad_toml = SAMPLE_TOML.replace("name = \"zlib\"", "name = \"\"");
|
||||||
|
assert!(PackageDefinition::from_str(&bad_toml).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_roundtrip_toml() {
|
||||||
|
let pkg = PackageDefinition::from_str(SAMPLE_TOML).unwrap();
|
||||||
|
let serialized = pkg.to_toml().unwrap();
|
||||||
|
let reparsed = PackageDefinition::from_str(&serialized).unwrap();
|
||||||
|
assert_eq!(pkg.package.name, reparsed.package.name);
|
||||||
|
assert_eq!(pkg.package.version, reparsed.package.version);
|
||||||
|
}
|
||||||
|
}
|
||||||
432
src/dpack/src/converter/crux.rs
Normal file
432
src/dpack/src/converter/crux.rs
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
//! CRUX Pkgfile converter.
|
||||||
|
//!
|
||||||
|
//! Parses CRUX `Pkgfile` format (bash-like syntax) and emits a dpack
|
||||||
|
//! `PackageDefinition`. Handles the common patterns:
|
||||||
|
//! - Variable assignments: `name=`, `version=`, `release=`, `source=()`
|
||||||
|
//! - Comment metadata: `# Description:`, `# URL:`, `# Depends on:`
|
||||||
|
//! - Build function: `build() { ... }`
|
||||||
|
//!
|
||||||
|
//! CRUX Pkgfile format reference:
|
||||||
|
//! - Variables are plain bash assignments
|
||||||
|
//! - `source=()` is a bash array of URLs (may span multiple lines)
|
||||||
|
//! - `build()` contains the full build logic
|
||||||
|
//! - Dependencies are in comments, not formal fields
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use regex::Regex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::config::package::*;
|
||||||
|
|
||||||
|
/// Parse a CRUX Pkgfile string into a dpack PackageDefinition.
|
||||||
|
pub fn parse_pkgfile(content: &str) -> Result<PackageDefinition> {
|
||||||
|
let mut name = String::new();
|
||||||
|
let mut version = String::new();
|
||||||
|
let mut release = 1u32;
|
||||||
|
let mut description = String::new();
|
||||||
|
let mut url = String::new();
|
||||||
|
let mut _maintainer = String::new();
|
||||||
|
let mut depends: Vec<String> = Vec::new();
|
||||||
|
let mut optional_deps: Vec<String> = Vec::new();
|
||||||
|
let source_urls: Vec<String>;
|
||||||
|
|
||||||
|
// --- Extract comment metadata ---
|
||||||
|
for line in content.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if let Some(desc) = trimmed.strip_prefix("# Description:") {
|
||||||
|
description = desc.trim().to_string();
|
||||||
|
} else if let Some(u) = trimmed.strip_prefix("# URL:") {
|
||||||
|
url = u.trim().to_string();
|
||||||
|
} else if let Some(m) = trimmed.strip_prefix("# Maintainer:") {
|
||||||
|
_maintainer = m.trim().to_string();
|
||||||
|
} else if let Some(d) = trimmed.strip_prefix("# Depends on:") {
|
||||||
|
depends = d
|
||||||
|
.split([',', ' '])
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
} else if let Some(o) = trimmed.strip_prefix("# Optional:") {
|
||||||
|
optional_deps = o
|
||||||
|
.split([',', ' '])
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Extract variable assignments ---
|
||||||
|
// name=value (no quotes or with quotes)
|
||||||
|
let var_re = Regex::new(r#"^(\w+)=["']?([^"'\n]*)["']?\s*$"#).unwrap();
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if let Some(caps) = var_re.captures(trimmed) {
|
||||||
|
let key = caps.get(1).unwrap().as_str();
|
||||||
|
let val = caps.get(2).unwrap().as_str().trim();
|
||||||
|
match key {
|
||||||
|
"name" => name = val.to_string(),
|
||||||
|
"version" => version = val.to_string(),
|
||||||
|
"release" => release = val.parse().unwrap_or(1),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Extract source array ---
|
||||||
|
source_urls = extract_source_array(content);
|
||||||
|
|
||||||
|
// --- Extract build function ---
|
||||||
|
let build_body = extract_build_function(content);
|
||||||
|
|
||||||
|
// --- Parse build commands from the build function ---
|
||||||
|
let (configure_cmd, make_cmd, install_cmd, prepare_cmd) =
|
||||||
|
parse_build_commands(&build_body, &name, &version);
|
||||||
|
|
||||||
|
// --- Expand source URL (replace $name, $version, ${name}, ${version}) ---
|
||||||
|
let primary_source = source_urls
|
||||||
|
.first()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let expanded_url = expand_crux_vars(&primary_source, &name, &version);
|
||||||
|
|
||||||
|
// Convert to template URL (replace version back with ${version})
|
||||||
|
let template_url = expanded_url.replace(&version, "${version}");
|
||||||
|
|
||||||
|
// --- Build the PackageDefinition ---
|
||||||
|
let mut optional_map = HashMap::new();
|
||||||
|
for opt in &optional_deps {
|
||||||
|
optional_map.insert(
|
||||||
|
opt.clone(),
|
||||||
|
OptionalDep {
|
||||||
|
description: format!("Optional: {} support", opt),
|
||||||
|
default: false,
|
||||||
|
deps: vec![opt.clone()],
|
||||||
|
build_deps: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PackageDefinition {
|
||||||
|
package: PackageMetadata {
|
||||||
|
name: name.clone(),
|
||||||
|
version: version.clone(),
|
||||||
|
description,
|
||||||
|
url,
|
||||||
|
license: String::new(), // CRUX doesn't track license in Pkgfile
|
||||||
|
epoch: 0,
|
||||||
|
revision: release,
|
||||||
|
},
|
||||||
|
source: SourceInfo {
|
||||||
|
url: template_url,
|
||||||
|
sha256: "FIXME_CHECKSUM".repeat(4)[..64].to_string(), // Placeholder
|
||||||
|
patches: vec![],
|
||||||
|
},
|
||||||
|
dependencies: Dependencies {
|
||||||
|
run: depends,
|
||||||
|
build: vec![], // CRUX doesn't distinguish build vs runtime deps
|
||||||
|
optional: optional_map,
|
||||||
|
},
|
||||||
|
build: BuildInstructions {
|
||||||
|
configure: configure_cmd,
|
||||||
|
make: make_cmd,
|
||||||
|
install: install_cmd,
|
||||||
|
prepare: prepare_cmd,
|
||||||
|
post_install: String::new(),
|
||||||
|
check: String::new(),
|
||||||
|
flags: BuildFlags::default(),
|
||||||
|
system: detect_build_system(&build_body),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the source=() array from a Pkgfile.
|
||||||
|
/// Handles single-line and multi-line arrays.
|
||||||
|
fn extract_source_array(content: &str) -> Vec<String> {
|
||||||
|
let mut sources = Vec::new();
|
||||||
|
let mut in_source = false;
|
||||||
|
let mut source_text = String::new();
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
if trimmed.starts_with("source=") || trimmed.starts_with("source =") {
|
||||||
|
in_source = true;
|
||||||
|
// Get everything after source=(
|
||||||
|
let after_eq = trimmed.splitn(2, '=').nth(1).unwrap_or("");
|
||||||
|
source_text.push_str(after_eq);
|
||||||
|
if after_eq.contains(')') {
|
||||||
|
in_source = false;
|
||||||
|
}
|
||||||
|
} else if in_source {
|
||||||
|
source_text.push(' ');
|
||||||
|
source_text.push_str(trimmed);
|
||||||
|
if trimmed.contains(')') {
|
||||||
|
in_source = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip parens and parse individual URLs
|
||||||
|
let cleaned = source_text
|
||||||
|
.trim_start_matches('(')
|
||||||
|
.trim_end_matches(')')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
for url in cleaned.split_whitespace() {
|
||||||
|
let u = url.trim().to_string();
|
||||||
|
if !u.is_empty() {
|
||||||
|
sources.push(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sources
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the build() function body from a Pkgfile.
|
||||||
|
fn extract_build_function(content: &str) -> String {
|
||||||
|
let mut in_build = false;
|
||||||
|
let mut brace_depth = 0;
|
||||||
|
let mut body = String::new();
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
if !in_build && (trimmed.starts_with("build()") || trimmed.starts_with("build ()")) {
|
||||||
|
in_build = true;
|
||||||
|
// Count braces on this line
|
||||||
|
for ch in trimmed.chars() {
|
||||||
|
match ch {
|
||||||
|
'{' => brace_depth += 1,
|
||||||
|
'}' => brace_depth -= 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if in_build {
|
||||||
|
for ch in trimmed.chars() {
|
||||||
|
match ch {
|
||||||
|
'{' => brace_depth += 1,
|
||||||
|
'}' => brace_depth -= 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if brace_depth <= 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.push_str(trimmed);
|
||||||
|
body.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse configure/make/install commands from the build function body.
|
||||||
|
fn parse_build_commands(
|
||||||
|
body: &str,
|
||||||
|
_name: &str,
|
||||||
|
_version: &str,
|
||||||
|
) -> (String, String, String, String) {
|
||||||
|
let mut configure = String::new();
|
||||||
|
let mut make = String::new();
|
||||||
|
let mut install = String::new();
|
||||||
|
let mut prepare = String::new();
|
||||||
|
|
||||||
|
let mut continuation = String::new();
|
||||||
|
|
||||||
|
for line in body.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
// Handle line continuations
|
||||||
|
if trimmed.ends_with('\\') {
|
||||||
|
continuation.push_str(&trimmed[..trimmed.len() - 1]);
|
||||||
|
continuation.push(' ');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let full_line = if !continuation.is_empty() {
|
||||||
|
let result = format!("{}{}", continuation, trimmed);
|
||||||
|
continuation.clear();
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
trimmed.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let fl = full_line.trim();
|
||||||
|
|
||||||
|
// Detect configure-like commands
|
||||||
|
if fl.starts_with("./configure")
|
||||||
|
|| fl.starts_with("../configure")
|
||||||
|
|| fl.starts_with("cmake")
|
||||||
|
|| fl.starts_with("meson setup")
|
||||||
|
|| fl.starts_with("meson ")
|
||||||
|
{
|
||||||
|
// Replace $PKG with ${PKG} for dpack template
|
||||||
|
configure = fl.replace("$PKG", "${PKG}");
|
||||||
|
}
|
||||||
|
// Detect install commands
|
||||||
|
else if fl.contains("DESTDIR=") && fl.contains("install")
|
||||||
|
|| fl.starts_with("make install")
|
||||||
|
|| fl.starts_with("make DESTDIR")
|
||||||
|
|| fl.starts_with("meson install")
|
||||||
|
|| fl.starts_with("DESTDIR=")
|
||||||
|
|| fl.starts_with("ninja -C") && fl.contains("install")
|
||||||
|
{
|
||||||
|
install = fl.replace("$PKG", "${PKG}");
|
||||||
|
}
|
||||||
|
// Detect make/build commands
|
||||||
|
else if fl == "make" || fl.starts_with("make -") || fl.starts_with("make ") && !fl.contains("install") {
|
||||||
|
make = fl.to_string();
|
||||||
|
} else if fl.starts_with("meson compile") || fl.starts_with("ninja") && !fl.contains("install") {
|
||||||
|
make = fl.to_string();
|
||||||
|
}
|
||||||
|
// Detect prepare steps (patching, sed, autoreconf)
|
||||||
|
else if fl.starts_with("sed ") || fl.starts_with("patch ") || fl.starts_with("autoreconf") {
|
||||||
|
if !prepare.is_empty() {
|
||||||
|
prepare.push_str(" && ");
|
||||||
|
}
|
||||||
|
prepare.push_str(fl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default make if not found
|
||||||
|
if make.is_empty() {
|
||||||
|
make = "make".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default install if not found
|
||||||
|
if install.is_empty() {
|
||||||
|
install = "make DESTDIR=${PKG} install".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
(configure, make, install, prepare)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand CRUX variables in a string ($name, $version, ${name}, ${version}).
|
||||||
|
fn expand_crux_vars(s: &str, name: &str, version: &str) -> String {
|
||||||
|
s.replace("$name", name)
|
||||||
|
.replace("${name}", name)
|
||||||
|
.replace("$version", version)
|
||||||
|
.replace("${version}", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detect the build system from the build function body.
|
||||||
|
fn detect_build_system(body: &str) -> BuildSystem {
|
||||||
|
if body.contains("meson setup") || body.contains("meson compile") {
|
||||||
|
BuildSystem::Meson
|
||||||
|
} else if body.contains("cmake") || body.contains("CMakeLists") {
|
||||||
|
BuildSystem::Cmake
|
||||||
|
} else if body.contains("cargo build") || body.contains("cargo install") {
|
||||||
|
BuildSystem::Cargo
|
||||||
|
} else if body.contains("./configure") || body.contains("../configure") {
|
||||||
|
BuildSystem::Autotools
|
||||||
|
} else {
|
||||||
|
BuildSystem::Custom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SAMPLE_PKGFILE: &str = r#"# Description: Compression library
|
||||||
|
# URL: https://zlib.net/
|
||||||
|
# Maintainer: Danny, danny@example.com
|
||||||
|
# Depends on: gcc
|
||||||
|
|
||||||
|
name=zlib
|
||||||
|
version=1.3.1
|
||||||
|
release=1
|
||||||
|
|
||||||
|
source=(https://zlib.net/$name-$version.tar.xz)
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd $name-$version
|
||||||
|
|
||||||
|
./configure --prefix=/usr
|
||||||
|
|
||||||
|
make
|
||||||
|
make DESTDIR=$PKG install
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_simple_pkgfile() {
|
||||||
|
let pkg = parse_pkgfile(SAMPLE_PKGFILE).unwrap();
|
||||||
|
assert_eq!(pkg.package.name, "zlib");
|
||||||
|
assert_eq!(pkg.package.version, "1.3.1");
|
||||||
|
assert_eq!(pkg.package.description, "Compression library");
|
||||||
|
assert_eq!(pkg.package.url, "https://zlib.net/");
|
||||||
|
assert_eq!(pkg.dependencies.run, vec!["gcc"]);
|
||||||
|
assert_eq!(pkg.build.configure, "./configure --prefix=/usr");
|
||||||
|
assert_eq!(pkg.build.install, "make DESTDIR=${PKG} install");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_source_url_expansion() {
|
||||||
|
let pkg = parse_pkgfile(SAMPLE_PKGFILE).unwrap();
|
||||||
|
let expanded = pkg.expanded_source_url();
|
||||||
|
assert_eq!(expanded, "https://zlib.net/zlib-1.3.1.tar.xz");
|
||||||
|
}
|
||||||
|
|
||||||
|
const COMPLEX_PKGFILE: &str = r#"# Description: A tool for transfering files with URL syntax
|
||||||
|
# URL: https://curl.haxx.se
|
||||||
|
# Maintainer: CRUX System Team
|
||||||
|
# Depends on: libnghttp2 openssl zstd
|
||||||
|
# Optional: brotli c-ares libpsl
|
||||||
|
|
||||||
|
name=curl
|
||||||
|
version=8.19.0
|
||||||
|
release=1
|
||||||
|
|
||||||
|
source=(https://curl.haxx.se/download/$name-$version.tar.xz)
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd $name-$version
|
||||||
|
|
||||||
|
sed -i 's|/usr/share/curl|/etc/ssl/certs|' lib/url.c
|
||||||
|
|
||||||
|
./configure \
|
||||||
|
--prefix=/usr \
|
||||||
|
--enable-ipv6 \
|
||||||
|
--with-openssl \
|
||||||
|
--with-nghttp2 \
|
||||||
|
--disable-ldap
|
||||||
|
|
||||||
|
make
|
||||||
|
make DESTDIR=$PKG install
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_complex_pkgfile() {
|
||||||
|
let pkg = parse_pkgfile(COMPLEX_PKGFILE).unwrap();
|
||||||
|
assert_eq!(pkg.package.name, "curl");
|
||||||
|
assert_eq!(pkg.package.version, "8.19.0");
|
||||||
|
assert_eq!(
|
||||||
|
pkg.dependencies.run,
|
||||||
|
vec!["libnghttp2", "openssl", "zstd"]
|
||||||
|
);
|
||||||
|
assert!(pkg.dependencies.optional.contains_key("brotli"));
|
||||||
|
assert!(pkg.dependencies.optional.contains_key("c-ares"));
|
||||||
|
assert!(pkg.build.configure.contains("--with-openssl"));
|
||||||
|
assert!(pkg.build.prepare.contains("sed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detect_meson_build_system() {
|
||||||
|
let body = "meson setup build --prefix=/usr\nmeson compile -C build\nDESTDIR=$PKG meson install -C build";
|
||||||
|
assert_eq!(detect_build_system(body), BuildSystem::Meson);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detect_cmake_build_system() {
|
||||||
|
let body = "cmake -B build -DCMAKE_INSTALL_PREFIX=/usr\nmake -C build\nmake -C build DESTDIR=$PKG install";
|
||||||
|
assert_eq!(detect_build_system(body), BuildSystem::Cmake);
|
||||||
|
}
|
||||||
|
}
|
||||||
570
src/dpack/src/converter/gentoo.rs
Normal file
570
src/dpack/src/converter/gentoo.rs
Normal file
@@ -0,0 +1,570 @@
|
|||||||
|
//! Gentoo ebuild converter.
|
||||||
|
//!
|
||||||
|
//! Parses Gentoo `.ebuild` files and emits dpack `PackageDefinition` TOML.
|
||||||
|
//! This is a best-effort converter — ebuilds can be extraordinarily complex
|
||||||
|
//! (eclasses, slot deps, multilib, conditional USE deps). We handle the
|
||||||
|
//! common 80% and flag the rest for manual review.
|
||||||
|
//!
|
||||||
|
//! What we extract:
|
||||||
|
//! - DESCRIPTION, HOMEPAGE, SRC_URI, LICENSE
|
||||||
|
//! - IUSE (USE flags → dpack optional deps)
|
||||||
|
//! - RDEPEND, DEPEND, BDEPEND (dependencies)
|
||||||
|
//! - src_configure/src_compile/src_install phase functions
|
||||||
|
//!
|
||||||
|
//! What requires manual review:
|
||||||
|
//! - Complex eclass-dependent logic
|
||||||
|
//! - Multilib builds (inherit multilib-minimal)
|
||||||
|
//! - Slot dependencies and subslots
|
||||||
|
//! - REQUIRED_USE constraints
|
||||||
|
//! - Conditional dependency atoms with nested logic
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use regex::Regex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::config::package::*;
|
||||||
|
|
||||||
|
/// Warnings generated during conversion that require manual review.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ConversionWarnings {
|
||||||
|
pub warnings: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConversionWarnings {
|
||||||
|
fn warn(&mut self, msg: impl Into<String>) {
|
||||||
|
self.warnings.push(msg.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a Gentoo ebuild string into a dpack PackageDefinition.
|
||||||
|
///
|
||||||
|
/// The filename is needed to extract name and version (Gentoo convention:
|
||||||
|
/// `<name>-<version>.ebuild`).
|
||||||
|
pub fn parse_ebuild(content: &str, filename: &str) -> Result<PackageDefinition> {
|
||||||
|
let mut warnings = ConversionWarnings::default();
|
||||||
|
|
||||||
|
// Extract name and version from filename
|
||||||
|
// Format: <name>-<version>.ebuild (e.g., curl-8.19.0.ebuild)
|
||||||
|
let (name, version) = parse_ebuild_filename(filename)?;
|
||||||
|
|
||||||
|
// Extract simple variables
|
||||||
|
let description = extract_var(content, "DESCRIPTION").unwrap_or_default();
|
||||||
|
let homepage = extract_var(content, "HOMEPAGE").unwrap_or_default();
|
||||||
|
let license = extract_var(content, "LICENSE").unwrap_or_default();
|
||||||
|
let src_uri = extract_var(content, "SRC_URI").unwrap_or_default();
|
||||||
|
let iuse = extract_var(content, "IUSE").unwrap_or_default();
|
||||||
|
|
||||||
|
// Check for eclasses that need manual review
|
||||||
|
let inherits = extract_var(content, "inherit").unwrap_or_default();
|
||||||
|
if inherits.contains("multilib-minimal") || inherits.contains("meson-multilib") {
|
||||||
|
warnings.warn("Package uses multilib — may need separate 32-bit build definitions");
|
||||||
|
}
|
||||||
|
if inherits.contains("cargo") {
|
||||||
|
warnings.warn("Package uses Rust cargo eclass — Rust crate deps may need manual handling");
|
||||||
|
}
|
||||||
|
if inherits.contains("git-r3") {
|
||||||
|
warnings.warn("Package fetches from git — needs a release tarball URL instead");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse USE flags into optional dependencies
|
||||||
|
let optional_deps = parse_use_flags(&iuse);
|
||||||
|
|
||||||
|
// Parse dependencies
|
||||||
|
let rdepend = extract_multiline_var(content, "RDEPEND");
|
||||||
|
let depend = extract_multiline_var(content, "DEPEND");
|
||||||
|
let bdepend = extract_multiline_var(content, "BDEPEND");
|
||||||
|
|
||||||
|
let run_deps = parse_dep_atoms(&rdepend, &mut warnings);
|
||||||
|
let build_deps = parse_dep_atoms(&bdepend, &mut warnings);
|
||||||
|
|
||||||
|
// If DEPEND is different from RDEPEND, merge its unique entries into build_deps
|
||||||
|
let depend_parsed = parse_dep_atoms(&depend, &mut warnings);
|
||||||
|
let extra_build_deps: Vec<String> = depend_parsed
|
||||||
|
.into_iter()
|
||||||
|
.filter(|d| !run_deps.contains(d) && !build_deps.contains(d))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut all_build_deps = build_deps;
|
||||||
|
all_build_deps.extend(extra_build_deps);
|
||||||
|
|
||||||
|
// Parse build phase functions
|
||||||
|
let configure_cmd = extract_phase_function(content, "src_configure");
|
||||||
|
let compile_cmd = extract_phase_function(content, "src_compile");
|
||||||
|
let install_cmd = extract_phase_function(content, "src_install");
|
||||||
|
let prepare_cmd = extract_phase_function(content, "src_prepare");
|
||||||
|
let test_cmd = extract_phase_function(content, "src_test");
|
||||||
|
|
||||||
|
// Determine build system from eclasses and configure commands
|
||||||
|
let build_system = if inherits.contains("meson") {
|
||||||
|
BuildSystem::Meson
|
||||||
|
} else if inherits.contains("cmake") {
|
||||||
|
BuildSystem::Cmake
|
||||||
|
} else if inherits.contains("cargo") {
|
||||||
|
BuildSystem::Cargo
|
||||||
|
} else if inherits.contains("autotools") || configure_cmd.contains("econf") {
|
||||||
|
BuildSystem::Autotools
|
||||||
|
} else {
|
||||||
|
BuildSystem::Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert econf to ./configure
|
||||||
|
let configure_converted = convert_phase_to_commands(&configure_cmd, &build_system);
|
||||||
|
let make_converted = convert_phase_to_commands(&compile_cmd, &build_system);
|
||||||
|
let install_converted = convert_phase_to_commands(&install_cmd, &build_system);
|
||||||
|
let prepare_converted = convert_phase_to_commands(&prepare_cmd, &build_system);
|
||||||
|
let check_converted = convert_phase_to_commands(&test_cmd, &build_system);
|
||||||
|
|
||||||
|
// Parse SRC_URI into a clean URL
|
||||||
|
let source_url = parse_src_uri(&src_uri, &name, &version);
|
||||||
|
|
||||||
|
// Check REQUIRED_USE for constraints
|
||||||
|
let required_use = extract_multiline_var(content, "REQUIRED_USE");
|
||||||
|
if !required_use.is_empty() {
|
||||||
|
warnings.warn(format!(
|
||||||
|
"REQUIRED_USE constraints exist — validate feature combinations: {}",
|
||||||
|
required_use.chars().take(200).collect::<String>()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the PackageDefinition
|
||||||
|
let mut pkg = PackageDefinition {
|
||||||
|
package: PackageMetadata {
|
||||||
|
name: name.clone(),
|
||||||
|
version: version.clone(),
|
||||||
|
description,
|
||||||
|
url: homepage,
|
||||||
|
license,
|
||||||
|
epoch: 0,
|
||||||
|
revision: 1,
|
||||||
|
},
|
||||||
|
source: SourceInfo {
|
||||||
|
url: source_url,
|
||||||
|
sha256: "FIXME_CHECKSUM".repeat(4)[..64].to_string(),
|
||||||
|
patches: vec![],
|
||||||
|
},
|
||||||
|
dependencies: Dependencies {
|
||||||
|
run: run_deps,
|
||||||
|
build: all_build_deps,
|
||||||
|
optional: optional_deps,
|
||||||
|
},
|
||||||
|
build: BuildInstructions {
|
||||||
|
configure: configure_converted,
|
||||||
|
make: make_converted,
|
||||||
|
install: if install_converted.is_empty() {
|
||||||
|
"make DESTDIR=${PKG} install".to_string()
|
||||||
|
} else {
|
||||||
|
install_converted
|
||||||
|
},
|
||||||
|
prepare: prepare_converted,
|
||||||
|
post_install: String::new(),
|
||||||
|
check: check_converted,
|
||||||
|
flags: BuildFlags::default(),
|
||||||
|
system: build_system,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Append warnings as comments in the TOML output
|
||||||
|
// We do this by adding a note to the description
|
||||||
|
if !warnings.warnings.is_empty() {
|
||||||
|
let warning_text = warnings
|
||||||
|
.warnings
|
||||||
|
.iter()
|
||||||
|
.map(|w| format!(" # REVIEW: {}", w))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
pkg.package.description = format!(
|
||||||
|
"{}\n# --- Conversion warnings (manual review needed) ---\n{}",
|
||||||
|
pkg.package.description, warning_text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse ebuild filename into (name, version).
|
||||||
|
/// Convention: `<name>-<version>.ebuild`
|
||||||
|
fn parse_ebuild_filename(filename: &str) -> Result<(String, String)> {
|
||||||
|
let stem = filename.strip_suffix(".ebuild").unwrap_or(filename);
|
||||||
|
|
||||||
|
// Find the version part: starts at the first `-` followed by a digit
|
||||||
|
let re = Regex::new(r"^(.+?)-(\d.*)$").unwrap();
|
||||||
|
|
||||||
|
if let Some(caps) = re.captures(stem) {
|
||||||
|
let name = caps.get(1).unwrap().as_str().to_string();
|
||||||
|
let version = caps.get(2).unwrap().as_str().to_string();
|
||||||
|
Ok((name, version))
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("Cannot parse name/version from ebuild filename: {}", filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a single-line variable assignment from ebuild content.
|
||||||
|
fn extract_var(content: &str, var_name: &str) -> Option<String> {
|
||||||
|
let re = Regex::new(&format!(
|
||||||
|
r#"(?m)^{}=["']([^"']*?)["']\s*$"#,
|
||||||
|
regex::escape(var_name)
|
||||||
|
))
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
re.captures(content)
|
||||||
|
.and_then(|caps| caps.get(1))
|
||||||
|
.map(|m| m.as_str().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a multi-line variable (handles heredoc-style and continuation).
|
||||||
|
fn extract_multiline_var(content: &str, var_name: &str) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut in_var = false;
|
||||||
|
let mut quote_char = '"';
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
if !in_var {
|
||||||
|
// Match: VARNAME="value or VARNAME='value
|
||||||
|
let pattern = format!("{}=", var_name);
|
||||||
|
if trimmed.starts_with(&pattern) {
|
||||||
|
let after_eq = &trimmed[pattern.len()..];
|
||||||
|
if after_eq.starts_with('"') {
|
||||||
|
quote_char = '"';
|
||||||
|
let inner = &after_eq[1..];
|
||||||
|
if inner.ends_with('"') {
|
||||||
|
// Single-line
|
||||||
|
result = inner[..inner.len() - 1].to_string();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.push_str(inner);
|
||||||
|
result.push('\n');
|
||||||
|
in_var = true;
|
||||||
|
} else if after_eq.starts_with('\'') {
|
||||||
|
quote_char = '\'';
|
||||||
|
let inner = &after_eq[1..];
|
||||||
|
if inner.ends_with('\'') {
|
||||||
|
result = inner[..inner.len() - 1].to_string();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.push_str(inner);
|
||||||
|
result.push('\n');
|
||||||
|
in_var = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let close = format!("{}", quote_char);
|
||||||
|
if trimmed.ends_with(quote_char) || trimmed == &close {
|
||||||
|
let end = if trimmed.ends_with(quote_char) {
|
||||||
|
&trimmed[..trimmed.len() - 1]
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
result.push_str(end);
|
||||||
|
in_var = false;
|
||||||
|
} else {
|
||||||
|
result.push_str(trimmed);
|
||||||
|
result.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.trim().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse IUSE string into optional dependency map.
|
||||||
|
fn parse_use_flags(iuse: &str) -> HashMap<String, OptionalDep> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
for flag in iuse.split_whitespace() {
|
||||||
|
let (name, default) = if let Some(stripped) = flag.strip_prefix('+') {
|
||||||
|
(stripped.to_string(), true)
|
||||||
|
} else if let Some(stripped) = flag.strip_prefix('-') {
|
||||||
|
(stripped.to_string(), false)
|
||||||
|
} else {
|
||||||
|
(flag.to_string(), false)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Skip internal/system flags
|
||||||
|
if name.starts_with("cpu_flags_")
|
||||||
|
|| name.starts_with("video_cards_")
|
||||||
|
|| name.starts_with("python_")
|
||||||
|
|| name == "test"
|
||||||
|
|| name == "doc"
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
name.clone(),
|
||||||
|
OptionalDep {
|
||||||
|
description: format!("Enable {} support", name),
|
||||||
|
default,
|
||||||
|
deps: vec![], // Would need dep analysis to fill
|
||||||
|
build_deps: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse Gentoo dependency atoms into a flat list of package names.
|
||||||
|
///
|
||||||
|
/// Handles:
|
||||||
|
/// - Simple atoms: `dev-libs/openssl`
|
||||||
|
/// - Versioned: `>=dev-libs/openssl-1.0.2`
|
||||||
|
/// - USE-conditional: `ssl? ( dev-libs/openssl )`
|
||||||
|
/// - Slot: `dev-libs/openssl:0=`
|
||||||
|
///
|
||||||
|
/// Strips category prefixes and version constraints for dpack format.
|
||||||
|
fn parse_dep_atoms(deps: &str, warnings: &mut ConversionWarnings) -> Vec<String> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let atom_re = Regex::new(
|
||||||
|
r"(?:>=|<=|~|=)?([a-zA-Z0-9_-]+/[a-zA-Z0-9_.+-]+?)(?:-\d[^\s\[\]:]*)?(?:\[.*?\])?(?::[\w/=*]*)?(?:\s|$)"
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
for caps in atom_re.captures_iter(deps) {
|
||||||
|
if let Some(m) = caps.get(1) {
|
||||||
|
let full_atom = m.as_str();
|
||||||
|
// Strip category prefix (e.g., "dev-libs/" -> "")
|
||||||
|
let pkg_name = full_atom
|
||||||
|
.rsplit('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or(full_atom)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Skip virtual packages and test-only deps
|
||||||
|
if full_atom.starts_with("virtual/") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result.contains(&pkg_name) {
|
||||||
|
result.push(pkg_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect complex constructs we can't fully parse
|
||||||
|
if deps.contains("^^") || deps.contains("||") {
|
||||||
|
warnings.warn("Complex dependency logic (^^ or ||) detected — manual review needed");
|
||||||
|
}
|
||||||
|
if deps.contains("${MULTILIB_USEDEP}") {
|
||||||
|
warnings.warn("Multilib dependencies detected — 32-bit builds may be needed");
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a phase function body (e.g., src_configure, src_install).
|
||||||
|
fn extract_phase_function(content: &str, func_name: &str) -> String {
|
||||||
|
let mut in_func = false;
|
||||||
|
let mut brace_depth = 0;
|
||||||
|
let mut body = String::new();
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
if !in_func {
|
||||||
|
// Match: func_name() { or func_name () {
|
||||||
|
if trimmed.starts_with(func_name) && trimmed.contains('{') {
|
||||||
|
in_func = true;
|
||||||
|
for ch in trimmed.chars() {
|
||||||
|
match ch {
|
||||||
|
'{' => brace_depth += 1,
|
||||||
|
'}' => brace_depth -= 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if brace_depth <= 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if in_func {
|
||||||
|
for ch in trimmed.chars() {
|
||||||
|
match ch {
|
||||||
|
'{' => brace_depth += 1,
|
||||||
|
'}' => {
|
||||||
|
brace_depth -= 1;
|
||||||
|
if brace_depth <= 0 {
|
||||||
|
return body.trim().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.push_str(trimmed);
|
||||||
|
body.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.trim().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert Gentoo eclass helper calls to plain shell commands.
|
||||||
|
fn convert_phase_to_commands(body: &str, _build_system: &BuildSystem) -> String {
|
||||||
|
if body.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = body.to_string();
|
||||||
|
|
||||||
|
// Replace common Gentoo helpers
|
||||||
|
result = result.replace("econf ", "./configure ");
|
||||||
|
result = result.replace("econf\n", "./configure\n");
|
||||||
|
result = result.replace("emake ", "make ");
|
||||||
|
result = result.replace("emake\n", "make\n");
|
||||||
|
result = result.replace("${ED}", "${PKG}");
|
||||||
|
result = result.replace("${D}", "${PKG}");
|
||||||
|
result = result.replace("${FILESDIR}", "./files");
|
||||||
|
result = result.replace("${WORKDIR}", ".");
|
||||||
|
result = result.replace("${S}", ".");
|
||||||
|
result = result.replace("${P}", "${name}-${version}");
|
||||||
|
result = result.replace("${PV}", "${version}");
|
||||||
|
result = result.replace("${PN}", "${name}");
|
||||||
|
|
||||||
|
// Replace einstall
|
||||||
|
result = result.replace("einstall", "make DESTDIR=${PKG} install");
|
||||||
|
|
||||||
|
// Remove Gentoo-specific calls that have no equivalent
|
||||||
|
let remove_patterns = [
|
||||||
|
"default",
|
||||||
|
"eapply_user",
|
||||||
|
"multilib_src_configure",
|
||||||
|
"multilib_src_compile",
|
||||||
|
"multilib_src_install",
|
||||||
|
];
|
||||||
|
for pattern in &remove_patterns {
|
||||||
|
result = result
|
||||||
|
.lines()
|
||||||
|
.filter(|l| !l.trim().starts_with(pattern))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.trim().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse SRC_URI into a clean download URL.
|
||||||
|
fn parse_src_uri(src_uri: &str, name: &str, version: &str) -> String {
|
||||||
|
// SRC_URI can have multiple entries, redirects, and mirror:// prefixes
|
||||||
|
// Take the first real URL
|
||||||
|
for token in src_uri.split_whitespace() {
|
||||||
|
if token.starts_with("http://") || token.starts_with("https://") || token.starts_with("mirror://") {
|
||||||
|
let url = token
|
||||||
|
.replace("mirror://sourceforge", "https://downloads.sourceforge.net")
|
||||||
|
.replace("mirror://gnu", "https://ftp.gnu.org/gnu")
|
||||||
|
.replace("mirror://gentoo", "https://distfiles.gentoo.org/distfiles");
|
||||||
|
|
||||||
|
// Replace ${P}, ${PV}, ${PN} with template vars
|
||||||
|
let templated = url
|
||||||
|
.replace(&format!("{}-{}", name, version), "${name}-${version}")
|
||||||
|
.replace(version, "${version}")
|
||||||
|
.replace(name, "${name}");
|
||||||
|
|
||||||
|
return templated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no URL found, return a placeholder
|
||||||
|
format!("https://FIXME/{}-{}.tar.xz", name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_ebuild_filename() {
|
||||||
|
let (name, version) = parse_ebuild_filename("curl-8.19.0.ebuild").unwrap();
|
||||||
|
assert_eq!(name, "curl");
|
||||||
|
assert_eq!(version, "8.19.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_ebuild_filename_complex() {
|
||||||
|
let (name, version) = parse_ebuild_filename("qt6-base-6.8.0.ebuild").unwrap();
|
||||||
|
assert_eq!(name, "qt6-base");
|
||||||
|
assert_eq!(version, "6.8.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIMPLE_EBUILD: &str = r#"
|
||||||
|
EAPI=8
|
||||||
|
|
||||||
|
DESCRIPTION="Standard (de)compression library"
|
||||||
|
HOMEPAGE="https://zlib.net/"
|
||||||
|
SRC_URI="https://zlib.net/zlib-${PV}.tar.xz"
|
||||||
|
|
||||||
|
LICENSE="ZLIB"
|
||||||
|
SLOT="0/1"
|
||||||
|
KEYWORDS="~alpha amd64 arm arm64"
|
||||||
|
IUSE="minizip static-libs"
|
||||||
|
|
||||||
|
RDEPEND=""
|
||||||
|
DEPEND=""
|
||||||
|
|
||||||
|
src_configure() {
|
||||||
|
econf
|
||||||
|
}
|
||||||
|
|
||||||
|
src_install() {
|
||||||
|
emake DESTDIR="${D}" install
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_simple_ebuild() {
|
||||||
|
let pkg = parse_ebuild(SIMPLE_EBUILD, "zlib-1.3.2.ebuild").unwrap();
|
||||||
|
assert_eq!(pkg.package.name, "zlib");
|
||||||
|
assert_eq!(pkg.package.version, "1.3.2");
|
||||||
|
assert_eq!(pkg.package.description, "Standard (de)compression library");
|
||||||
|
assert_eq!(pkg.package.license, "ZLIB");
|
||||||
|
assert!(pkg.dependencies.optional.contains_key("minizip"));
|
||||||
|
assert!(pkg.dependencies.optional.contains_key("static-libs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_multiline_var() {
|
||||||
|
let content = r#"
|
||||||
|
RDEPEND="
|
||||||
|
dev-libs/openssl:=
|
||||||
|
>=net-libs/nghttp2-1.0
|
||||||
|
sys-libs/zlib
|
||||||
|
"
|
||||||
|
"#;
|
||||||
|
let result = extract_multiline_var(content, "RDEPEND");
|
||||||
|
assert!(result.contains("openssl"));
|
||||||
|
assert!(result.contains("nghttp2"));
|
||||||
|
assert!(result.contains("zlib"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_dep_atoms() {
|
||||||
|
let deps = ">=dev-libs/openssl-1.0.2:=[static-libs?] net-libs/nghttp2:= sys-libs/zlib";
|
||||||
|
let mut warnings = ConversionWarnings::default();
|
||||||
|
let result = parse_dep_atoms(deps, &mut warnings);
|
||||||
|
assert!(result.contains(&"openssl".to_string()));
|
||||||
|
assert!(result.contains(&"nghttp2".to_string()));
|
||||||
|
assert!(result.contains(&"zlib".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_use_flags() {
|
||||||
|
let iuse = "+http2 +quic brotli debug test doc";
|
||||||
|
let flags = parse_use_flags(iuse);
|
||||||
|
assert!(flags.get("http2").unwrap().default);
|
||||||
|
assert!(flags.get("quic").unwrap().default);
|
||||||
|
assert!(!flags.get("brotli").unwrap().default);
|
||||||
|
// test and doc should be filtered out
|
||||||
|
assert!(!flags.contains_key("test"));
|
||||||
|
assert!(!flags.contains_key("doc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_convert_phase_to_commands() {
|
||||||
|
let body = "econf --prefix=/usr\nemake\nemake DESTDIR=\"${D}\" install";
|
||||||
|
let result = convert_phase_to_commands(body, &BuildSystem::Autotools);
|
||||||
|
assert!(result.contains("./configure --prefix=/usr"));
|
||||||
|
assert!(result.contains("make DESTDIR=\"${PKG}\" install"));
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/dpack/src/converter/mod.rs
Normal file
34
src/dpack/src/converter/mod.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
//! Foreign package format converters.
|
||||||
|
//!
|
||||||
|
//! Converts CRUX Pkgfiles and Gentoo ebuilds to `.toml` dpack format.
|
||||||
|
//! Both converters are best-effort: they handle common patterns and flag
|
||||||
|
//! anything that requires manual review.
|
||||||
|
|
||||||
|
pub mod crux;
|
||||||
|
pub mod gentoo;
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Detect the format of a foreign package file and convert it.
|
||||||
|
pub fn convert_file(path: &Path) -> Result<String> {
|
||||||
|
let filename = path
|
||||||
|
.file_name()
|
||||||
|
.map(|f| f.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if filename == "Pkgfile" {
|
||||||
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
let pkg = crux::parse_pkgfile(&content)?;
|
||||||
|
pkg.to_toml()
|
||||||
|
} else if filename.ends_with(".ebuild") {
|
||||||
|
let content = std::fs::read_to_string(path)?;
|
||||||
|
let pkg = gentoo::parse_ebuild(&content, &filename)?;
|
||||||
|
pkg.to_toml()
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"Unknown package format: '{}'. Expected 'Pkgfile' or '*.ebuild'",
|
||||||
|
filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
329
src/dpack/src/db/mod.rs
Normal file
329
src/dpack/src/db/mod.rs
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
//! Installed package database.
|
||||||
|
//!
|
||||||
|
//! File-based database stored at `/var/lib/dpack/db/`. One TOML file per
|
||||||
|
//! installed package, tracking: name, version, installed files, dependencies,
|
||||||
|
//! features enabled, install timestamp, and link type (shared/static).
|
||||||
|
//!
|
||||||
|
//! The database is the source of truth for what's installed on the system.
|
||||||
|
//! It's used by the resolver (to skip already-installed packages), the
|
||||||
|
//! remove command (to know which files to delete), and the upgrade command
|
||||||
|
//! (to compare installed vs available versions).
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// A record of a single installed package.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct InstalledPackage {
|
||||||
|
/// Package name
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// Installed version
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
/// Package description (copied from definition at install time)
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
/// Runtime dependencies at time of installation
|
||||||
|
pub run_deps: Vec<String>,
|
||||||
|
|
||||||
|
/// Build dependencies used during installation
|
||||||
|
pub build_deps: Vec<String>,
|
||||||
|
|
||||||
|
/// Features that were enabled during build
|
||||||
|
pub features: Vec<String>,
|
||||||
|
|
||||||
|
/// All files installed by this package (absolute paths)
|
||||||
|
pub files: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// Install timestamp (seconds since epoch)
|
||||||
|
pub installed_at: u64,
|
||||||
|
|
||||||
|
/// Which repository this package came from
|
||||||
|
pub repo: String,
|
||||||
|
|
||||||
|
/// Size in bytes of all installed files
|
||||||
|
pub size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The package database — manages the collection of installed package records.
|
||||||
|
pub struct PackageDb {
|
||||||
|
/// Path to the database directory
|
||||||
|
db_dir: PathBuf,
|
||||||
|
|
||||||
|
/// In-memory cache of installed packages
|
||||||
|
cache: HashMap<String, InstalledPackage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackageDb {
|
||||||
|
/// Open or create the package database at the given directory.
|
||||||
|
pub fn open(db_dir: &Path) -> Result<Self> {
|
||||||
|
std::fs::create_dir_all(db_dir)
|
||||||
|
.with_context(|| format!("Failed to create db dir: {}", db_dir.display()))?;
|
||||||
|
|
||||||
|
let mut db = Self {
|
||||||
|
db_dir: db_dir.to_path_buf(),
|
||||||
|
cache: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.load_all()?;
|
||||||
|
Ok(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load all package records from disk into the cache.
|
||||||
|
fn load_all(&mut self) -> Result<()> {
|
||||||
|
self.cache.clear();
|
||||||
|
|
||||||
|
if !self.db_dir.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in std::fs::read_dir(&self.db_dir)
|
||||||
|
.with_context(|| format!("Failed to read db dir: {}", self.db_dir.display()))?
|
||||||
|
{
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.extension().map_or(false, |ext| ext == "toml") {
|
||||||
|
match self.load_one(&path) {
|
||||||
|
Ok(pkg) => {
|
||||||
|
self.cache.insert(pkg.name.clone(), pkg);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Skipping corrupt db entry {}: {}", path.display(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a single package record from a TOML file.
|
||||||
|
fn load_one(&self, path: &Path) -> Result<InstalledPackage> {
|
||||||
|
let content = std::fs::read_to_string(path)
|
||||||
|
.with_context(|| format!("Failed to read: {}", path.display()))?;
|
||||||
|
toml::from_str(&content).context("Failed to parse package db entry")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a newly installed package in the database.
|
||||||
|
pub fn register(&mut self, pkg: InstalledPackage) -> Result<()> {
|
||||||
|
let path = self.db_dir.join(format!("{}.toml", pkg.name));
|
||||||
|
let content = toml::to_string_pretty(&pkg)
|
||||||
|
.context("Failed to serialize package record")?;
|
||||||
|
std::fs::write(&path, content)
|
||||||
|
.with_context(|| format!("Failed to write db entry: {}", path.display()))?;
|
||||||
|
|
||||||
|
self.cache.insert(pkg.name.clone(), pkg);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a package record from the database.
|
||||||
|
pub fn unregister(&mut self, name: &str) -> Result<Option<InstalledPackage>> {
|
||||||
|
let path = self.db_dir.join(format!("{}.toml", name));
|
||||||
|
if path.exists() {
|
||||||
|
std::fs::remove_file(&path)
|
||||||
|
.with_context(|| format!("Failed to remove db entry: {}", path.display()))?;
|
||||||
|
}
|
||||||
|
Ok(self.cache.remove(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a package is installed.
|
||||||
|
pub fn is_installed(&self, name: &str) -> bool {
|
||||||
|
self.cache.contains_key(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the installed version of a package.
|
||||||
|
pub fn installed_version(&self, name: &str) -> Option<&str> {
|
||||||
|
self.cache.get(name).map(|p| p.version.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the full record of an installed package.
|
||||||
|
pub fn get(&self, name: &str) -> Option<&InstalledPackage> {
|
||||||
|
self.cache.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all installed packages.
|
||||||
|
pub fn list_all(&self) -> Vec<&InstalledPackage> {
|
||||||
|
let mut pkgs: Vec<_> = self.cache.values().collect();
|
||||||
|
pkgs.sort_by_key(|p| &p.name);
|
||||||
|
pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a map of all installed packages: name -> version.
|
||||||
|
/// Used by the dependency resolver.
|
||||||
|
pub fn installed_versions(&self) -> HashMap<String, String> {
|
||||||
|
self.cache
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.clone(), v.version.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find all packages that own a specific file.
|
||||||
|
pub fn who_owns(&self, file_path: &Path) -> Vec<String> {
|
||||||
|
self.cache
|
||||||
|
.values()
|
||||||
|
.filter(|pkg| pkg.files.iter().any(|f| f == file_path))
|
||||||
|
.map(|pkg| pkg.name.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find packages with file conflicts (files owned by multiple packages).
|
||||||
|
pub fn find_conflicts(&self) -> HashMap<PathBuf, Vec<String>> {
|
||||||
|
let mut file_owners: HashMap<PathBuf, Vec<String>> = HashMap::new();
|
||||||
|
|
||||||
|
for pkg in self.cache.values() {
|
||||||
|
for file in &pkg.files {
|
||||||
|
file_owners
|
||||||
|
.entry(file.clone())
|
||||||
|
.or_default()
|
||||||
|
.push(pkg.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return only files with multiple owners
|
||||||
|
file_owners
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(_, owners)| owners.len() > 1)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get total number of installed packages.
|
||||||
|
pub fn count(&self) -> usize {
|
||||||
|
self.cache.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get total disk usage of all installed packages.
|
||||||
|
pub fn total_size(&self) -> u64 {
|
||||||
|
self.cache.values().map(|p| p.size).sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
fn make_installed(name: &str, version: &str, files: Vec<&str>) -> InstalledPackage {
|
||||||
|
InstalledPackage {
|
||||||
|
name: name.to_string(),
|
||||||
|
version: version.to_string(),
|
||||||
|
description: format!("Test package {}", name),
|
||||||
|
run_deps: vec![],
|
||||||
|
build_deps: vec![],
|
||||||
|
features: vec![],
|
||||||
|
files: files.into_iter().map(PathBuf::from).collect(),
|
||||||
|
installed_at: SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs(),
|
||||||
|
repo: "core".to_string(),
|
||||||
|
size: 1024,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_register_and_get() {
|
||||||
|
let tmpdir = std::env::temp_dir().join("dpack-test-db-reg");
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
|
||||||
|
let mut db = PackageDb::open(&tmpdir).unwrap();
|
||||||
|
assert_eq!(db.count(), 0);
|
||||||
|
|
||||||
|
let pkg = make_installed("zlib", "1.3.1", vec!["/usr/lib/libz.so"]);
|
||||||
|
db.register(pkg).unwrap();
|
||||||
|
|
||||||
|
assert!(db.is_installed("zlib"));
|
||||||
|
assert_eq!(db.installed_version("zlib"), Some("1.3.1"));
|
||||||
|
assert_eq!(db.count(), 1);
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unregister() {
|
||||||
|
let tmpdir = std::env::temp_dir().join("dpack-test-db-unreg");
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
|
||||||
|
let mut db = PackageDb::open(&tmpdir).unwrap();
|
||||||
|
db.register(make_installed("zlib", "1.3.1", vec![])).unwrap();
|
||||||
|
|
||||||
|
assert!(db.is_installed("zlib"));
|
||||||
|
let removed = db.unregister("zlib").unwrap();
|
||||||
|
assert!(removed.is_some());
|
||||||
|
assert!(!db.is_installed("zlib"));
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_persistence() {
|
||||||
|
let tmpdir = std::env::temp_dir().join("dpack-test-db-persist");
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut db = PackageDb::open(&tmpdir).unwrap();
|
||||||
|
db.register(make_installed("zlib", "1.3.1", vec!["/usr/lib/libz.so"])).unwrap();
|
||||||
|
db.register(make_installed("gcc", "15.2.0", vec!["/usr/bin/gcc"])).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-open and verify data persisted
|
||||||
|
let db = PackageDb::open(&tmpdir).unwrap();
|
||||||
|
assert_eq!(db.count(), 2);
|
||||||
|
assert!(db.is_installed("zlib"));
|
||||||
|
assert!(db.is_installed("gcc"));
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_who_owns() {
|
||||||
|
let tmpdir = std::env::temp_dir().join("dpack-test-db-owns");
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
|
||||||
|
let mut db = PackageDb::open(&tmpdir).unwrap();
|
||||||
|
db.register(make_installed("zlib", "1.3.1", vec!["/usr/lib/libz.so"])).unwrap();
|
||||||
|
|
||||||
|
let owners = db.who_owns(Path::new("/usr/lib/libz.so"));
|
||||||
|
assert_eq!(owners, vec!["zlib"]);
|
||||||
|
|
||||||
|
let owners = db.who_owns(Path::new("/usr/lib/nonexistent.so"));
|
||||||
|
assert!(owners.is_empty());
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_conflicts() {
|
||||||
|
let tmpdir = std::env::temp_dir().join("dpack-test-db-conflicts");
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
|
||||||
|
let mut db = PackageDb::open(&tmpdir).unwrap();
|
||||||
|
db.register(make_installed("pkg-a", "1.0", vec!["/usr/lib/shared.so"])).unwrap();
|
||||||
|
db.register(make_installed("pkg-b", "2.0", vec!["/usr/lib/shared.so"])).unwrap();
|
||||||
|
|
||||||
|
let conflicts = db.find_conflicts();
|
||||||
|
assert!(conflicts.contains_key(&PathBuf::from("/usr/lib/shared.so")));
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_all_sorted() {
|
||||||
|
let tmpdir = std::env::temp_dir().join("dpack-test-db-list");
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
|
||||||
|
let mut db = PackageDb::open(&tmpdir).unwrap();
|
||||||
|
db.register(make_installed("zlib", "1.3.1", vec![])).unwrap();
|
||||||
|
db.register(make_installed("bash", "5.3", vec![])).unwrap();
|
||||||
|
db.register(make_installed("gcc", "15.2.0", vec![])).unwrap();
|
||||||
|
|
||||||
|
let all = db.list_all();
|
||||||
|
let names: Vec<&str> = all.iter().map(|p| p.name.as_str()).collect();
|
||||||
|
assert_eq!(names, vec!["bash", "gcc", "zlib"]); // sorted
|
||||||
|
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/dpack/src/lib.rs
Normal file
21
src/dpack/src/lib.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//! dpack library — core functionality for the DarkForge package manager.
|
||||||
|
//!
|
||||||
|
//! This crate provides:
|
||||||
|
//! - Package definition parsing (`config`)
|
||||||
|
//! - Dependency resolution (`resolver`)
|
||||||
|
//! - Build sandboxing (`sandbox`)
|
||||||
|
//! - Foreign format converters (`converter`)
|
||||||
|
//! - Installed package database (`db`)
|
||||||
|
//! - Build orchestration (`build`)
|
||||||
|
|
||||||
|
// Many public API items are not yet used from main.rs but will be
|
||||||
|
// consumed as later phases are implemented. Suppress dead_code warnings
|
||||||
|
// for the library crate.
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod resolver;
|
||||||
|
pub mod sandbox;
|
||||||
|
pub mod converter;
|
||||||
|
pub mod db;
|
||||||
|
pub mod build;
|
||||||
389
src/dpack/src/main.rs
Normal file
389
src/dpack/src/main.rs
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
//! dpack — DarkForge Linux Package Manager
|
||||||
|
//!
|
||||||
|
//! A source-based package manager inspired by CRUX's pkgutils and Gentoo's emerge.
|
||||||
|
//! Supports TOML package definitions, dependency resolution, sandboxed builds,
|
||||||
|
//! and converters for CRUX Pkgfiles and Gentoo ebuilds.
|
||||||
|
|
||||||
|
// Public API items in submodules are used across phases — suppress dead_code
|
||||||
|
// warnings for items not yet wired into CLI commands.
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod resolver;
|
||||||
|
mod sandbox;
|
||||||
|
mod converter;
|
||||||
|
mod db;
|
||||||
|
mod build;
|
||||||
|
|
||||||
|
use config::{DpackConfig, PackageDefinition};
|
||||||
|
use db::PackageDb;
|
||||||
|
use build::BuildOrchestrator;
|
||||||
|
|
||||||
|
/// DarkForge package manager
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "dpack")]
|
||||||
|
#[command(about = "DarkForge Linux package manager")]
|
||||||
|
#[command(version)]
|
||||||
|
struct Cli {
|
||||||
|
/// Path to dpack configuration file
|
||||||
|
#[arg(short, long, default_value = "/etc/dpack.conf")]
|
||||||
|
config: String,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
/// Install a package (resolve deps → build → install → update db)
|
||||||
|
Install {
|
||||||
|
/// Package name(s) to install
|
||||||
|
#[arg(required = true)]
|
||||||
|
packages: Vec<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Remove an installed package
|
||||||
|
Remove {
|
||||||
|
/// Package name(s) to remove
|
||||||
|
#[arg(required = true)]
|
||||||
|
packages: Vec<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Upgrade installed package(s) to latest version
|
||||||
|
Upgrade {
|
||||||
|
/// Package name(s) to upgrade, or empty for all
|
||||||
|
packages: Vec<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Search for packages in the repository
|
||||||
|
Search {
|
||||||
|
/// Search query
|
||||||
|
query: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Show information about a package
|
||||||
|
Info {
|
||||||
|
/// Package name
|
||||||
|
package: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// List installed packages
|
||||||
|
List,
|
||||||
|
|
||||||
|
/// Convert a foreign package definition to dpack format
|
||||||
|
Convert {
|
||||||
|
/// Path to the foreign package file (Pkgfile or .ebuild)
|
||||||
|
path: String,
|
||||||
|
|
||||||
|
/// Output path for the generated .toml file
|
||||||
|
#[arg(short, long)]
|
||||||
|
output: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Check for shared library conflicts
|
||||||
|
Check,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
let result = run(cli);
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
eprintln!("{}: {:#}", "error".red().bold(), e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(cli: Cli) -> Result<()> {
|
||||||
|
let config = if std::path::Path::new(&cli.config).exists() {
|
||||||
|
DpackConfig::from_file(std::path::Path::new(&cli.config))?
|
||||||
|
} else {
|
||||||
|
DpackConfig::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
match cli.command {
|
||||||
|
Commands::Install { packages } => {
|
||||||
|
let db = PackageDb::open(&config.paths.db_dir)?;
|
||||||
|
let mut orchestrator = BuildOrchestrator::new(config, db);
|
||||||
|
orchestrator.install(&packages)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::Remove { packages } => {
|
||||||
|
let mut db = PackageDb::open(&config.paths.db_dir)?;
|
||||||
|
|
||||||
|
// Load repos for reverse-dep checking
|
||||||
|
let mut all_repo_packages = std::collections::HashMap::new();
|
||||||
|
for repo in &config.repos {
|
||||||
|
let repo_pkgs = resolver::DependencyGraph::load_repo(&repo.path)?;
|
||||||
|
all_repo_packages.extend(repo_pkgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
let installed_names: std::collections::HashSet<String> =
|
||||||
|
db.list_all().iter().map(|p| p.name.clone()).collect();
|
||||||
|
|
||||||
|
for name in &packages {
|
||||||
|
// Check reverse dependencies before removing
|
||||||
|
let rdeps = resolver::reverse_deps(name, &all_repo_packages, &installed_names);
|
||||||
|
if !rdeps.is_empty() {
|
||||||
|
println!(
|
||||||
|
"{} '{}' is required by: {}",
|
||||||
|
"Warning:".yellow().bold(),
|
||||||
|
name,
|
||||||
|
rdeps.join(", ")
|
||||||
|
);
|
||||||
|
println!(" Removing it may break these packages.");
|
||||||
|
println!(" Proceeding anyway...");
|
||||||
|
}
|
||||||
|
|
||||||
|
match db.unregister(name)? {
|
||||||
|
Some(pkg) => {
|
||||||
|
// Remove installed files (in reverse order to clean dirs)
|
||||||
|
let mut files = pkg.files.clone();
|
||||||
|
files.sort();
|
||||||
|
files.reverse();
|
||||||
|
|
||||||
|
let mut removed_count = 0;
|
||||||
|
for file in &files {
|
||||||
|
if file.is_file() {
|
||||||
|
if std::fs::remove_file(file).is_ok() {
|
||||||
|
removed_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try to remove empty parent directories
|
||||||
|
for file in &files {
|
||||||
|
if let Some(parent) = file.parent() {
|
||||||
|
std::fs::remove_dir(parent).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"{} {} (removed {}/{} files)",
|
||||||
|
"Removed".green().bold(),
|
||||||
|
name,
|
||||||
|
removed_count,
|
||||||
|
files.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("{} '{}' is not installed", "Warning:".yellow().bold(), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::Upgrade { packages } => {
|
||||||
|
let db = PackageDb::open(&config.paths.db_dir)?;
|
||||||
|
|
||||||
|
// Load all repos to compare available vs installed versions
|
||||||
|
let mut all_repo_packages = std::collections::HashMap::new();
|
||||||
|
for repo in &config.repos {
|
||||||
|
let repo_pkgs = resolver::DependencyGraph::load_repo(&repo.path)?;
|
||||||
|
all_repo_packages.extend(repo_pkgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which packages to upgrade
|
||||||
|
let targets: Vec<String> = if packages.is_empty() {
|
||||||
|
// Upgrade all installed packages that have newer versions
|
||||||
|
db.list_all()
|
||||||
|
.iter()
|
||||||
|
.filter(|installed| {
|
||||||
|
all_repo_packages
|
||||||
|
.get(&installed.name)
|
||||||
|
.map_or(false, |repo_pkg| repo_pkg.package.version != installed.version)
|
||||||
|
})
|
||||||
|
.map(|p| p.name.clone())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
packages
|
||||||
|
};
|
||||||
|
|
||||||
|
if targets.is_empty() {
|
||||||
|
println!("{}", "All packages are up to date.".green());
|
||||||
|
} else {
|
||||||
|
println!("Packages to upgrade:");
|
||||||
|
for name in &targets {
|
||||||
|
let installed_ver = db.installed_version(name).unwrap_or("?");
|
||||||
|
let repo_ver = all_repo_packages
|
||||||
|
.get(name)
|
||||||
|
.map(|p| p.package.version.as_str())
|
||||||
|
.unwrap_or("?");
|
||||||
|
println!(" {} {} → {}", name.bold(), installed_ver.red(), repo_ver.green());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for shared library conflicts before proceeding
|
||||||
|
let _solib_map = resolver::solib::build_solib_map(&db);
|
||||||
|
|
||||||
|
for name in &targets {
|
||||||
|
// Warn about packages that depend on this one
|
||||||
|
let rdeps = resolver::reverse_deps(
|
||||||
|
name,
|
||||||
|
&all_repo_packages,
|
||||||
|
&db.list_all().iter().map(|p| p.name.clone()).collect(),
|
||||||
|
);
|
||||||
|
if !rdeps.is_empty() {
|
||||||
|
println!(
|
||||||
|
"\n{} {} is depended on by: {}",
|
||||||
|
"Note:".cyan().bold(),
|
||||||
|
name,
|
||||||
|
rdeps.join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with the upgrade (remove old, install new)
|
||||||
|
println!("\nProceeding with upgrade...");
|
||||||
|
let mut orchestrator = BuildOrchestrator::new(config, db);
|
||||||
|
orchestrator.install(&targets)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::Search { query } => {
|
||||||
|
// Search through all repos for matching package names/descriptions
|
||||||
|
for repo in &config.repos {
|
||||||
|
let packages = resolver::DependencyGraph::load_repo(&repo.path)?;
|
||||||
|
for (name, pkg) in &packages {
|
||||||
|
if name.contains(&query) || pkg.package.description.to_lowercase().contains(&query.to_lowercase()) {
|
||||||
|
println!(
|
||||||
|
"{}/{} {} — {}",
|
||||||
|
repo.name.cyan(),
|
||||||
|
name.bold(),
|
||||||
|
pkg.package.version.green(),
|
||||||
|
pkg.package.description
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::Info { package } => {
|
||||||
|
// Check installed first
|
||||||
|
let db = PackageDb::open(&config.paths.db_dir)?;
|
||||||
|
if let Some(installed) = db.get(&package) {
|
||||||
|
println!("{}: {}", "Name".bold(), installed.name);
|
||||||
|
println!("{}: {}", "Version".bold(), installed.version);
|
||||||
|
println!("{}: {}", "Description".bold(), installed.description);
|
||||||
|
println!("{}: {}", "Status".bold(), "installed".green());
|
||||||
|
println!("{}: {}", "Repo".bold(), installed.repo);
|
||||||
|
println!("{}: {}", "Files".bold(), installed.files.len());
|
||||||
|
println!("{}: {} bytes", "Size".bold(), installed.size);
|
||||||
|
if !installed.features.is_empty() {
|
||||||
|
println!("{}: {}", "Features".bold(), installed.features.join(", "));
|
||||||
|
}
|
||||||
|
if !installed.run_deps.is_empty() {
|
||||||
|
println!("{}: {}", "Run deps".bold(), installed.run_deps.join(", "));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Search repos
|
||||||
|
for repo in &config.repos {
|
||||||
|
if let Some(pkg_path) = repo.path.join(&package).join(format!("{}.toml", package)).exists().then(|| repo.path.join(&package).join(format!("{}.toml", package))) {
|
||||||
|
let pkg = PackageDefinition::from_file(&pkg_path)?;
|
||||||
|
println!("{}: {}", "Name".bold(), pkg.package.name);
|
||||||
|
println!("{}: {}", "Version".bold(), pkg.package.version);
|
||||||
|
println!("{}: {}", "Description".bold(), pkg.package.description);
|
||||||
|
println!("{}: {}", "Status".bold(), "not installed".yellow());
|
||||||
|
println!("{}: {}", "URL".bold(), pkg.package.url);
|
||||||
|
println!("{}: {}", "License".bold(), pkg.package.license);
|
||||||
|
if !pkg.dependencies.run.is_empty() {
|
||||||
|
println!("{}: {}", "Run deps".bold(), pkg.dependencies.run.join(", "));
|
||||||
|
}
|
||||||
|
if !pkg.dependencies.build.is_empty() {
|
||||||
|
println!("{}: {}", "Build deps".bold(), pkg.dependencies.build.join(", "));
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("{} Package '{}' not found", "Error:".red().bold(), package);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::List => {
|
||||||
|
let db = PackageDb::open(&config.paths.db_dir)?;
|
||||||
|
let all = db.list_all();
|
||||||
|
if all.is_empty() {
|
||||||
|
println!("No packages installed.");
|
||||||
|
} else {
|
||||||
|
println!("{} installed packages:", all.len());
|
||||||
|
for pkg in &all {
|
||||||
|
println!(
|
||||||
|
" {} {} [{}]",
|
||||||
|
pkg.name.bold(),
|
||||||
|
pkg.version.green(),
|
||||||
|
pkg.repo.cyan()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!("\nTotal disk usage: {} MB", db.total_size() / (1024 * 1024));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::Convert { path, output } => {
|
||||||
|
let input_path = std::path::Path::new(&path);
|
||||||
|
if !input_path.exists() {
|
||||||
|
anyhow::bail!("Input file not found: {}", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Converting: {}", path);
|
||||||
|
let toml_output = converter::convert_file(input_path)?;
|
||||||
|
|
||||||
|
if let Some(out_path) = output {
|
||||||
|
std::fs::write(&out_path, &toml_output)
|
||||||
|
.with_context(|| format!("Failed to write: {}", out_path))?;
|
||||||
|
println!("{} Written to: {}", "Converted!".green().bold(), out_path);
|
||||||
|
} else {
|
||||||
|
// Print to stdout
|
||||||
|
println!("{}", "--- Converted TOML ---".cyan().bold());
|
||||||
|
println!("{}", toml_output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Commands::Check => {
|
||||||
|
let db = PackageDb::open(&config.paths.db_dir)?;
|
||||||
|
|
||||||
|
// Check for file ownership conflicts
|
||||||
|
let file_conflicts = db.find_conflicts();
|
||||||
|
if file_conflicts.is_empty() {
|
||||||
|
println!("{}", "No file ownership conflicts detected.".green());
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{} {} file conflict(s) found:",
|
||||||
|
"Warning:".yellow().bold(),
|
||||||
|
file_conflicts.len()
|
||||||
|
);
|
||||||
|
for (file, owners) in &file_conflicts {
|
||||||
|
println!(" {} — owned by: {}", file.display(), owners.join(", "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build solib dependency map
|
||||||
|
println!("\nScanning shared library dependencies...");
|
||||||
|
let solib_map = resolver::solib::build_solib_map(&db);
|
||||||
|
println!(
|
||||||
|
"Tracked {} unique shared libraries across {} packages.",
|
||||||
|
solib_map.len(),
|
||||||
|
db.count()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Report any libraries linked by multiple packages
|
||||||
|
let multi_user_libs: Vec<_> = solib_map
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, pkgs)| pkgs.len() > 2)
|
||||||
|
.collect();
|
||||||
|
if !multi_user_libs.is_empty() {
|
||||||
|
println!(
|
||||||
|
"\n{} libraries used by 3+ packages (upgrade with care):",
|
||||||
|
"Widely-used".cyan().bold()
|
||||||
|
);
|
||||||
|
for (lib, pkgs) in &multi_user_libs {
|
||||||
|
println!(" {} — {} packages", lib, pkgs.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
389
src/dpack/src/resolver/mod.rs
Normal file
389
src/dpack/src/resolver/mod.rs
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
//! Dependency resolution engine.
|
||||||
|
//!
|
||||||
|
//! Resolves a package's full dependency tree into a topologically sorted
|
||||||
|
//! build order. Handles:
|
||||||
|
//! - Direct runtime dependencies
|
||||||
|
//! - Build-time dependencies
|
||||||
|
//! - Optional feature dependencies
|
||||||
|
//! - Circular dependency detection
|
||||||
|
//! - Version constraints (basic)
|
||||||
|
//!
|
||||||
|
//! The resolver operates on a `PackageGraph` built from the repository's
|
||||||
|
//! package definitions and the installed package database.
|
||||||
|
|
||||||
|
pub mod solib;
|
||||||
|
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::config::PackageDefinition;
|
||||||
|
|
||||||
|
/// The result of dependency resolution: an ordered list of packages to build.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ResolutionPlan {
|
||||||
|
/// Packages in topological order (build these first-to-last)
|
||||||
|
pub build_order: Vec<ResolvedPackage>,
|
||||||
|
|
||||||
|
/// Packages that are already installed and don't need rebuilding
|
||||||
|
pub already_installed: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single package in the resolution plan.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ResolvedPackage {
|
||||||
|
/// Package name
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// Package version
|
||||||
|
pub version: String,
|
||||||
|
|
||||||
|
/// Whether this is a build-only dependency (not needed at runtime)
|
||||||
|
pub build_only: bool,
|
||||||
|
|
||||||
|
/// Which features are enabled for this package
|
||||||
|
pub features: Vec<String>,
|
||||||
|
|
||||||
|
/// Path to the package definition file
|
||||||
|
pub definition_path: std::path::PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The dependency graph used internally for resolution.
|
||||||
|
pub struct DependencyGraph {
|
||||||
|
/// All known package definitions, keyed by name
|
||||||
|
packages: HashMap<String, PackageDefinition>,
|
||||||
|
|
||||||
|
/// Set of currently installed packages (name -> version)
|
||||||
|
installed: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DependencyGraph {
|
||||||
|
/// Create a new graph from a set of package definitions and installed state.
|
||||||
|
pub fn new(
|
||||||
|
packages: HashMap<String, PackageDefinition>,
|
||||||
|
installed: HashMap<String, String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
packages,
|
||||||
|
installed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load all package definitions from a repository directory.
|
||||||
|
///
|
||||||
|
/// Expects: `repo_dir/<name>/<name>.toml`
|
||||||
|
pub fn load_repo(repo_dir: &Path) -> Result<HashMap<String, PackageDefinition>> {
|
||||||
|
let mut packages = HashMap::new();
|
||||||
|
|
||||||
|
if !repo_dir.is_dir() {
|
||||||
|
return Ok(packages);
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in std::fs::read_dir(repo_dir)
|
||||||
|
.with_context(|| format!("Failed to read repo: {}", repo_dir.display()))?
|
||||||
|
{
|
||||||
|
let entry = entry?;
|
||||||
|
if !entry.file_type()?.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pkg_name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
let toml_path = entry.path().join(format!("{}.toml", pkg_name));
|
||||||
|
|
||||||
|
if toml_path.exists() {
|
||||||
|
match PackageDefinition::from_file(&toml_path) {
|
||||||
|
Ok(pkg) => {
|
||||||
|
packages.insert(pkg_name, pkg);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Skipping {}: {}", toml_path.display(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve all dependencies for the given package names.
|
||||||
|
///
|
||||||
|
/// Returns a topologically sorted build order. Detects circular deps.
|
||||||
|
pub fn resolve(
|
||||||
|
&self,
|
||||||
|
targets: &[String],
|
||||||
|
enabled_features: &HashMap<String, Vec<String>>,
|
||||||
|
) -> Result<ResolutionPlan> {
|
||||||
|
let mut visited: HashSet<String> = HashSet::new();
|
||||||
|
let mut in_stack: HashSet<String> = HashSet::new();
|
||||||
|
let mut order: Vec<ResolvedPackage> = Vec::new();
|
||||||
|
let mut already_installed: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
for target in targets {
|
||||||
|
self.resolve_recursive(
|
||||||
|
target,
|
||||||
|
false, // not build-only
|
||||||
|
enabled_features,
|
||||||
|
&mut visited,
|
||||||
|
&mut in_stack,
|
||||||
|
&mut order,
|
||||||
|
&mut already_installed,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ResolutionPlan {
|
||||||
|
build_order: order,
|
||||||
|
already_installed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursive DFS for topological sort with cycle detection.
|
||||||
|
fn resolve_recursive(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
build_only: bool,
|
||||||
|
enabled_features: &HashMap<String, Vec<String>>,
|
||||||
|
visited: &mut HashSet<String>,
|
||||||
|
in_stack: &mut HashSet<String>,
|
||||||
|
order: &mut Vec<ResolvedPackage>,
|
||||||
|
already_installed: &mut Vec<String>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Already fully resolved
|
||||||
|
if visited.contains(name) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Circular dependency detected
|
||||||
|
if in_stack.contains(name) {
|
||||||
|
bail!(
|
||||||
|
"Circular dependency detected: '{}' depends on itself (chain: {:?})",
|
||||||
|
name,
|
||||||
|
in_stack
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already installed at the right version
|
||||||
|
if let Some(installed_version) = self.installed.get(name) {
|
||||||
|
if let Some(pkg) = self.packages.get(name) {
|
||||||
|
if installed_version == &pkg.package.version {
|
||||||
|
already_installed.push(name.to_string());
|
||||||
|
visited.insert(name.to_string());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the package definition
|
||||||
|
let pkg = self
|
||||||
|
.packages
|
||||||
|
.get(name)
|
||||||
|
.with_context(|| format!("Package '{}' not found in any repository", name))?;
|
||||||
|
|
||||||
|
in_stack.insert(name.to_string());
|
||||||
|
|
||||||
|
// Get features for this package
|
||||||
|
let features = enabled_features
|
||||||
|
.get(name)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| pkg.default_features());
|
||||||
|
|
||||||
|
// Resolve build dependencies first
|
||||||
|
for dep in &pkg.effective_build_deps(&features) {
|
||||||
|
self.resolve_recursive(
|
||||||
|
dep,
|
||||||
|
true,
|
||||||
|
enabled_features,
|
||||||
|
visited,
|
||||||
|
in_stack,
|
||||||
|
order,
|
||||||
|
already_installed,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then resolve runtime dependencies
|
||||||
|
for dep in &pkg.effective_run_deps(&features) {
|
||||||
|
self.resolve_recursive(
|
||||||
|
dep,
|
||||||
|
false,
|
||||||
|
enabled_features,
|
||||||
|
visited,
|
||||||
|
in_stack,
|
||||||
|
order,
|
||||||
|
already_installed,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
in_stack.remove(name);
|
||||||
|
visited.insert(name.to_string());
|
||||||
|
|
||||||
|
order.push(ResolvedPackage {
|
||||||
|
name: name.to_string(),
|
||||||
|
version: pkg.package.version.clone(),
|
||||||
|
build_only,
|
||||||
|
features,
|
||||||
|
definition_path: std::path::PathBuf::new(), // Set by caller
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform a simple reverse-dependency lookup: which installed packages
|
||||||
|
/// depend on the given package?
|
||||||
|
pub fn reverse_deps(
|
||||||
|
package: &str,
|
||||||
|
all_packages: &HashMap<String, PackageDefinition>,
|
||||||
|
installed: &HashSet<String>,
|
||||||
|
) -> Vec<String> {
|
||||||
|
let mut rdeps = Vec::new();
|
||||||
|
|
||||||
|
for inst_name in installed {
|
||||||
|
if let Some(pkg) = all_packages.get(inst_name) {
|
||||||
|
let features = pkg.default_features();
|
||||||
|
let all_deps: Vec<String> = pkg
|
||||||
|
.effective_run_deps(&features)
|
||||||
|
.into_iter()
|
||||||
|
.chain(pkg.effective_build_deps(&features))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if all_deps.iter().any(|d| d == package) {
|
||||||
|
rdeps.push(inst_name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rdeps
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::config::package::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Helper: create a minimal PackageDefinition for testing.
|
||||||
|
fn make_pkg(name: &str, version: &str, run_deps: Vec<&str>, build_deps: Vec<&str>) -> PackageDefinition {
|
||||||
|
PackageDefinition {
|
||||||
|
package: PackageMetadata {
|
||||||
|
name: name.to_string(),
|
||||||
|
version: version.to_string(),
|
||||||
|
description: format!("Test package {}", name),
|
||||||
|
url: "https://example.com".to_string(),
|
||||||
|
license: "MIT".to_string(),
|
||||||
|
epoch: 0,
|
||||||
|
revision: 1,
|
||||||
|
},
|
||||||
|
source: SourceInfo {
|
||||||
|
url: format!("https://example.com/{}-{}.tar.xz", name, version),
|
||||||
|
sha256: "a".repeat(64),
|
||||||
|
patches: vec![],
|
||||||
|
},
|
||||||
|
dependencies: Dependencies {
|
||||||
|
run: run_deps.into_iter().map(String::from).collect(),
|
||||||
|
build: build_deps.into_iter().map(String::from).collect(),
|
||||||
|
optional: HashMap::new(),
|
||||||
|
},
|
||||||
|
build: BuildInstructions {
|
||||||
|
configure: "./configure --prefix=/usr".to_string(),
|
||||||
|
make: "make".to_string(),
|
||||||
|
install: "make DESTDIR=${PKG} install".to_string(),
|
||||||
|
prepare: String::new(),
|
||||||
|
post_install: String::new(),
|
||||||
|
check: String::new(),
|
||||||
|
flags: BuildFlags::default(),
|
||||||
|
system: BuildSystem::default(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_resolution() {
|
||||||
|
let mut packages = HashMap::new();
|
||||||
|
packages.insert("zlib".to_string(), make_pkg("zlib", "1.3.1", vec![], vec!["gcc", "make"]));
|
||||||
|
packages.insert("gcc".to_string(), make_pkg("gcc", "15.2.0", vec![], vec![]));
|
||||||
|
packages.insert("make".to_string(), make_pkg("make", "4.4.1", vec![], vec![]));
|
||||||
|
|
||||||
|
let graph = DependencyGraph::new(packages, HashMap::new());
|
||||||
|
let plan = graph.resolve(&["zlib".to_string()], &HashMap::new()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(plan.build_order.len(), 3);
|
||||||
|
// gcc and make should come before zlib
|
||||||
|
let names: Vec<&str> = plan.build_order.iter().map(|p| p.name.as_str()).collect();
|
||||||
|
let zlib_pos = names.iter().position(|&n| n == "zlib").unwrap();
|
||||||
|
let gcc_pos = names.iter().position(|&n| n == "gcc").unwrap();
|
||||||
|
let make_pos = names.iter().position(|&n| n == "make").unwrap();
|
||||||
|
assert!(gcc_pos < zlib_pos);
|
||||||
|
assert!(make_pos < zlib_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_circular_dependency_detection() {
|
||||||
|
let mut packages = HashMap::new();
|
||||||
|
packages.insert("a".to_string(), make_pkg("a", "1.0", vec!["b"], vec![]));
|
||||||
|
packages.insert("b".to_string(), make_pkg("b", "1.0", vec!["a"], vec![]));
|
||||||
|
|
||||||
|
let graph = DependencyGraph::new(packages, HashMap::new());
|
||||||
|
let result = graph.resolve(&["a".to_string()], &HashMap::new());
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().to_string().contains("Circular"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_already_installed() {
|
||||||
|
let mut packages = HashMap::new();
|
||||||
|
packages.insert("zlib".to_string(), make_pkg("zlib", "1.3.1", vec![], vec![]));
|
||||||
|
|
||||||
|
let mut installed = HashMap::new();
|
||||||
|
installed.insert("zlib".to_string(), "1.3.1".to_string());
|
||||||
|
|
||||||
|
let graph = DependencyGraph::new(packages, installed);
|
||||||
|
let plan = graph.resolve(&["zlib".to_string()], &HashMap::new()).unwrap();
|
||||||
|
|
||||||
|
assert!(plan.build_order.is_empty());
|
||||||
|
assert_eq!(plan.already_installed, vec!["zlib"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_dependency() {
|
||||||
|
let mut packages = HashMap::new();
|
||||||
|
packages.insert("foo".to_string(), make_pkg("foo", "1.0", vec!["missing"], vec![]));
|
||||||
|
|
||||||
|
let graph = DependencyGraph::new(packages, HashMap::new());
|
||||||
|
let result = graph.resolve(&["foo".to_string()], &HashMap::new());
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().to_string().contains("missing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diamond_dependency() {
|
||||||
|
let mut packages = HashMap::new();
|
||||||
|
packages.insert("a".to_string(), make_pkg("a", "1.0", vec![], vec![]));
|
||||||
|
packages.insert("b".to_string(), make_pkg("b", "1.0", vec!["a"], vec![]));
|
||||||
|
packages.insert("c".to_string(), make_pkg("c", "1.0", vec!["a"], vec![]));
|
||||||
|
packages.insert("d".to_string(), make_pkg("d", "1.0", vec!["b", "c"], vec![]));
|
||||||
|
|
||||||
|
let graph = DependencyGraph::new(packages, HashMap::new());
|
||||||
|
let plan = graph.resolve(&["d".to_string()], &HashMap::new()).unwrap();
|
||||||
|
|
||||||
|
let names: Vec<&str> = plan.build_order.iter().map(|p| p.name.as_str()).collect();
|
||||||
|
// A should appear only once
|
||||||
|
assert_eq!(names.iter().filter(|&&n| n == "a").count(), 1);
|
||||||
|
// A before B and C, B and C before D
|
||||||
|
let a_pos = names.iter().position(|&n| n == "a").unwrap();
|
||||||
|
let d_pos = names.iter().position(|&n| n == "d").unwrap();
|
||||||
|
assert!(a_pos < d_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reverse_deps() {
|
||||||
|
let mut packages = HashMap::new();
|
||||||
|
packages.insert("zlib".to_string(), make_pkg("zlib", "1.3.1", vec![], vec![]));
|
||||||
|
packages.insert("curl".to_string(), make_pkg("curl", "8.0", vec!["zlib"], vec![]));
|
||||||
|
packages.insert("git".to_string(), make_pkg("git", "2.0", vec!["curl"], vec![]));
|
||||||
|
|
||||||
|
let installed: HashSet<String> = ["zlib", "curl", "git"].iter().map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
let rdeps = reverse_deps("zlib", &packages, &installed);
|
||||||
|
assert!(rdeps.contains(&"curl".to_string()));
|
||||||
|
assert!(!rdeps.contains(&"git".to_string())); // git depends on curl, not zlib directly
|
||||||
|
}
|
||||||
|
}
|
||||||
311
src/dpack/src/resolver/solib.rs
Normal file
311
src/dpack/src/resolver/solib.rs
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
//! Shared library conflict detection and resolution.
|
||||||
|
//!
|
||||||
|
//! When upgrading or installing a package that provides a shared library,
|
||||||
|
//! check if other installed packages depend on the old version of that library.
|
||||||
|
//!
|
||||||
|
//! Resolution strategies:
|
||||||
|
//! 1. Check if dependents have an update that works with the new lib version
|
||||||
|
//! 2. If yes, offer to upgrade them too
|
||||||
|
//! 3. If no, offer: (a) static compilation, (b) hold back, (c) force
|
||||||
|
//!
|
||||||
|
//! Implementation uses `readelf` or `objdump` to parse ELF shared library
|
||||||
|
//! dependencies from installed binaries.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use crate::db::PackageDb;
|
||||||
|
|
||||||
|
/// A shared library dependency found in an ELF binary.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SharedLib {
|
||||||
|
/// Library soname (e.g., "libz.so.1")
|
||||||
|
pub soname: String,
|
||||||
|
|
||||||
|
/// Full path to the library file
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A conflict where a library upgrade would break a dependent package.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LibConflict {
|
||||||
|
/// The library being upgraded
|
||||||
|
pub library: String,
|
||||||
|
|
||||||
|
/// The old soname that dependents link against
|
||||||
|
pub old_soname: String,
|
||||||
|
|
||||||
|
/// The new soname after the upgrade
|
||||||
|
pub new_soname: String,
|
||||||
|
|
||||||
|
/// Packages that depend on the old soname
|
||||||
|
pub affected_packages: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolution action chosen by the user.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ConflictResolution {
|
||||||
|
/// Upgrade all affected packages
|
||||||
|
UpgradeAll,
|
||||||
|
/// Compile the new package with static linking
|
||||||
|
StaticLink,
|
||||||
|
/// Hold back the library (don't upgrade)
|
||||||
|
HoldBack,
|
||||||
|
/// Force the upgrade (user accepts the risk)
|
||||||
|
Force,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scan an ELF binary for shared library dependencies.
|
||||||
|
///
|
||||||
|
/// Uses `readelf -d` to extract NEEDED entries.
|
||||||
|
pub fn get_needed_libs(binary_path: &Path) -> Result<Vec<String>> {
|
||||||
|
let output = Command::new("readelf")
|
||||||
|
.args(["-d", &binary_path.to_string_lossy()])
|
||||||
|
.output()
|
||||||
|
.or_else(|_| {
|
||||||
|
// Fallback to objdump
|
||||||
|
Command::new("objdump")
|
||||||
|
.args(["-p", &binary_path.to_string_lossy()])
|
||||||
|
.output()
|
||||||
|
})
|
||||||
|
.context("Neither readelf nor objdump available")?;
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let mut libs = Vec::new();
|
||||||
|
|
||||||
|
for line in stdout.lines() {
|
||||||
|
// readelf format: 0x0000000000000001 (NEEDED) Shared library: [libz.so.1]
|
||||||
|
if line.contains("NEEDED") {
|
||||||
|
if let Some(start) = line.find('[') {
|
||||||
|
if let Some(end) = line.find(']') {
|
||||||
|
libs.push(line[start + 1..end].to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// objdump format: NEEDED libz.so.1
|
||||||
|
else if line.trim().starts_with("NEEDED") {
|
||||||
|
if let Some(lib) = line.split_whitespace().last() {
|
||||||
|
libs.push(lib.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(libs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the soname of a shared library file.
|
||||||
|
///
|
||||||
|
/// Uses `readelf -d` to extract the SONAME entry.
|
||||||
|
pub fn get_soname(lib_path: &Path) -> Result<Option<String>> {
|
||||||
|
let output = Command::new("readelf")
|
||||||
|
.args(["-d", &lib_path.to_string_lossy()])
|
||||||
|
.output()
|
||||||
|
.context("readelf not available")?;
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
for line in stdout.lines() {
|
||||||
|
if line.contains("SONAME") {
|
||||||
|
if let Some(start) = line.find('[') {
|
||||||
|
if let Some(end) = line.find(']') {
|
||||||
|
return Ok(Some(line[start + 1..end].to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a map of soname → packages that link against it.
|
||||||
|
///
|
||||||
|
/// Scans all ELF binaries/libraries in installed packages.
|
||||||
|
pub fn build_solib_map(db: &PackageDb) -> HashMap<String, Vec<String>> {
|
||||||
|
let mut map: HashMap<String, Vec<String>> = HashMap::new();
|
||||||
|
|
||||||
|
for pkg in db.list_all() {
|
||||||
|
for file in &pkg.files {
|
||||||
|
// Only check ELF binaries and shared libraries
|
||||||
|
let ext = file.extension().map(|e| e.to_string_lossy().to_string());
|
||||||
|
let is_elf = file.starts_with("/usr/bin")
|
||||||
|
|| file.starts_with("/usr/lib")
|
||||||
|
|| file.starts_with("/usr/sbin")
|
||||||
|
|| ext.as_deref() == Some("so")
|
||||||
|
|| file.to_string_lossy().contains(".so.");
|
||||||
|
|
||||||
|
if !is_elf || !file.exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(libs) = get_needed_libs(file) {
|
||||||
|
for lib in libs {
|
||||||
|
map.entry(lib)
|
||||||
|
.or_default()
|
||||||
|
.push(pkg.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate package names per soname
|
||||||
|
for pkgs in map.values_mut() {
|
||||||
|
pkgs.sort();
|
||||||
|
pkgs.dedup();
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if upgrading a package would cause shared library conflicts.
|
||||||
|
///
|
||||||
|
/// Compares the old package's provided sonames with the new package's sonames.
|
||||||
|
/// If a soname changes (e.g., `libfoo.so.1` → `libfoo.so.2`), find all
|
||||||
|
/// packages that link against the old soname.
|
||||||
|
pub fn check_upgrade_conflicts(
|
||||||
|
package_name: &str,
|
||||||
|
old_files: &[PathBuf],
|
||||||
|
new_files: &[PathBuf],
|
||||||
|
solib_map: &HashMap<String, Vec<String>>,
|
||||||
|
) -> Vec<LibConflict> {
|
||||||
|
let mut conflicts = Vec::new();
|
||||||
|
|
||||||
|
// Find sonames provided by the old version
|
||||||
|
let old_sonames = collect_provided_sonames(old_files);
|
||||||
|
let new_sonames = collect_provided_sonames(new_files);
|
||||||
|
|
||||||
|
// Check for sonames that exist in old but not in new
|
||||||
|
for old_so in &old_sonames {
|
||||||
|
if !new_sonames.contains(old_so) {
|
||||||
|
// Find the replacement (if any) — same base name, different version
|
||||||
|
let base = soname_base(old_so);
|
||||||
|
let replacement = new_sonames
|
||||||
|
.iter()
|
||||||
|
.find(|s| soname_base(s) == base)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| "REMOVED".to_string());
|
||||||
|
|
||||||
|
// Find affected packages
|
||||||
|
if let Some(dependents) = solib_map.get(old_so) {
|
||||||
|
let affected: Vec<String> = dependents
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.as_str() != package_name) // Exclude self
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !affected.is_empty() {
|
||||||
|
conflicts.push(LibConflict {
|
||||||
|
library: package_name.to_string(),
|
||||||
|
old_soname: old_so.clone(),
|
||||||
|
new_soname: replacement,
|
||||||
|
affected_packages: affected,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conflicts
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect sonames provided by a set of files.
|
||||||
|
fn collect_provided_sonames(files: &[PathBuf]) -> HashSet<String> {
|
||||||
|
let mut sonames = HashSet::new();
|
||||||
|
|
||||||
|
for file in files {
|
||||||
|
if file.to_string_lossy().contains(".so") && file.exists() {
|
||||||
|
if let Ok(Some(soname)) = get_soname(file) {
|
||||||
|
sonames.insert(soname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sonames
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the base name from a soname (strip version suffix).
|
||||||
|
/// e.g., "libz.so.1" → "libz.so", "libfoo.so.2.3.4" → "libfoo.so"
|
||||||
|
fn soname_base(soname: &str) -> String {
|
||||||
|
if let Some(pos) = soname.find(".so.") {
|
||||||
|
soname[..pos + 3].to_string() // Include ".so"
|
||||||
|
} else {
|
||||||
|
soname.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a conflict report for display to the user.
|
||||||
|
pub fn format_conflict_report(conflicts: &[LibConflict]) -> String {
|
||||||
|
if conflicts.is_empty() {
|
||||||
|
return "No shared library conflicts detected.".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut report = String::new();
|
||||||
|
report.push_str(&format!(
|
||||||
|
"WARNING: {} shared library conflict(s) detected:\n\n",
|
||||||
|
conflicts.len()
|
||||||
|
));
|
||||||
|
|
||||||
|
for conflict in conflicts {
|
||||||
|
report.push_str(&format!(
|
||||||
|
" Library: {} → {}\n",
|
||||||
|
conflict.old_soname, conflict.new_soname
|
||||||
|
));
|
||||||
|
report.push_str(&format!(" Source: {}\n", conflict.library));
|
||||||
|
report.push_str(&format!(
|
||||||
|
" Affected packages: {}\n",
|
||||||
|
conflict.affected_packages.join(", ")
|
||||||
|
));
|
||||||
|
report.push_str("\n Options:\n");
|
||||||
|
report.push_str(" 1. Upgrade affected packages\n");
|
||||||
|
report.push_str(" 2. Compile with static linking\n");
|
||||||
|
report.push_str(" 3. Hold back the upgrade\n");
|
||||||
|
report.push_str(" 4. Force (accept the risk)\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
report
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_soname_base() {
|
||||||
|
assert_eq!(soname_base("libz.so.1"), "libz.so");
|
||||||
|
assert_eq!(soname_base("libfoo.so.2.3.4"), "libfoo.so");
|
||||||
|
assert_eq!(soname_base("libbar.so"), "libbar.so");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_upgrade_no_conflict() {
|
||||||
|
let old_files: Vec<PathBuf> = vec![];
|
||||||
|
let new_files: Vec<PathBuf> = vec![];
|
||||||
|
let solib_map = HashMap::new();
|
||||||
|
|
||||||
|
let conflicts = check_upgrade_conflicts("test", &old_files, &new_files, &solib_map);
|
||||||
|
assert!(conflicts.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_empty_report() {
|
||||||
|
let report = format_conflict_report(&[]);
|
||||||
|
assert!(report.contains("No shared library conflicts"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_conflict_report() {
|
||||||
|
let conflicts = vec![LibConflict {
|
||||||
|
library: "zlib".to_string(),
|
||||||
|
old_soname: "libz.so.1".to_string(),
|
||||||
|
new_soname: "libz.so.2".to_string(),
|
||||||
|
affected_packages: vec!["curl".to_string(), "git".to_string()],
|
||||||
|
}];
|
||||||
|
|
||||||
|
let report = format_conflict_report(&conflicts);
|
||||||
|
assert!(report.contains("libz.so.1"));
|
||||||
|
assert!(report.contains("libz.so.2"));
|
||||||
|
assert!(report.contains("curl"));
|
||||||
|
assert!(report.contains("git"));
|
||||||
|
}
|
||||||
|
}
|
||||||
340
src/dpack/src/sandbox/mod.rs
Normal file
340
src/dpack/src/sandbox/mod.rs
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
//! Build sandboxing using Linux namespaces or bubblewrap.
|
||||||
|
//!
|
||||||
|
//! Isolates package builds so that:
|
||||||
|
//! - Only declared dependencies are visible in the sandbox's filesystem
|
||||||
|
//! - Build processes run in a separate PID namespace
|
||||||
|
//! - Network access is blocked by default (configurable)
|
||||||
|
//! - Installed files are captured via `DESTDIR` to a staging area
|
||||||
|
//!
|
||||||
|
//! Two backends are supported:
|
||||||
|
//! 1. **bubblewrap (bwrap)** — preferred, lightweight, unprivileged
|
||||||
|
//! 2. **direct** — no sandboxing (fallback for bootstrapping or debugging)
|
||||||
|
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use crate::config::{DpackConfig, PackageDefinition};
|
||||||
|
|
||||||
|
/// Sandbox backend selection.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum SandboxBackend {
|
||||||
|
/// Use bubblewrap for isolation
|
||||||
|
Bubblewrap,
|
||||||
|
/// No sandboxing — run directly (for bootstrap or debugging)
|
||||||
|
Direct,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A configured build sandbox ready to execute a package build.
|
||||||
|
pub struct BuildSandbox {
|
||||||
|
/// The backend to use
|
||||||
|
backend: SandboxBackend,
|
||||||
|
|
||||||
|
/// Working directory for the build (contains extracted source)
|
||||||
|
build_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Staging directory where `DESTDIR` installs to
|
||||||
|
staging_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Path to bubblewrap binary
|
||||||
|
bwrap_path: PathBuf,
|
||||||
|
|
||||||
|
/// Whether to allow network access during build
|
||||||
|
allow_network: bool,
|
||||||
|
|
||||||
|
/// Paths to bind-mount read-only into the sandbox (dependency install dirs)
|
||||||
|
ro_binds: Vec<(PathBuf, PathBuf)>,
|
||||||
|
|
||||||
|
/// Environment variables to set in the sandbox
|
||||||
|
env_vars: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuildSandbox {
|
||||||
|
/// Create a new sandbox for building a package.
|
||||||
|
pub fn new(
|
||||||
|
config: &DpackConfig,
|
||||||
|
pkg: &PackageDefinition,
|
||||||
|
build_dir: &Path,
|
||||||
|
staging_dir: &Path,
|
||||||
|
) -> Result<Self> {
|
||||||
|
std::fs::create_dir_all(build_dir)
|
||||||
|
.with_context(|| format!("Failed to create build dir: {}", build_dir.display()))?;
|
||||||
|
std::fs::create_dir_all(staging_dir)
|
||||||
|
.with_context(|| format!("Failed to create staging dir: {}", staging_dir.display()))?;
|
||||||
|
|
||||||
|
let backend = if config.sandbox.enabled {
|
||||||
|
// Check if bwrap is available
|
||||||
|
if config.sandbox.bwrap_path.exists() {
|
||||||
|
SandboxBackend::Bubblewrap
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"Bubblewrap not found at {}, falling back to direct execution",
|
||||||
|
config.sandbox.bwrap_path.display()
|
||||||
|
);
|
||||||
|
SandboxBackend::Direct
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SandboxBackend::Direct
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up environment variables for the build
|
||||||
|
let cflags = config.effective_cflags(&pkg.build.flags.cflags).to_string();
|
||||||
|
let cxxflags = if pkg.build.flags.cxxflags.is_empty() {
|
||||||
|
cflags.clone()
|
||||||
|
} else {
|
||||||
|
pkg.build.flags.cxxflags.clone()
|
||||||
|
};
|
||||||
|
let ldflags = config.effective_ldflags(&pkg.build.flags.ldflags).to_string();
|
||||||
|
let makeflags = if pkg.build.flags.makeflags.is_empty() {
|
||||||
|
config.flags.makeflags.clone()
|
||||||
|
} else {
|
||||||
|
pkg.build.flags.makeflags.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let env_vars = vec![
|
||||||
|
("CFLAGS".to_string(), cflags),
|
||||||
|
("CXXFLAGS".to_string(), cxxflags),
|
||||||
|
("LDFLAGS".to_string(), ldflags),
|
||||||
|
("MAKEFLAGS".to_string(), makeflags),
|
||||||
|
("PKG".to_string(), staging_dir.to_string_lossy().to_string()),
|
||||||
|
("HOME".to_string(), "/tmp".to_string()),
|
||||||
|
("PATH".to_string(), "/usr/bin:/usr/sbin:/bin:/sbin".to_string()),
|
||||||
|
("LC_ALL".to_string(), "POSIX".to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
backend,
|
||||||
|
build_dir: build_dir.to_path_buf(),
|
||||||
|
staging_dir: staging_dir.to_path_buf(),
|
||||||
|
bwrap_path: config.sandbox.bwrap_path.clone(),
|
||||||
|
allow_network: config.sandbox.allow_network,
|
||||||
|
ro_binds: Vec::new(),
|
||||||
|
env_vars,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a read-only bind mount (e.g., dependency install paths).
|
||||||
|
pub fn add_ro_bind(&mut self, host_path: PathBuf, sandbox_path: PathBuf) {
|
||||||
|
self.ro_binds.push((host_path, sandbox_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a shell command inside the sandbox.
|
||||||
|
pub fn exec(&self, command: &str) -> Result<()> {
|
||||||
|
if command.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Sandbox exec: {}", command);
|
||||||
|
|
||||||
|
match &self.backend {
|
||||||
|
SandboxBackend::Direct => self.exec_direct(command),
|
||||||
|
SandboxBackend::Bubblewrap => self.exec_bwrap(command),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute without sandboxing.
|
||||||
|
fn exec_direct(&self, command: &str) -> Result<()> {
|
||||||
|
let mut cmd = Command::new("bash");
|
||||||
|
cmd.arg("-c").arg(command);
|
||||||
|
cmd.current_dir(&self.build_dir);
|
||||||
|
|
||||||
|
for (key, val) in &self.env_vars {
|
||||||
|
cmd.env(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = cmd
|
||||||
|
.status()
|
||||||
|
.with_context(|| format!("Failed to execute: {}", command))?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
bail!(
|
||||||
|
"Command failed with exit code {}: {}",
|
||||||
|
status.code().unwrap_or(-1),
|
||||||
|
command
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute inside a bubblewrap sandbox.
|
||||||
|
fn exec_bwrap(&self, command: &str) -> Result<()> {
|
||||||
|
let mut cmd = Command::new(&self.bwrap_path);
|
||||||
|
|
||||||
|
// Base filesystem: overlay the build directory as writable
|
||||||
|
cmd.arg("--bind").arg(&self.build_dir).arg("/build");
|
||||||
|
cmd.arg("--bind").arg(&self.staging_dir).arg("/staging");
|
||||||
|
|
||||||
|
// Mount essential system directories read-only
|
||||||
|
cmd.arg("--ro-bind").arg("/usr").arg("/usr");
|
||||||
|
cmd.arg("--ro-bind").arg("/lib").arg("/lib");
|
||||||
|
if Path::new("/lib64").exists() {
|
||||||
|
cmd.arg("--ro-bind").arg("/lib64").arg("/lib64");
|
||||||
|
}
|
||||||
|
cmd.arg("--ro-bind").arg("/bin").arg("/bin");
|
||||||
|
cmd.arg("--ro-bind").arg("/sbin").arg("/sbin");
|
||||||
|
|
||||||
|
// Mount /dev minimal
|
||||||
|
cmd.arg("--dev").arg("/dev");
|
||||||
|
|
||||||
|
// Mount /proc and /tmp
|
||||||
|
cmd.arg("--proc").arg("/proc");
|
||||||
|
cmd.arg("--tmpfs").arg("/tmp");
|
||||||
|
|
||||||
|
// Dependency bind mounts
|
||||||
|
for (host, sandbox) in &self.ro_binds {
|
||||||
|
cmd.arg("--ro-bind").arg(host).arg(sandbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PID namespace
|
||||||
|
cmd.arg("--unshare-pid");
|
||||||
|
|
||||||
|
// Network isolation (unless explicitly allowed)
|
||||||
|
if !self.allow_network {
|
||||||
|
cmd.arg("--unshare-net");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set working directory
|
||||||
|
cmd.arg("--chdir").arg("/build");
|
||||||
|
|
||||||
|
// Environment variables
|
||||||
|
for (key, val) in &self.env_vars {
|
||||||
|
cmd.arg("--setenv").arg(key).arg(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override PKG to point to sandbox staging path
|
||||||
|
cmd.arg("--setenv").arg("PKG").arg("/staging");
|
||||||
|
|
||||||
|
// The actual command
|
||||||
|
cmd.arg("bash").arg("-c").arg(command);
|
||||||
|
|
||||||
|
let status = cmd
|
||||||
|
.status()
|
||||||
|
.with_context(|| format!("Bubblewrap execution failed: {}", command))?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
bail!(
|
||||||
|
"Sandboxed command failed with exit code {}: {}",
|
||||||
|
status.code().unwrap_or(-1),
|
||||||
|
command
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the full build sequence: prepare → configure → make → install
|
||||||
|
pub fn run_build(&self, pkg: &PackageDefinition) -> Result<()> {
|
||||||
|
// Prepare step (optional: patching, autoreconf, etc.)
|
||||||
|
if !pkg.build.prepare.is_empty() {
|
||||||
|
log::info!(">>> Prepare step");
|
||||||
|
self.exec(&pkg.build.prepare)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure step
|
||||||
|
if !pkg.build.configure.is_empty() {
|
||||||
|
log::info!(">>> Configure step");
|
||||||
|
self.exec(&pkg.build.configure)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build step
|
||||||
|
log::info!(">>> Build step");
|
||||||
|
self.exec(&pkg.build.make)?;
|
||||||
|
|
||||||
|
// Test step (optional)
|
||||||
|
if !pkg.build.check.is_empty() {
|
||||||
|
log::info!(">>> Check step");
|
||||||
|
// Don't fail the build on test failures — log a warning
|
||||||
|
if let Err(e) = self.exec(&pkg.build.check) {
|
||||||
|
log::warn!("Check step failed (non-fatal): {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install step
|
||||||
|
log::info!(">>> Install step");
|
||||||
|
self.exec(&pkg.build.install)?;
|
||||||
|
|
||||||
|
// Post-install step (optional)
|
||||||
|
if !pkg.build.post_install.is_empty() {
|
||||||
|
log::info!(">>> Post-install step");
|
||||||
|
self.exec(&pkg.build.post_install)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the path to the staging directory where installed files landed.
|
||||||
|
pub fn staging_dir(&self) -> &Path {
|
||||||
|
&self.staging_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the build directory path.
|
||||||
|
pub fn build_dir(&self) -> &Path {
|
||||||
|
&self.build_dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect all files in the staging directory (for database tracking).
|
||||||
|
pub fn collect_staged_files(staging_dir: &Path) -> Result<Vec<PathBuf>> {
|
||||||
|
let mut files = Vec::new();
|
||||||
|
|
||||||
|
if !staging_dir.exists() {
|
||||||
|
return Ok(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in walkdir::WalkDir::new(staging_dir)
|
||||||
|
.min_depth(1)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
|
if entry.file_type().is_file() || entry.file_type().is_symlink() {
|
||||||
|
// Store path relative to staging dir (= absolute path on target)
|
||||||
|
let rel = entry
|
||||||
|
.path()
|
||||||
|
.strip_prefix(staging_dir)
|
||||||
|
.unwrap_or(entry.path());
|
||||||
|
files.push(PathBuf::from("/").join(rel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sandbox_backend_selection_disabled() {
|
||||||
|
let mut config = DpackConfig::default();
|
||||||
|
config.sandbox.enabled = false;
|
||||||
|
|
||||||
|
let pkg_toml = r#"
|
||||||
|
[package]
|
||||||
|
name = "test"
|
||||||
|
version = "1.0"
|
||||||
|
description = "test"
|
||||||
|
url = "https://example.com"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://example.com/test-1.0.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
install = "make DESTDIR=${PKG} install"
|
||||||
|
"#;
|
||||||
|
let pkg = crate::config::PackageDefinition::from_str(pkg_toml).unwrap();
|
||||||
|
let tmpdir = std::env::temp_dir().join("dpack-test-sandbox");
|
||||||
|
let staging = std::env::temp_dir().join("dpack-test-staging");
|
||||||
|
|
||||||
|
let sandbox = BuildSandbox::new(&config, &pkg, &tmpdir, &staging).unwrap();
|
||||||
|
assert_eq!(sandbox.backend, SandboxBackend::Direct);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let _ = std::fs::remove_dir_all(&tmpdir);
|
||||||
|
let _ = std::fs::remove_dir_all(&staging);
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/install/README.md
Normal file
75
src/install/README.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# DarkForge Linux Installer
|
||||||
|
|
||||||
|
A CRUX-style interactive text-mode installer that runs from the DarkForge live ISO.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The installer walks through 9 steps to get a working DarkForge system on disk:
|
||||||
|
|
||||||
|
1. **Disk selection** — choose the target drive
|
||||||
|
2. **Partitioning** — GPT auto-partition (ESP 512MB + Swap 96GB + Root)
|
||||||
|
3. **Filesystem creation** — FAT32 (ESP), ext4 (root), swap
|
||||||
|
4. **Base system install** — via dpack or direct copy from live environment
|
||||||
|
5. **Kernel install** — copies kernel to ESP
|
||||||
|
6. **User setup** — root password, user account with groups
|
||||||
|
7. **Locale/timezone** — timezone, locale, keyboard layout
|
||||||
|
8. **Boot config** — EFISTUB boot entry via efibootmgr
|
||||||
|
9. **Optional packages** — desktop, gaming, dev tool groups
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
The installer runs from the DarkForge live ISO environment. It expects:
|
||||||
|
|
||||||
|
- UEFI firmware (no legacy BIOS support)
|
||||||
|
- At least one NVMe or SATA disk
|
||||||
|
- `sgdisk` (GPT partitioning)
|
||||||
|
- `mkfs.ext4`, `mkfs.fat`, `mkswap`
|
||||||
|
- `efibootmgr` (UEFI boot entry creation)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Boot the DarkForge live ISO, then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
install
|
||||||
|
```
|
||||||
|
|
||||||
|
Or run directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/install/install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
install/
|
||||||
|
├── install.sh # Main entry point (9-step wizard)
|
||||||
|
└── modules/
|
||||||
|
├── disk.sh # Disk selection, partitioning, formatting, mounting
|
||||||
|
├── user.sh # User account and hostname setup
|
||||||
|
├── locale.sh # Timezone, locale, keyboard
|
||||||
|
└── packages.sh # Base system install, kernel, optional packages
|
||||||
|
```
|
||||||
|
|
||||||
|
## Partition Scheme
|
||||||
|
|
||||||
|
The auto-partitioner creates:
|
||||||
|
|
||||||
|
| # | Type | Size | Filesystem | Mount |
|
||||||
|
|---|------|------|------------|-------|
|
||||||
|
| 1 | EFI System | 512MB | FAT32 | /boot/efi |
|
||||||
|
| 2 | Linux Swap | 96GB | swap | — |
|
||||||
|
| 3 | Linux Root | Remaining | ext4 | / |
|
||||||
|
|
||||||
|
The 96GB swap matches the RAM size to enable hibernation.
|
||||||
|
|
||||||
|
## Post-Install
|
||||||
|
|
||||||
|
After installation and reboot, the system boots via EFISTUB directly to a tty1 auto-login, which launches the dwl Wayland compositor.
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
|
||||||
|
```
|
||||||
|
git@git.dannyhaslund.dk:danny8632/darkforge.git
|
||||||
|
```
|
||||||
150
src/install/install.sh
Executable file
150
src/install/install.sh
Executable file
@@ -0,0 +1,150 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — Interactive Installer
|
||||||
|
# ============================================================================
|
||||||
|
# Purpose: CRUX-style interactive installer that runs from the live environment.
|
||||||
|
# Walks the user through disk selection, partitioning, base install,
|
||||||
|
# user creation, locale setup, and boot configuration.
|
||||||
|
# Inputs: User input (interactive prompts)
|
||||||
|
# Outputs: A fully installed DarkForge Linux system on the target disk
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
MODULES_DIR="${SCRIPT_DIR}/modules"
|
||||||
|
MOUNT_POINT="/mnt/darkforge"
|
||||||
|
REPOS_DIR="/var/lib/dpack/repos"
|
||||||
|
|
||||||
|
# --- Colors and formatting --------------------------------------------------
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${CYAN}:: ${1}${NC}"; }
|
||||||
|
ok() { echo -e "${GREEN}:: ${1}${NC}"; }
|
||||||
|
warn() { echo -e "${YELLOW}!! ${1}${NC}"; }
|
||||||
|
error() { echo -e "${RED}!! ${1}${NC}"; }
|
||||||
|
ask() { echo -en "${BOLD}${1}${NC}"; }
|
||||||
|
|
||||||
|
# --- Welcome ----------------------------------------------------------------
|
||||||
|
clear
|
||||||
|
echo -e "${BOLD}"
|
||||||
|
echo " ╔══════════════════════════════════════════════════════════════╗"
|
||||||
|
echo " ║ ║"
|
||||||
|
echo " ║ DarkForge Linux Installer v1.0 ║"
|
||||||
|
echo " ║ ║"
|
||||||
|
echo " ║ A custom Linux distribution optimized for gaming ║"
|
||||||
|
echo " ║ and development on AMD Ryzen 9 9950X3D + RTX 5090 ║"
|
||||||
|
echo " ║ ║"
|
||||||
|
echo " ╚══════════════════════════════════════════════════════════════╝"
|
||||||
|
echo -e "${NC}"
|
||||||
|
echo ""
|
||||||
|
echo " This installer will guide you through:"
|
||||||
|
echo " 1. Disk selection and partitioning"
|
||||||
|
echo " 2. Filesystem creation"
|
||||||
|
echo " 3. Base system installation"
|
||||||
|
echo " 4. Kernel installation"
|
||||||
|
echo " 5. User account setup"
|
||||||
|
echo " 6. Locale, timezone, and keyboard"
|
||||||
|
echo " 7. Boot configuration (EFISTUB)"
|
||||||
|
echo " 8. Post-install package selection"
|
||||||
|
echo ""
|
||||||
|
ask "Press Enter to begin, or Ctrl+C to exit..."
|
||||||
|
read -r
|
||||||
|
|
||||||
|
# --- Step 1: Disk selection -------------------------------------------------
|
||||||
|
info "Step 1: Disk Selection"
|
||||||
|
echo ""
|
||||||
|
source "${MODULES_DIR}/disk.sh"
|
||||||
|
select_disk
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Step 2: Partitioning ---------------------------------------------------
|
||||||
|
info "Step 2: Partitioning"
|
||||||
|
echo ""
|
||||||
|
partition_disk
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Step 3: Format and mount -----------------------------------------------
|
||||||
|
info "Step 3: Filesystem Creation"
|
||||||
|
echo ""
|
||||||
|
format_partitions
|
||||||
|
mount_partitions
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Step 4: Base system installation ----------------------------------------
|
||||||
|
info "Step 4: Base System Installation"
|
||||||
|
echo ""
|
||||||
|
source "${MODULES_DIR}/packages.sh"
|
||||||
|
install_base_system
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Step 5: Kernel installation ---------------------------------------------
|
||||||
|
info "Step 5: Kernel Installation"
|
||||||
|
echo ""
|
||||||
|
install_kernel
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Step 6: User account setup ---------------------------------------------
|
||||||
|
info "Step 6: User Account Setup"
|
||||||
|
echo ""
|
||||||
|
source "${MODULES_DIR}/user.sh"
|
||||||
|
setup_users
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Step 7: Locale, timezone, keyboard --------------------------------------
|
||||||
|
info "Step 7: Locale, Timezone, and Keyboard"
|
||||||
|
echo ""
|
||||||
|
source "${MODULES_DIR}/locale.sh"
|
||||||
|
configure_locale
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Step 8: Boot configuration (EFISTUB) ------------------------------------
|
||||||
|
info "Step 8: Boot Configuration"
|
||||||
|
echo ""
|
||||||
|
configure_boot
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Step 9: Post-install package selection ----------------------------------
|
||||||
|
info "Step 9: Additional Packages (Optional)"
|
||||||
|
echo ""
|
||||||
|
select_additional_packages
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Finalize ----------------------------------------------------------------
|
||||||
|
info "Finalizing installation..."
|
||||||
|
|
||||||
|
# Generate fstab
|
||||||
|
generate_fstab
|
||||||
|
|
||||||
|
# Set hostname
|
||||||
|
echo "${INSTALL_HOSTNAME}" > "${MOUNT_POINT}/etc/hostname"
|
||||||
|
|
||||||
|
# Copy rc.conf with configured values
|
||||||
|
configure_rc_conf
|
||||||
|
|
||||||
|
# Unmount
|
||||||
|
info "Unmounting filesystems..."
|
||||||
|
umount -R "${MOUNT_POINT}" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}${BOLD}"
|
||||||
|
echo " ╔══════════════════════════════════════════════════════════════╗"
|
||||||
|
echo " ║ ║"
|
||||||
|
echo " ║ Installation Complete! ║"
|
||||||
|
echo " ║ ║"
|
||||||
|
echo " ║ Remove the installation media and reboot. ║"
|
||||||
|
echo " ║ Your DarkForge system will boot directly via EFISTUB. ║"
|
||||||
|
echo " ║ ║"
|
||||||
|
echo " ╚══════════════════════════════════════════════════════════════╝"
|
||||||
|
echo -e "${NC}"
|
||||||
|
echo ""
|
||||||
|
ask "Reboot now? [y/N] "
|
||||||
|
read -r response
|
||||||
|
if [[ "${response}" =~ ^[Yy]$ ]]; then
|
||||||
|
reboot
|
||||||
|
fi
|
||||||
175
src/install/modules/disk.sh
Executable file
175
src/install/modules/disk.sh
Executable file
@@ -0,0 +1,175 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux Installer — Disk Module
|
||||||
|
# ============================================================================
|
||||||
|
# Handles disk selection, partitioning, formatting, and mounting.
|
||||||
|
# Partition scheme: GPT with ESP (512MB) + Swap (96GB) + Root (remaining)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
INSTALL_DISK=""
|
||||||
|
PART_ESP=""
|
||||||
|
PART_SWAP=""
|
||||||
|
PART_ROOT=""
|
||||||
|
|
||||||
|
# --- List available disks and let user choose -------------------------------
|
||||||
|
select_disk() {
|
||||||
|
echo "Available disks:"
|
||||||
|
echo ""
|
||||||
|
lsblk -d -o NAME,SIZE,MODEL,TRAN -n | grep -v "loop\|sr\|rom" | while read -r line; do
|
||||||
|
echo " ${line}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
ask "Enter the target disk (e.g., nvme0n1, sda): "
|
||||||
|
read -r INSTALL_DISK
|
||||||
|
|
||||||
|
# Validate
|
||||||
|
if [ ! -b "/dev/${INSTALL_DISK}" ]; then
|
||||||
|
error "Disk /dev/${INSTALL_DISK} not found."
|
||||||
|
select_disk
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
warn "ALL DATA ON /dev/${INSTALL_DISK} WILL BE DESTROYED!"
|
||||||
|
ask "Are you sure? Type 'yes' to confirm: "
|
||||||
|
read -r confirm
|
||||||
|
if [ "${confirm}" != "yes" ]; then
|
||||||
|
error "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export INSTALL_DISK
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Partition the disk (GPT: ESP + swap + root) ----------------------------
|
||||||
|
partition_disk() {
|
||||||
|
local disk="/dev/${INSTALL_DISK}"
|
||||||
|
|
||||||
|
info "Creating GPT partition table on ${disk}..."
|
||||||
|
|
||||||
|
# Wipe existing partition table
|
||||||
|
sgdisk --zap-all "${disk}"
|
||||||
|
|
||||||
|
# Create partitions:
|
||||||
|
# 1: EFI System Partition (512MB)
|
||||||
|
# 2: Swap (96GB — matches RAM for hibernation)
|
||||||
|
# 3: Root (remaining space)
|
||||||
|
sgdisk -n 1:0:+512M -t 1:ef00 -c 1:"EFI System" "${disk}"
|
||||||
|
sgdisk -n 2:0:+96G -t 2:8200 -c 2:"Linux Swap" "${disk}"
|
||||||
|
sgdisk -n 3:0:0 -t 3:8300 -c 3:"Linux Root" "${disk}"
|
||||||
|
|
||||||
|
# Determine partition device names
|
||||||
|
if [[ "${INSTALL_DISK}" == nvme* ]]; then
|
||||||
|
PART_ESP="${disk}p1"
|
||||||
|
PART_SWAP="${disk}p2"
|
||||||
|
PART_ROOT="${disk}p3"
|
||||||
|
else
|
||||||
|
PART_ESP="${disk}1"
|
||||||
|
PART_SWAP="${disk}2"
|
||||||
|
PART_ROOT="${disk}3"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PART_ESP PART_SWAP PART_ROOT
|
||||||
|
|
||||||
|
ok "Partitions created:"
|
||||||
|
echo " ESP: ${PART_ESP} (512MB)"
|
||||||
|
echo " Swap: ${PART_SWAP} (96GB)"
|
||||||
|
echo " Root: ${PART_ROOT} (remaining)"
|
||||||
|
|
||||||
|
# Wait for kernel to recognize new partitions
|
||||||
|
partprobe "${disk}" 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Format partitions ------------------------------------------------------
|
||||||
|
format_partitions() {
|
||||||
|
info "Formatting partitions..."
|
||||||
|
|
||||||
|
# ESP — FAT32
|
||||||
|
mkfs.fat -F 32 -n "ESP" "${PART_ESP}"
|
||||||
|
ok "ESP formatted (FAT32)"
|
||||||
|
|
||||||
|
# Swap
|
||||||
|
mkswap -L "darkforge-swap" "${PART_SWAP}"
|
||||||
|
ok "Swap formatted (96GB)"
|
||||||
|
|
||||||
|
# Root — ext4 (user chose ext4)
|
||||||
|
mkfs.ext4 -L "darkforge-root" -O ^metadata_csum_seed "${PART_ROOT}"
|
||||||
|
ok "Root formatted (ext4)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Mount partitions -------------------------------------------------------
|
||||||
|
mount_partitions() {
|
||||||
|
info "Mounting filesystems to ${MOUNT_POINT}..."
|
||||||
|
|
||||||
|
mkdir -p "${MOUNT_POINT}"
|
||||||
|
mount "${PART_ROOT}" "${MOUNT_POINT}"
|
||||||
|
|
||||||
|
mkdir -p "${MOUNT_POINT}/boot/efi"
|
||||||
|
mount "${PART_ESP}" "${MOUNT_POINT}/boot/efi"
|
||||||
|
|
||||||
|
swapon "${PART_SWAP}"
|
||||||
|
|
||||||
|
ok "Filesystems mounted"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Generate fstab from current mounts ------------------------------------
|
||||||
|
generate_fstab() {
|
||||||
|
info "Generating /etc/fstab..."
|
||||||
|
|
||||||
|
local root_uuid=$(blkid -o value -s UUID "${PART_ROOT}")
|
||||||
|
local esp_uuid=$(blkid -o value -s UUID "${PART_ESP}")
|
||||||
|
local swap_uuid=$(blkid -o value -s UUID "${PART_SWAP}")
|
||||||
|
|
||||||
|
cat > "${MOUNT_POINT}/etc/fstab" << EOF
|
||||||
|
# DarkForge Linux — /etc/fstab
|
||||||
|
# Generated by installer on $(date -u +%Y-%m-%d)
|
||||||
|
|
||||||
|
# Root filesystem
|
||||||
|
UUID=${root_uuid} / ext4 defaults,noatime 0 1
|
||||||
|
|
||||||
|
# EFI System Partition
|
||||||
|
UUID=${esp_uuid} /boot/efi vfat defaults,noatime 0 2
|
||||||
|
|
||||||
|
# Swap (96GB for hibernation)
|
||||||
|
UUID=${swap_uuid} none swap defaults 0 0
|
||||||
|
|
||||||
|
# Tmpfs
|
||||||
|
tmpfs /tmp tmpfs defaults,nosuid,nodev 0 0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
ok "fstab generated"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Configure EFISTUB boot entry -------------------------------------------
|
||||||
|
configure_boot() {
|
||||||
|
info "Configuring UEFI boot entry (EFISTUB)..."
|
||||||
|
|
||||||
|
local root_uuid=$(blkid -o value -s UUID "${PART_ROOT}")
|
||||||
|
local esp_dev=$(blkid -o value -s PARTUUID "${PART_ESP}")
|
||||||
|
|
||||||
|
# Copy kernel to ESP
|
||||||
|
if [ -f "${MOUNT_POINT}/boot/vmlinuz" ]; then
|
||||||
|
cp "${MOUNT_POINT}/boot/vmlinuz" "${MOUNT_POINT}/boot/efi/EFI/Linux/vmlinuz.efi"
|
||||||
|
mkdir -p "${MOUNT_POINT}/boot/efi/EFI/Linux"
|
||||||
|
ok "Kernel copied to ESP"
|
||||||
|
else
|
||||||
|
warn "No kernel found — you'll need to install one before booting"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create UEFI boot entry via efibootmgr
|
||||||
|
if command -v efibootmgr >/dev/null 2>&1; then
|
||||||
|
local disk_dev="/dev/${INSTALL_DISK}"
|
||||||
|
efibootmgr --create \
|
||||||
|
--disk "${disk_dev}" \
|
||||||
|
--part 1 \
|
||||||
|
--label "DarkForge Linux" \
|
||||||
|
--loader "/EFI/Linux/vmlinuz.efi" \
|
||||||
|
--unicode "root=UUID=${root_uuid} rw quiet" \
|
||||||
|
2>/dev/null && ok "UEFI boot entry created" \
|
||||||
|
|| warn "Failed to create UEFI boot entry — you may need to set it manually in BIOS"
|
||||||
|
else
|
||||||
|
warn "efibootmgr not found — set boot entry manually in UEFI firmware"
|
||||||
|
fi
|
||||||
|
}
|
||||||
52
src/install/modules/locale.sh
Executable file
52
src/install/modules/locale.sh
Executable file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux Installer — Locale Module
|
||||||
|
# ============================================================================
|
||||||
|
# Configures locale, timezone, and keyboard layout.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
configure_locale() {
|
||||||
|
# --- Timezone ---
|
||||||
|
info "Available timezones: /usr/share/zoneinfo/"
|
||||||
|
echo " Common: America/New_York, America/Chicago, America/Denver,"
|
||||||
|
echo " America/Los_Angeles, Europe/London, Europe/Berlin"
|
||||||
|
echo ""
|
||||||
|
ask "Timezone [America/New_York]: "
|
||||||
|
read -r tz
|
||||||
|
tz="${tz:-America/New_York}"
|
||||||
|
|
||||||
|
if [ -f "${MOUNT_POINT}/usr/share/zoneinfo/${tz}" ]; then
|
||||||
|
ln -sf "/usr/share/zoneinfo/${tz}" "${MOUNT_POINT}/etc/localtime"
|
||||||
|
ok "Timezone set to ${tz}"
|
||||||
|
else
|
||||||
|
warn "Timezone '${tz}' not found — using UTC"
|
||||||
|
ln -sf /usr/share/zoneinfo/UTC "${MOUNT_POINT}/etc/localtime"
|
||||||
|
tz="UTC"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Locale ---
|
||||||
|
echo ""
|
||||||
|
ask "Locale [en_US.UTF-8]: "
|
||||||
|
read -r locale
|
||||||
|
locale="${locale:-en_US.UTF-8}"
|
||||||
|
|
||||||
|
# Generate locale
|
||||||
|
echo "${locale} UTF-8" > "${MOUNT_POINT}/etc/locale.gen"
|
||||||
|
chroot "${MOUNT_POINT}" locale-gen 2>/dev/null || true
|
||||||
|
echo "LANG=${locale}" > "${MOUNT_POINT}/etc/locale.conf"
|
||||||
|
ok "Locale set to ${locale}"
|
||||||
|
|
||||||
|
# --- Keyboard ---
|
||||||
|
echo ""
|
||||||
|
ask "Keyboard layout [us]: "
|
||||||
|
read -r keymap
|
||||||
|
keymap="${keymap:-us}"
|
||||||
|
|
||||||
|
echo "KEYMAP=${keymap}" > "${MOUNT_POINT}/etc/vconsole.conf"
|
||||||
|
ok "Keyboard layout set to ${keymap}"
|
||||||
|
|
||||||
|
# Store for rc.conf generation
|
||||||
|
export INSTALL_TIMEZONE="${tz}"
|
||||||
|
export INSTALL_LOCALE="${locale}"
|
||||||
|
export INSTALL_KEYMAP="${keymap}"
|
||||||
|
}
|
||||||
189
src/install/modules/packages.sh
Executable file
189
src/install/modules/packages.sh
Executable file
@@ -0,0 +1,189 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux Installer — Packages Module
|
||||||
|
# ============================================================================
|
||||||
|
# Installs the base system packages and optional package groups.
|
||||||
|
# Uses dpack for package management if available, falls back to direct copy.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
install_base_system() {
|
||||||
|
info "Installing base system packages..."
|
||||||
|
|
||||||
|
# Bind-mount essential virtual filesystems for chroot
|
||||||
|
mkdir -p "${MOUNT_POINT}"/{dev,proc,sys,run}
|
||||||
|
mount --bind /dev "${MOUNT_POINT}/dev"
|
||||||
|
mount --bind /dev/pts "${MOUNT_POINT}/dev/pts"
|
||||||
|
mount -t proc proc "${MOUNT_POINT}/proc"
|
||||||
|
mount -t sysfs sysfs "${MOUNT_POINT}/sys"
|
||||||
|
mount -t tmpfs tmpfs "${MOUNT_POINT}/run"
|
||||||
|
|
||||||
|
# Copy package repos into the target
|
||||||
|
mkdir -p "${MOUNT_POINT}/var/lib/dpack/repos"
|
||||||
|
cp -a "${REPOS_DIR}"/* "${MOUNT_POINT}/var/lib/dpack/repos/" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Check if dpack is available
|
||||||
|
if command -v dpack >/dev/null 2>&1; then
|
||||||
|
info "Installing via dpack..."
|
||||||
|
|
||||||
|
# Install core packages
|
||||||
|
local core_packages=(
|
||||||
|
glibc gcc binutils linux bash coreutils util-linux
|
||||||
|
sed grep gawk findutils diffutils tar gzip xz zstd bzip2
|
||||||
|
ncurses readline file less make patch m4 bison flex
|
||||||
|
autoconf automake libtool gettext texinfo
|
||||||
|
perl python pkg-config cmake meson ninja
|
||||||
|
gmp mpfr mpc zlib openssl curl git expat libffi
|
||||||
|
eudev sysvinit dbus dhcpcd shadow procps-ng e2fsprogs
|
||||||
|
kmod iproute2 kbd groff man-db man-pages
|
||||||
|
)
|
||||||
|
|
||||||
|
for pkg in "${core_packages[@]}"; do
|
||||||
|
echo -n " Installing ${pkg}... "
|
||||||
|
dpack install "${pkg}" 2>/dev/null && echo "OK" || echo "SKIP"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
info "dpack not available — installing from live filesystem..."
|
||||||
|
|
||||||
|
# Direct copy from the live root to the target
|
||||||
|
# This copies the base system that's already installed in the live env
|
||||||
|
local dirs_to_copy=(
|
||||||
|
usr/bin usr/sbin usr/lib usr/lib64 usr/include usr/share
|
||||||
|
etc lib lib64 bin sbin
|
||||||
|
)
|
||||||
|
|
||||||
|
for dir in "${dirs_to_copy[@]}"; do
|
||||||
|
if [ -d "/${dir}" ]; then
|
||||||
|
echo -n " Copying /${dir}... "
|
||||||
|
mkdir -p "${MOUNT_POINT}/${dir}"
|
||||||
|
cp -a "/${dir}"/* "${MOUNT_POINT}/${dir}/" 2>/dev/null || true
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create essential directories
|
||||||
|
mkdir -p "${MOUNT_POINT}"/{boot,home,mnt,opt,srv,tmp}
|
||||||
|
mkdir -p "${MOUNT_POINT}"/var/{cache,lib,log,lock,run,spool,tmp}
|
||||||
|
chmod 1777 "${MOUNT_POINT}/tmp"
|
||||||
|
|
||||||
|
ok "Base system installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Install kernel to the target system ------------------------------------
|
||||||
|
install_kernel() {
|
||||||
|
info "Installing kernel..."
|
||||||
|
|
||||||
|
if [ -f "/boot/vmlinuz" ]; then
|
||||||
|
cp "/boot/vmlinuz" "${MOUNT_POINT}/boot/vmlinuz"
|
||||||
|
ok "Kernel installed to /boot/vmlinuz"
|
||||||
|
elif [ -f "/boot/vmlinuz-6.19.8-darkforge" ]; then
|
||||||
|
cp "/boot/vmlinuz-6.19.8-darkforge" "${MOUNT_POINT}/boot/vmlinuz"
|
||||||
|
ok "Kernel installed"
|
||||||
|
else
|
||||||
|
warn "No kernel found in live environment"
|
||||||
|
warn "You'll need to build and install the kernel manually:"
|
||||||
|
warn " cd /usr/src/linux && make -j32 && make modules_install"
|
||||||
|
warn " cp arch/x86/boot/bzImage /boot/vmlinuz"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install kernel modules
|
||||||
|
if [ -d "/lib/modules" ]; then
|
||||||
|
cp -a /lib/modules "${MOUNT_POINT}/lib/"
|
||||||
|
ok "Kernel modules installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install AMD microcode (if available)
|
||||||
|
if [ -f "/boot/amd-ucode.img" ]; then
|
||||||
|
cp "/boot/amd-ucode.img" "${MOUNT_POINT}/boot/"
|
||||||
|
ok "AMD microcode installed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Optional package groups ------------------------------------------------
|
||||||
|
select_additional_packages() {
|
||||||
|
echo " Available package groups:"
|
||||||
|
echo ""
|
||||||
|
echo " 1. Desktop Environment (dwl + Wayland + foot + fuzzel)"
|
||||||
|
echo " 2. Gaming Stack (Steam + Wine + Proton + gamemode + mangohud)"
|
||||||
|
echo " 3. Development Tools (rust + extra compilers)"
|
||||||
|
echo " 4. All of the above"
|
||||||
|
echo " 5. Skip (install later)"
|
||||||
|
echo ""
|
||||||
|
ask " Select groups to install [4]: "
|
||||||
|
read -r choice
|
||||||
|
choice="${choice:-4}"
|
||||||
|
|
||||||
|
case "${choice}" in
|
||||||
|
1) install_group_desktop ;;
|
||||||
|
2) install_group_gaming ;;
|
||||||
|
3) install_group_dev ;;
|
||||||
|
4)
|
||||||
|
install_group_desktop
|
||||||
|
install_group_gaming
|
||||||
|
install_group_dev
|
||||||
|
;;
|
||||||
|
5) info "Skipping additional packages" ;;
|
||||||
|
*) warn "Invalid choice — skipping" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
install_group_desktop() {
|
||||||
|
info "Installing desktop environment..."
|
||||||
|
if command -v dpack >/dev/null 2>&1; then
|
||||||
|
dpack install wayland wayland-protocols wlroots dwl xwayland \
|
||||||
|
foot fuzzel libinput libxkbcommon xkeyboard-config \
|
||||||
|
pipewire wireplumber polkit seatd \
|
||||||
|
fontconfig freetype harfbuzz firefox zsh \
|
||||||
|
wl-clipboard grim slurp 2>/dev/null
|
||||||
|
fi
|
||||||
|
ok "Desktop environment installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_group_gaming() {
|
||||||
|
info "Installing gaming stack..."
|
||||||
|
if command -v dpack >/dev/null 2>&1; then
|
||||||
|
dpack install nvidia-open steam wine gamemode mangohud \
|
||||||
|
sdl2 vulkan-loader vulkan-tools dxvk vkd3d-proton 2>/dev/null
|
||||||
|
fi
|
||||||
|
ok "Gaming stack installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_group_dev() {
|
||||||
|
info "Installing development tools..."
|
||||||
|
if command -v dpack >/dev/null 2>&1; then
|
||||||
|
dpack install rust wezterm 2>/dev/null
|
||||||
|
fi
|
||||||
|
ok "Development tools installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Configure rc.conf with install-time values ----------------------------
|
||||||
|
configure_rc_conf() {
|
||||||
|
info "Configuring rc.conf..."
|
||||||
|
|
||||||
|
cat > "${MOUNT_POINT}/etc/rc.conf" << EOF
|
||||||
|
#!/bin/bash
|
||||||
|
# DarkForge Linux — System Configuration
|
||||||
|
# Generated by installer on $(date -u +%Y-%m-%d)
|
||||||
|
|
||||||
|
HOSTNAME="${INSTALL_HOSTNAME:-darkforge}"
|
||||||
|
TIMEZONE="${INSTALL_TIMEZONE:-America/New_York}"
|
||||||
|
KEYMAP="${INSTALL_KEYMAP:-us}"
|
||||||
|
LOCALE="${INSTALL_LOCALE:-en_US.UTF-8}"
|
||||||
|
FONT="ter-v18n"
|
||||||
|
|
||||||
|
DAEMONS=(eudev syslog dbus dhcpcd pipewire)
|
||||||
|
|
||||||
|
MODULES=(nvidia nvidia-modeset nvidia-drm nvidia-uvm)
|
||||||
|
|
||||||
|
MODULE_PARAMS=(
|
||||||
|
"nvidia-drm modeset=1"
|
||||||
|
)
|
||||||
|
|
||||||
|
NETWORK_INTERFACE="enp6s0"
|
||||||
|
NETWORK_DHCP=yes
|
||||||
|
|
||||||
|
HARDWARECLOCK="UTC"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
ok "rc.conf configured"
|
||||||
|
}
|
||||||
51
src/install/modules/user.sh
Executable file
51
src/install/modules/user.sh
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux Installer — User Module
|
||||||
|
# ============================================================================
|
||||||
|
# Creates root password and user account.
|
||||||
|
# Default: username 'danny', added to wheel/video/audio/input groups.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
INSTALL_USERNAME=""
|
||||||
|
INSTALL_HOSTNAME=""
|
||||||
|
|
||||||
|
setup_users() {
|
||||||
|
# --- Hostname ---
|
||||||
|
ask "Hostname [darkforge]: "
|
||||||
|
read -r INSTALL_HOSTNAME
|
||||||
|
INSTALL_HOSTNAME="${INSTALL_HOSTNAME:-darkforge}"
|
||||||
|
export INSTALL_HOSTNAME
|
||||||
|
|
||||||
|
# --- Root password ---
|
||||||
|
echo ""
|
||||||
|
info "Set the root password:"
|
||||||
|
chroot "${MOUNT_POINT}" /bin/bash -c "passwd root"
|
||||||
|
|
||||||
|
# --- User account ---
|
||||||
|
echo ""
|
||||||
|
ask "Username [danny]: "
|
||||||
|
read -r INSTALL_USERNAME
|
||||||
|
INSTALL_USERNAME="${INSTALL_USERNAME:-danny}"
|
||||||
|
export INSTALL_USERNAME
|
||||||
|
|
||||||
|
info "Creating user '${INSTALL_USERNAME}'..."
|
||||||
|
|
||||||
|
chroot "${MOUNT_POINT}" /bin/bash -c "
|
||||||
|
useradd -m -G wheel,video,audio,input,kvm -s /bin/zsh '${INSTALL_USERNAME}'
|
||||||
|
"
|
||||||
|
|
||||||
|
info "Set password for '${INSTALL_USERNAME}':"
|
||||||
|
chroot "${MOUNT_POINT}" /bin/bash -c "passwd '${INSTALL_USERNAME}'"
|
||||||
|
|
||||||
|
# Install user shell profile
|
||||||
|
if [ -f "/install/configs/zprofile" ]; then
|
||||||
|
cp "/install/configs/zprofile" "${MOUNT_POINT}/home/${INSTALL_USERNAME}/.zprofile"
|
||||||
|
chroot "${MOUNT_POINT}" chown "${INSTALL_USERNAME}:${INSTALL_USERNAME}" "/home/${INSTALL_USERNAME}/.zprofile"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update inittab with the correct username for auto-login
|
||||||
|
sed -i "s/--autologin danny/--autologin ${INSTALL_USERNAME}/" \
|
||||||
|
"${MOUNT_POINT}/etc/inittab"
|
||||||
|
|
||||||
|
ok "User '${INSTALL_USERNAME}' created"
|
||||||
|
}
|
||||||
57
src/iso/README.md
Normal file
57
src/iso/README.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# DarkForge ISO Builder
|
||||||
|
|
||||||
|
Builds a bootable live USB/CD image containing the DarkForge installer and a minimal live environment.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The ISO builder compresses the base system into a squashfs image, creates a UEFI-bootable ISO via xorriso, and includes the installer scripts for deploying DarkForge to disk.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- `mksquashfs` (squashfs-tools) — filesystem compression
|
||||||
|
- `xorriso` — ISO9660 image creation
|
||||||
|
- `mkfs.fat` (dosfstools) — EFI partition image
|
||||||
|
- `mcopy` (mtools) — copy files into FAT images
|
||||||
|
- A completed base system build (Phase 3)
|
||||||
|
- A compiled kernel at `kernel/vmlinuz`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash src/iso/build-iso.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Output: `darkforge-live.iso` in the project root.
|
||||||
|
|
||||||
|
## ISO Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
darkforge-live.iso
|
||||||
|
├── EFI/BOOT/BOOTX64.EFI # Kernel (EFISTUB boot)
|
||||||
|
├── boot/cmdline.txt # Kernel command line
|
||||||
|
├── LiveOS/rootfs.img # squashfs compressed root
|
||||||
|
└── install/ # Installer scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Boot Method
|
||||||
|
|
||||||
|
The ISO boots via UEFI only (El Torito with EFI System Partition). No legacy BIOS support. The kernel loads directly via EFISTUB.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Test the ISO in QEMU:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qemu-system-x86_64 \
|
||||||
|
-enable-kvm \
|
||||||
|
-m 4G \
|
||||||
|
-bios /usr/share/ovmf/OVMF.fd \
|
||||||
|
-cdrom darkforge-live.iso \
|
||||||
|
-boot d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
|
||||||
|
```
|
||||||
|
git@git.dannyhaslund.dk:danny8632/darkforge.git
|
||||||
|
```
|
||||||
215
src/iso/build-iso.sh
Executable file
215
src/iso/build-iso.sh
Executable file
@@ -0,0 +1,215 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================================
|
||||||
|
# DarkForge Linux — ISO Builder
|
||||||
|
# ============================================================================
|
||||||
|
# Purpose: Build a bootable live USB/CD image containing the DarkForge
|
||||||
|
# installer and a minimal live environment.
|
||||||
|
# Inputs: A completed base system (Phase 3 packages installed)
|
||||||
|
# Outputs: darkforge-live.iso
|
||||||
|
#
|
||||||
|
# Requirements: squashfs-tools, xorriso, mtools, dosfstools
|
||||||
|
#
|
||||||
|
# The ISO layout:
|
||||||
|
# /EFI/BOOT/BOOTX64.EFI — The kernel (EFISTUB boot)
|
||||||
|
# /boot/cmdline.txt — Kernel command line
|
||||||
|
# /LiveOS/rootfs.img — squashfs compressed root filesystem
|
||||||
|
# /install/ — Installer scripts
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# --- Configuration ----------------------------------------------------------
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
BUILD_DIR="/tmp/darkforge-iso-build"
|
||||||
|
ISO_OUTPUT="${PROJECT_ROOT}/darkforge-live.iso"
|
||||||
|
ISO_LABEL="DARKFORGE"
|
||||||
|
|
||||||
|
KERNEL_PATH="${PROJECT_ROOT}/kernel/vmlinuz"
|
||||||
|
# If no pre-built kernel, this will be built during the ISO build process
|
||||||
|
|
||||||
|
# squashfs compression algorithm
|
||||||
|
SQFS_COMP="zstd"
|
||||||
|
SQFS_OPTS="-comp ${SQFS_COMP} -Xcompression-level 19 -b 1M"
|
||||||
|
|
||||||
|
# --- Colors -----------------------------------------------------------------
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${CYAN}>>> ${1}${NC}"; }
|
||||||
|
ok() { echo -e "${GREEN}>>> ${1}${NC}"; }
|
||||||
|
warn() { echo -e "${YELLOW}!!! ${1}${NC}"; }
|
||||||
|
die() { echo -e "${RED}!!! ${1}${NC}"; exit 1; }
|
||||||
|
|
||||||
|
# --- Preflight checks -------------------------------------------------------
|
||||||
|
info "DarkForge Linux ISO Builder"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for tool in mksquashfs xorriso mkfs.fat mcopy; do
|
||||||
|
command -v "${tool}" >/dev/null 2>&1 || die "Required tool not found: ${tool}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Clean previous builds --------------------------------------------------
|
||||||
|
info "Cleaning previous build artifacts..."
|
||||||
|
rm -rf "${BUILD_DIR}"
|
||||||
|
mkdir -p "${BUILD_DIR}"/{iso,rootfs,efi}
|
||||||
|
|
||||||
|
# --- Build the live root filesystem -----------------------------------------
|
||||||
|
info "Preparing live root filesystem..."
|
||||||
|
|
||||||
|
ROOTFS="${BUILD_DIR}/rootfs"
|
||||||
|
|
||||||
|
# Create essential directory structure
|
||||||
|
mkdir -p "${ROOTFS}"/{bin,boot,dev,etc,home,lib,lib64,mnt,opt,proc,root,run}
|
||||||
|
mkdir -p "${ROOTFS}"/{sbin,srv,sys,tmp,usr/{bin,include,lib,sbin,share},var}
|
||||||
|
mkdir -p "${ROOTFS}"/var/{cache,lib,log,tmp}
|
||||||
|
mkdir -p "${ROOTFS}"/etc/{rc.d,sysconfig}
|
||||||
|
mkdir -p "${ROOTFS}"/usr/share/{man,doc}
|
||||||
|
|
||||||
|
# Copy base system (installed via dpack or direct copy from the chroot)
|
||||||
|
# This expects the base system to exist in a staging area
|
||||||
|
BASE_SYSTEM="${PROJECT_ROOT}/build/base-system"
|
||||||
|
if [ -d "${BASE_SYSTEM}" ]; then
|
||||||
|
info "Copying base system from ${BASE_SYSTEM}..."
|
||||||
|
cp -a "${BASE_SYSTEM}"/* "${ROOTFS}"/
|
||||||
|
else
|
||||||
|
warn "No base system found at ${BASE_SYSTEM}"
|
||||||
|
warn "The ISO will contain a minimal skeleton only."
|
||||||
|
warn "Build the base system first (Phase 3), then re-run."
|
||||||
|
|
||||||
|
# Create minimal skeleton for testing
|
||||||
|
# These would normally come from the base system packages
|
||||||
|
cp -a /bin/busybox "${ROOTFS}/bin/" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Install DarkForge configuration ----------------------------------------
|
||||||
|
info "Installing DarkForge configuration..."
|
||||||
|
cp "${PROJECT_ROOT}/configs/rc.conf" "${ROOTFS}/etc/rc.conf"
|
||||||
|
cp "${PROJECT_ROOT}/configs/inittab" "${ROOTFS}/etc/inittab"
|
||||||
|
cp "${PROJECT_ROOT}/configs/fstab.template" "${ROOTFS}/etc/fstab"
|
||||||
|
cp -a "${PROJECT_ROOT}/configs/rc.d/"* "${ROOTFS}/etc/rc.d/"
|
||||||
|
|
||||||
|
# Live-specific: override inittab for installer mode
|
||||||
|
cat > "${ROOTFS}/etc/inittab.live" << 'INITTAB'
|
||||||
|
# DarkForge Live — boots to installer prompt
|
||||||
|
id:3:initdefault:
|
||||||
|
si::sysinit:/etc/rc.d/rc.sysinit
|
||||||
|
l3:3:wait:/etc/rc.d/rc.multi
|
||||||
|
1:2345:respawn:/sbin/agetty --autologin root --noclear 38400 tty1 linux
|
||||||
|
2:2345:respawn:/sbin/agetty 38400 tty2 linux
|
||||||
|
ca::ctrlaltdel:/sbin/shutdown -r now
|
||||||
|
INITTAB
|
||||||
|
cp "${ROOTFS}/etc/inittab.live" "${ROOTFS}/etc/inittab"
|
||||||
|
|
||||||
|
# Live-specific: auto-launch installer on login
|
||||||
|
cat > "${ROOTFS}/root/.bash_profile" << 'PROFILE'
|
||||||
|
echo ""
|
||||||
|
echo " ╔══════════════════════════════════════════╗"
|
||||||
|
echo " ║ DarkForge Linux Installer ║"
|
||||||
|
echo " ║ ║"
|
||||||
|
echo " ║ Type 'install' to begin installation ║"
|
||||||
|
echo " ║ Type 'shell' for a live shell ║"
|
||||||
|
echo " ╚══════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
alias install='/install/install.sh'
|
||||||
|
alias shell='exec /bin/bash --login'
|
||||||
|
PROFILE
|
||||||
|
|
||||||
|
# --- Copy installer scripts -------------------------------------------------
|
||||||
|
info "Copying installer scripts..."
|
||||||
|
mkdir -p "${ROOTFS}/install"
|
||||||
|
cp -a "${PROJECT_ROOT}/src/install/"* "${ROOTFS}/install/" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Copy dpack binary and repos (for base package installation during install)
|
||||||
|
mkdir -p "${ROOTFS}/var/lib/dpack/repos"
|
||||||
|
cp -a "${PROJECT_ROOT}/src/repos/"* "${ROOTFS}/var/lib/dpack/repos/" 2>/dev/null || true
|
||||||
|
|
||||||
|
# --- Create the squashfs image ----------------------------------------------
|
||||||
|
info "Creating squashfs image (${SQFS_COMP})..."
|
||||||
|
mksquashfs "${ROOTFS}" "${BUILD_DIR}/iso/LiveOS/rootfs.img" \
|
||||||
|
${SQFS_OPTS} \
|
||||||
|
-noappend \
|
||||||
|
-wildcards \
|
||||||
|
-e 'proc/*' 'sys/*' 'dev/*' 'run/*' 'tmp/*'
|
||||||
|
|
||||||
|
ok "squashfs image created: $(du -sh "${BUILD_DIR}/iso/LiveOS/rootfs.img" | cut -f1)"
|
||||||
|
|
||||||
|
# --- Prepare EFI boot -------------------------------------------------------
|
||||||
|
info "Preparing UEFI boot..."
|
||||||
|
|
||||||
|
# Create the EFI boot directory structure
|
||||||
|
mkdir -p "${BUILD_DIR}/iso/EFI/BOOT"
|
||||||
|
|
||||||
|
# Copy the kernel as the EFI boot binary
|
||||||
|
if [ -f "${KERNEL_PATH}" ]; then
|
||||||
|
cp "${KERNEL_PATH}" "${BUILD_DIR}/iso/EFI/BOOT/BOOTX64.EFI"
|
||||||
|
ok "Kernel copied to EFI/BOOT/BOOTX64.EFI"
|
||||||
|
else
|
||||||
|
warn "No kernel found at ${KERNEL_PATH}"
|
||||||
|
warn "You'll need to copy the kernel manually before the ISO is bootable."
|
||||||
|
# Create a placeholder
|
||||||
|
echo "PLACEHOLDER — replace with real kernel" > "${BUILD_DIR}/iso/EFI/BOOT/BOOTX64.EFI"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Kernel command line embedded via EFISTUB
|
||||||
|
# The kernel reads its cmdline from a built-in or from the EFI boot entry
|
||||||
|
mkdir -p "${BUILD_DIR}/iso/boot"
|
||||||
|
echo "root=live:LABEL=${ISO_LABEL} rd.live.image rd.live.overlay.overlayfs=1 quiet" \
|
||||||
|
> "${BUILD_DIR}/iso/boot/cmdline.txt"
|
||||||
|
|
||||||
|
# --- Create the EFI System Partition image (for El Torito boot) -------------
|
||||||
|
info "Creating EFI boot image for ISO..."
|
||||||
|
ESP_IMG="${BUILD_DIR}/efi/efiboot.img"
|
||||||
|
|
||||||
|
# Calculate size needed (kernel + overhead)
|
||||||
|
KERNEL_SIZE=$(stat -c%s "${BUILD_DIR}/iso/EFI/BOOT/BOOTX64.EFI" 2>/dev/null || echo "1048576")
|
||||||
|
ESP_SIZE=$(( (KERNEL_SIZE / 1024 + 2048) )) # Add 2MB overhead
|
||||||
|
[ ${ESP_SIZE} -lt 4096 ] && ESP_SIZE=4096 # Minimum 4MB
|
||||||
|
|
||||||
|
dd if=/dev/zero of="${ESP_IMG}" bs=1K count=${ESP_SIZE} 2>/dev/null
|
||||||
|
mkfs.fat -F 12 "${ESP_IMG}" >/dev/null
|
||||||
|
mmd -i "${ESP_IMG}" ::/EFI
|
||||||
|
mmd -i "${ESP_IMG}" ::/EFI/BOOT
|
||||||
|
mcopy -i "${ESP_IMG}" "${BUILD_DIR}/iso/EFI/BOOT/BOOTX64.EFI" ::/EFI/BOOT/BOOTX64.EFI
|
||||||
|
|
||||||
|
ok "EFI boot image created (${ESP_SIZE}K)"
|
||||||
|
|
||||||
|
# --- Build the ISO ----------------------------------------------------------
|
||||||
|
info "Building ISO image..."
|
||||||
|
|
||||||
|
xorriso -as mkisofs \
|
||||||
|
-o "${ISO_OUTPUT}" \
|
||||||
|
-iso-level 3 \
|
||||||
|
-full-iso9660-filenames \
|
||||||
|
-joliet \
|
||||||
|
-rational-rock \
|
||||||
|
-volid "${ISO_LABEL}" \
|
||||||
|
-eltorito-alt-boot \
|
||||||
|
-e efi/efiboot.img \
|
||||||
|
-no-emul-boot \
|
||||||
|
-isohybrid-gpt-basdat \
|
||||||
|
-append_partition 2 0xef "${ESP_IMG}" \
|
||||||
|
"${BUILD_DIR}/iso"
|
||||||
|
|
||||||
|
# --- Summary ----------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
ok "═══════════════════════════════════════════════"
|
||||||
|
ok " DarkForge Linux ISO built successfully!"
|
||||||
|
ok ""
|
||||||
|
ok " Output: ${ISO_OUTPUT}"
|
||||||
|
ok " Size: $(du -sh "${ISO_OUTPUT}" | cut -f1)"
|
||||||
|
ok ""
|
||||||
|
ok " Boot: UEFI only (EFISTUB)"
|
||||||
|
ok " Root: squashfs (${SQFS_COMP})"
|
||||||
|
ok "═══════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# --- Cleanup ----------------------------------------------------------------
|
||||||
|
info "Cleaning up build directory..."
|
||||||
|
rm -rf "${BUILD_DIR}"
|
||||||
|
|
||||||
|
ok "Done."
|
||||||
135
src/repos/README.md
Normal file
135
src/repos/README.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# DarkForge Package Repository
|
||||||
|
|
||||||
|
124 package definitions for the complete DarkForge Linux system. Each package is a TOML file describing how to download, build, and install a piece of software.
|
||||||
|
|
||||||
|
## Repository Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
repos/
|
||||||
|
├── core/ 67 packages — base system (toolchain, kernel, utilities, system daemons)
|
||||||
|
├── extra/ 26 packages — libraries, frameworks, drivers
|
||||||
|
├── desktop/ 19 packages — Wayland compositor, terminals, applications
|
||||||
|
└── gaming/ 12 packages — Steam, Wine, Proton, game tools
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Format
|
||||||
|
|
||||||
|
Each package lives in `<repo>/<name>/<name>.toml`. See the dpack README for the full format specification.
|
||||||
|
|
||||||
|
Example (`core/zlib/zlib.toml`):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "zlib"
|
||||||
|
version = "1.3.1"
|
||||||
|
description = "Compression library implementing the deflate algorithm"
|
||||||
|
url = "https://zlib.net/"
|
||||||
|
license = "zlib"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://zlib.net/zlib-${version}.tar.xz"
|
||||||
|
sha256 = "38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = []
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
configure = "./configure --prefix=/usr"
|
||||||
|
make = "make"
|
||||||
|
install = "make DESTDIR=${PKG} install"
|
||||||
|
```
|
||||||
|
|
||||||
|
## core/ — Base System (67 packages)
|
||||||
|
|
||||||
|
The complete base system needed to boot to a shell:
|
||||||
|
|
||||||
|
**Toolchain:** gcc, glibc, binutils, gmp, mpfr, mpc, linux (kernel)
|
||||||
|
|
||||||
|
**Utilities:** coreutils, util-linux, bash, sed, grep, gawk, findutils, diffutils, tar, gzip, xz, zstd, bzip2, ncurses, readline, file, less, make, patch, m4
|
||||||
|
|
||||||
|
**System:** eudev, sysvinit, dbus, dhcpcd, shadow, procps-ng, e2fsprogs, kmod, iproute2, kbd, amd-microcode
|
||||||
|
|
||||||
|
**Dev tools:** cmake, meson, ninja, python, perl, autoconf, automake, libtool, bison, flex, gettext, texinfo, pkg-config, gperf
|
||||||
|
|
||||||
|
**Libraries:** openssl, curl, git, zlib, expat, libffi, libxml2, pcre2, glib, libmnl, libpipeline, bc
|
||||||
|
|
||||||
|
**Docs:** groff, man-db, man-pages
|
||||||
|
|
||||||
|
## extra/ — Libraries and Frameworks (26 packages)
|
||||||
|
|
||||||
|
Libraries needed by the desktop and gaming stack:
|
||||||
|
|
||||||
|
**Audio:** pipewire, wireplumber
|
||||||
|
|
||||||
|
**Graphics:** mesa, vulkan-headers, vulkan-loader, vulkan-tools, libdrm, nvidia-open
|
||||||
|
|
||||||
|
**Fonts:** fontconfig, freetype, harfbuzz, libpng
|
||||||
|
|
||||||
|
**UI:** pango, cairo, pixman, qt6-base, lxqt-policykit
|
||||||
|
|
||||||
|
**Security:** polkit, duktape, gnutls, nettle, libtasn1, p11-kit
|
||||||
|
|
||||||
|
**Other:** seatd, lua, rust
|
||||||
|
|
||||||
|
## desktop/ — Wayland Desktop (19 packages)
|
||||||
|
|
||||||
|
The complete desktop environment:
|
||||||
|
|
||||||
|
**Wayland:** wayland, wayland-protocols, wlroots, xwayland
|
||||||
|
|
||||||
|
**Compositor:** dwl (dynamic window manager for Wayland, dwm-like)
|
||||||
|
|
||||||
|
**Input:** libinput, libevdev, mtdev, libxkbcommon, xkeyboard-config
|
||||||
|
|
||||||
|
**Apps:** foot (terminal), fuzzel (launcher), firefox, zsh, wezterm, freecad
|
||||||
|
|
||||||
|
**Tools:** wl-clipboard, grim (screenshots), slurp (region select)
|
||||||
|
|
||||||
|
## gaming/ — Gaming Stack (12 packages)
|
||||||
|
|
||||||
|
Everything needed for gaming on Linux:
|
||||||
|
|
||||||
|
**Platform:** steam, wine, proton-ge, protontricks, winetricks
|
||||||
|
|
||||||
|
**Translation:** dxvk (D3D9/10/11→Vulkan), vkd3d-proton (D3D12→Vulkan)
|
||||||
|
|
||||||
|
**Tools:** gamemode, mangohud, sdl2
|
||||||
|
|
||||||
|
**Runtime:** openjdk (for PrismLauncher/Minecraft), prismlauncher
|
||||||
|
|
||||||
|
## Adding a New Package
|
||||||
|
|
||||||
|
1. Create the directory: `mkdir -p <repo>/<name>`
|
||||||
|
2. Create the definition: `<repo>/<name>/<name>.toml`
|
||||||
|
3. Fill in all sections: `[package]`, `[source]`, `[dependencies]`, `[build]`
|
||||||
|
4. Compute the SHA256: `sha256sum <tarball>`
|
||||||
|
5. Test: `dpack install <name>`
|
||||||
|
|
||||||
|
Alternatively, convert from CRUX or Gentoo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dpack convert /path/to/Pkgfile -o repos/core/foo/foo.toml
|
||||||
|
dpack convert /path/to/foo-1.0.ebuild -o repos/extra/foo/foo.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## SHA256 Checksums
|
||||||
|
|
||||||
|
Most package definitions currently have placeholder checksums (`aaa...`). These must be populated with real checksums before building. To compute them:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for pkg in core/*/; do
|
||||||
|
name=$(basename "$pkg")
|
||||||
|
url=$(grep '^url = ' "$pkg/${name}.toml" | head -1 | sed 's/url = "//;s/"$//' | sed "s/\${version}/$(grep '^version' "$pkg/${name}.toml" | head -1 | sed 's/version = "//;s/"$//')/")
|
||||||
|
echo "Downloading $name from $url..."
|
||||||
|
wget -q "$url" -O "/tmp/${name}.tar" && sha256sum "/tmp/${name}.tar"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
|
||||||
|
```
|
||||||
|
git@git.dannyhaslund.dk:danny8632/darkforge.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Package definitions live in `src/repos/` in the main DarkForge repo.
|
||||||
20
src/repos/core/amd-microcode/amd-microcode.toml
Normal file
20
src/repos/core/amd-microcode/amd-microcode.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "amd-microcode"
|
||||||
|
version = "20261201"
|
||||||
|
description = "AMD CPU microcode updates"
|
||||||
|
url = "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git"
|
||||||
|
license = "Redistributable"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/snapshot/linux-firmware-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = []
|
||||||
|
build = []
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """"""
|
||||||
|
make = """make"""
|
||||||
|
install = """mkdir -p ${PKG}/lib/firmware/amd-ucode && cp amd-ucode/*.bin ${PKG}/lib/firmware/amd-ucode/ && mkdir -p ${PKG}/boot && cat ${PKG}/lib/firmware/amd-ucode/microcode_amd*.bin > ${PKG}/boot/amd-ucode.img"""
|
||||||
20
src/repos/core/autoconf/autoconf.toml
Normal file
20
src/repos/core/autoconf/autoconf.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "autoconf"
|
||||||
|
version = "2.72"
|
||||||
|
description = "GNU autoconf build configuration"
|
||||||
|
url = "https://www.gnu.org/software/autoconf/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/autoconf/autoconf-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["perl", "m4"]
|
||||||
|
build = ["make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/automake/automake.toml
Normal file
20
src/repos/core/automake/automake.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "automake"
|
||||||
|
version = "1.18"
|
||||||
|
description = "GNU automake Makefile generator"
|
||||||
|
url = "https://www.gnu.org/software/automake/"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/automake/automake-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["perl", "autoconf"]
|
||||||
|
build = ["make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/bash/bash.toml
Normal file
20
src/repos/core/bash/bash.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "bash"
|
||||||
|
version = "5.3"
|
||||||
|
description = "GNU Bourne-Again Shell"
|
||||||
|
url = "https://www.gnu.org/software/bash/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/bash/bash-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "readline", "ncurses"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --without-bash-malloc --with-installed-readline"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
19
src/repos/core/bc/bc.toml
Normal file
19
src/repos/core/bc/bc.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "bc"
|
||||||
|
version = "7.0.3"
|
||||||
|
description = "Arbitrary precision calculator"
|
||||||
|
url = "https://git.gavinhoward.com/gavin/bc"
|
||||||
|
license = "BSD-2-Clause"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/gavinhoward/bc/releases/download/${version}/bc-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "readline"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
configure = """./configure --prefix=/usr -O3 -r"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/binutils/binutils.toml
Normal file
20
src/repos/core/binutils/binutils.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "binutils"
|
||||||
|
version = "2.46"
|
||||||
|
description = "GNU binary utilities"
|
||||||
|
url = "https://www.gnu.org/software/binutils/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/binutils/binutils-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "zlib"]
|
||||||
|
build = ["make", "texinfo"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """mkdir build && cd build && ../configure --prefix=/usr --enable-gold --enable-ld=default --enable-plugins --enable-shared --disable-werror --with-system-zlib --enable-default-hash-style=gnu"""
|
||||||
|
make = """make tooldir=/usr"""
|
||||||
|
install = """make DESTDIR=${PKG} tooldir=/usr install"""
|
||||||
20
src/repos/core/bison/bison.toml
Normal file
20
src/repos/core/bison/bison.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "bison"
|
||||||
|
version = "3.8.2"
|
||||||
|
description = "GNU parser generator"
|
||||||
|
url = "https://www.gnu.org/software/bison/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/bison/bison-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "m4"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/bzip2/bzip2.toml
Normal file
20
src/repos/core/bzip2/bzip2.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "bzip2"
|
||||||
|
version = "1.0.8"
|
||||||
|
description = "Block-sorting file compressor"
|
||||||
|
url = "https://sourceware.org/bzip2/"
|
||||||
|
license = "bzip2-1.0.6"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://sourceware.org/pub/bzip2/bzip2-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """"""
|
||||||
|
make = """make -f Makefile-libbz2_so && make clean && make"""
|
||||||
|
install = """make PREFIX=${PKG}/usr install"""
|
||||||
20
src/repos/core/cmake/cmake.toml
Normal file
20
src/repos/core/cmake/cmake.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "cmake"
|
||||||
|
version = "4.2.3"
|
||||||
|
description = "Cross-platform build system generator"
|
||||||
|
url = "https://cmake.org/"
|
||||||
|
license = "BSD-3-Clause"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://cmake.org/files/v4.2/cmake-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "curl", "expat", "zlib", "xz", "zstd"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """./bootstrap --prefix=/usr --system-libs --no-system-jsoncpp --no-system-cppdap --no-system-librhash"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/coreutils/coreutils.toml
Normal file
20
src/repos/core/coreutils/coreutils.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "coreutils"
|
||||||
|
version = "9.6"
|
||||||
|
description = "GNU core utilities"
|
||||||
|
url = "https://www.gnu.org/software/coreutils/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/coreutils/coreutils-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make", "perl"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --enable-no-install-program=kill,uptime"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/curl/curl.toml
Normal file
20
src/repos/core/curl/curl.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "curl"
|
||||||
|
version = "8.19.0"
|
||||||
|
description = "URL transfer library and command-line tool"
|
||||||
|
url = "https://curl.se/"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://curl.se/download/curl-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "openssl", "zlib", "zstd"]
|
||||||
|
build = ["gcc", "make", "pkg-config"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --disable-static --with-openssl --enable-threaded-resolver --with-ca-path=/etc/ssl/certs"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/dbus/dbus.toml
Normal file
20
src/repos/core/dbus/dbus.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "dbus"
|
||||||
|
version = "1.16.2"
|
||||||
|
description = "D-Bus message bus system"
|
||||||
|
url = "https://www.freedesktop.org/wiki/Software/dbus/"
|
||||||
|
license = "AFL-2.1"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://dbus.freedesktop.org/releases/dbus/dbus-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "expat"]
|
||||||
|
build = ["gcc", "make", "pkg-config", "meson", "ninja"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "meson"
|
||||||
|
configure = """meson setup build --prefix=/usr --buildtype=release -Druntime_dir=/run -Dsystem_pid_file=/run/dbus/pid -Dsystem_socket=/run/dbus/system_bus_socket -Ddoxygen_docs=disabled -Dxml_docs=disabled"""
|
||||||
|
make = """ninja -C build"""
|
||||||
|
install = """DESTDIR=${PKG} ninja -C build install"""
|
||||||
20
src/repos/core/dhcpcd/dhcpcd.toml
Normal file
20
src/repos/core/dhcpcd/dhcpcd.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "dhcpcd"
|
||||||
|
version = "10.3.0"
|
||||||
|
description = "DHCP client daemon"
|
||||||
|
url = "https://github.com/NetworkConfiguration/dhcpcd"
|
||||||
|
license = "BSD-2-Clause"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/NetworkConfiguration/dhcpcd/releases/download/v${version}/dhcpcd-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "eudev"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --sysconfdir=/etc --libexecdir=/usr/lib/dhcpcd --dbdir=/var/lib/dhcpcd --runstatedir=/run --disable-privsep"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/diffutils/diffutils.toml
Normal file
20
src/repos/core/diffutils/diffutils.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "diffutils"
|
||||||
|
version = "3.10"
|
||||||
|
description = "GNU file comparison utilities"
|
||||||
|
url = "https://www.gnu.org/software/diffutils/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/diffutils/diffutils-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/e2fsprogs/e2fsprogs.toml
Normal file
20
src/repos/core/e2fsprogs/e2fsprogs.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "e2fsprogs"
|
||||||
|
version = "1.47.4"
|
||||||
|
description = "Ext2/3/4 filesystem utilities"
|
||||||
|
url = "https://e2fsprogs.sourceforge.net/"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://downloads.sourceforge.net/project/e2fsprogs/e2fsprogs/v${version}/e2fsprogs-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "util-linux"]
|
||||||
|
build = ["gcc", "make", "pkg-config", "texinfo"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """mkdir -v build && cd build && ../configure --prefix=/usr --bindir=/usr/bin --with-root-prefix="" --enable-elf-shlibs --disable-libblkid --disable-libuuid --disable-uuidd --disable-fsck"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/eudev/eudev.toml
Normal file
20
src/repos/core/eudev/eudev.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "eudev"
|
||||||
|
version = "3.2.14"
|
||||||
|
description = "Device manager (udev fork without systemd)"
|
||||||
|
url = "https://github.com/eudev-project/eudev"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/eudev-project/eudev/releases/download/v${version}/eudev-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "kmod", "util-linux"]
|
||||||
|
build = ["gcc", "make", "gperf", "pkg-config"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --bindir=/usr/sbin --sysconfdir=/etc --enable-manpages --disable-static"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/expat/expat.toml
Normal file
20
src/repos/core/expat/expat.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "expat"
|
||||||
|
version = "2.7.4"
|
||||||
|
description = "XML parsing library"
|
||||||
|
url = "https://libexpat.github.io/"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/libexpat/libexpat/releases/download/R_2_7_4/expat-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --disable-static"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/file/file.toml
Normal file
20
src/repos/core/file/file.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "file"
|
||||||
|
version = "5.47"
|
||||||
|
description = "File type identification utility"
|
||||||
|
url = "https://www.darwinsys.com/file/"
|
||||||
|
license = "BSD-2-Clause"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://astron.com/pub/file/file-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "zlib"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/findutils/findutils.toml
Normal file
20
src/repos/core/findutils/findutils.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "findutils"
|
||||||
|
version = "4.10.0"
|
||||||
|
description = "GNU file search utilities"
|
||||||
|
url = "https://www.gnu.org/software/findutils/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/findutils/findutils-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --localstatedir=/var/lib/locate"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/flex/flex.toml
Normal file
20
src/repos/core/flex/flex.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "flex"
|
||||||
|
version = "2.6.4"
|
||||||
|
description = "Fast lexical analyzer generator"
|
||||||
|
url = "https://github.com/westes/flex"
|
||||||
|
license = "BSD-2-Clause"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/westes/flex/releases/download/v${version}/flex-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "m4"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --disable-static"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/gawk/gawk.toml
Normal file
20
src/repos/core/gawk/gawk.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "gawk"
|
||||||
|
version = "5.4.0"
|
||||||
|
description = "GNU awk text processing language"
|
||||||
|
url = "https://www.gnu.org/software/gawk/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/gawk/gawk-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "readline", "mpfr"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/gcc/gcc.toml
Normal file
20
src/repos/core/gcc/gcc.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "gcc"
|
||||||
|
version = "15.2.0"
|
||||||
|
description = "The GNU Compiler Collection"
|
||||||
|
url = "https://gcc.gnu.org/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/pub/gnu/gcc/gcc-${version}/gcc-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "gmp", "mpfr", "mpc", "zlib"]
|
||||||
|
build = ["make", "sed", "gawk", "texinfo"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """mkdir build && cd build && ../configure --prefix=/usr --enable-languages=c,c++ --enable-default-pie --enable-default-ssp --disable-multilib --with-system-zlib"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/gettext/gettext.toml
Normal file
20
src/repos/core/gettext/gettext.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "gettext"
|
||||||
|
version = "0.23.1"
|
||||||
|
description = "GNU internationalization utilities"
|
||||||
|
url = "https://www.gnu.org/software/gettext/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/gettext/gettext-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --disable-static"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/git/git.toml
Normal file
20
src/repos/core/git/git.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "git"
|
||||||
|
version = "2.53.0"
|
||||||
|
description = "Distributed version control system"
|
||||||
|
url = "https://git-scm.com/"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://www.kernel.org/pub/software/scm/git/git-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "curl", "openssl", "zlib", "expat", "perl", "python"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --with-gitconfig=/etc/gitconfig --with-python=python3"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} perllibdir=/usr/lib/perl5/5.40/site_perl install"""
|
||||||
19
src/repos/core/glib/glib.toml
Normal file
19
src/repos/core/glib/glib.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "glib"
|
||||||
|
version = "2.84.1"
|
||||||
|
description = "GLib low-level core library"
|
||||||
|
url = "https://gitlab.gnome.org/GNOME/glib"
|
||||||
|
license = "LGPL-2.1"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://download.gnome.org/sources/glib/2.84/glib-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "libffi", "zlib", "pcre2"]
|
||||||
|
build = ["gcc", "meson", "ninja", "pkg-config", "python"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
configure = """meson setup build --prefix=/usr --buildtype=release -Dman-pages=disabled"""
|
||||||
|
make = """ninja -C build"""
|
||||||
|
install = """DESTDIR=${PKG} ninja -C build install"""
|
||||||
20
src/repos/core/glibc/glibc.toml
Normal file
20
src/repos/core/glibc/glibc.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "glibc"
|
||||||
|
version = "2.43"
|
||||||
|
description = "The GNU C Library"
|
||||||
|
url = "https://www.gnu.org/software/libc/"
|
||||||
|
license = "LGPL-2.1"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/glibc/glibc-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = []
|
||||||
|
build = ["gcc", "binutils", "make", "sed", "gawk"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """mkdir -v build && cd build && ../configure --prefix=/usr --disable-werror --enable-kernel=5.4 --enable-stack-protector=strong libc_cv_slibdir=/usr/lib"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/gmp/gmp.toml
Normal file
20
src/repos/core/gmp/gmp.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "gmp"
|
||||||
|
version = "6.3.0"
|
||||||
|
description = "GNU Multiple Precision Arithmetic Library"
|
||||||
|
url = "https://gmplib.org/"
|
||||||
|
license = "LGPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://gmplib.org/download/gmp/gmp-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = []
|
||||||
|
build = ["gcc", "make", "m4"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --enable-cxx --disable-static"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
19
src/repos/core/gperf/gperf.toml
Normal file
19
src/repos/core/gperf/gperf.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "gperf"
|
||||||
|
version = "3.1"
|
||||||
|
description = "Perfect hash function generator"
|
||||||
|
url = "https://www.gnu.org/software/gperf/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/gperf/gperf-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/grep/grep.toml
Normal file
20
src/repos/core/grep/grep.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "grep"
|
||||||
|
version = "3.14"
|
||||||
|
description = "GNU grep pattern matching"
|
||||||
|
url = "https://www.gnu.org/software/grep/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/grep/grep-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/groff/groff.toml
Normal file
20
src/repos/core/groff/groff.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "groff"
|
||||||
|
version = "1.24.1"
|
||||||
|
description = "GNU troff typesetting system"
|
||||||
|
url = "https://www.gnu.org/software/groff/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/groff/groff-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "perl"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/gzip/gzip.toml
Normal file
20
src/repos/core/gzip/gzip.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "gzip"
|
||||||
|
version = "1.14"
|
||||||
|
description = "GNU compression utility"
|
||||||
|
url = "https://www.gnu.org/software/gzip/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/gzip/gzip-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/iproute2/iproute2.toml
Normal file
20
src/repos/core/iproute2/iproute2.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "iproute2"
|
||||||
|
version = "6.19.0"
|
||||||
|
description = "IP routing utilities"
|
||||||
|
url = "https://wiki.linuxfoundation.org/networking/iproute2"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://www.kernel.org/pub/linux/utils/net/iproute2/iproute2-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "libmnl"]
|
||||||
|
build = ["gcc", "make", "pkg-config", "bison", "flex"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """"""
|
||||||
|
make = """make NETNS_RUN_DIR=/run/netns"""
|
||||||
|
install = """make DESTDIR=${PKG} SBINDIR=/usr/sbin install"""
|
||||||
20
src/repos/core/kbd/kbd.toml
Normal file
20
src/repos/core/kbd/kbd.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "kbd"
|
||||||
|
version = "2.6.4"
|
||||||
|
description = "Keyboard utilities"
|
||||||
|
url = "https://kbd-project.org/"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://www.kernel.org/pub/linux/utils/kbd/kbd-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make", "autoconf", "automake"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --disable-vlock"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/kmod/kmod.toml
Normal file
20
src/repos/core/kmod/kmod.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "kmod"
|
||||||
|
version = "34.2"
|
||||||
|
description = "Linux kernel module handling"
|
||||||
|
url = "https://github.com/kmod-project/kmod"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/kmod-project/kmod/archive/refs/tags/v${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "zlib", "xz", "zstd", "openssl"]
|
||||||
|
build = ["gcc", "make", "meson", "ninja", "pkg-config"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "meson"
|
||||||
|
configure = """meson setup build --prefix=/usr --buildtype=release"""
|
||||||
|
make = """ninja -C build"""
|
||||||
|
install = """DESTDIR=${PKG} ninja -C build install"""
|
||||||
20
src/repos/core/less/less.toml
Normal file
20
src/repos/core/less/less.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "less"
|
||||||
|
version = "692"
|
||||||
|
description = "Terminal pager"
|
||||||
|
url = "http://www.greenwoodsoftware.com/less/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://www.greenwoodsoftware.com/less/less-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "ncurses"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --sysconfdir=/etc"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/libffi/libffi.toml
Normal file
20
src/repos/core/libffi/libffi.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "libffi"
|
||||||
|
version = "3.5.2"
|
||||||
|
description = "Foreign function interface library"
|
||||||
|
url = "https://github.com/libffi/libffi"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/libffi/libffi/releases/download/v${version}/libffi-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --disable-static --with-gcc-arch=native"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
19
src/repos/core/libmnl/libmnl.toml
Normal file
19
src/repos/core/libmnl/libmnl.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "libmnl"
|
||||||
|
version = "1.0.5"
|
||||||
|
description = "Minimalistic Netlink library"
|
||||||
|
url = "https://netfilter.org/projects/libmnl/"
|
||||||
|
license = "LGPL-2.1"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://www.netfilter.org/projects/libmnl/files/libmnl-${version}.tar.bz2"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
configure = """./configure --prefix=/usr --disable-static"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
19
src/repos/core/libpipeline/libpipeline.toml
Normal file
19
src/repos/core/libpipeline/libpipeline.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "libpipeline"
|
||||||
|
version = "1.5.8"
|
||||||
|
description = "Pipeline manipulation library"
|
||||||
|
url = "https://gitlab.com/cjwatson/libpipeline"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://download.savannah.nongnu.org/releases/libpipeline/libpipeline-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
configure = """./configure --prefix=/usr --disable-static"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/libtool/libtool.toml
Normal file
20
src/repos/core/libtool/libtool.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "libtool"
|
||||||
|
version = "2.5.4"
|
||||||
|
description = "GNU libtool generic library support script"
|
||||||
|
url = "https://www.gnu.org/software/libtool/"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/libtool/libtool-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/libxml2/libxml2.toml
Normal file
20
src/repos/core/libxml2/libxml2.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "libxml2"
|
||||||
|
version = "2.15.2"
|
||||||
|
description = "XML C parser and toolkit"
|
||||||
|
url = "https://gitlab.gnome.org/GNOME/libxml2"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://download.gnome.org/sources/libxml2/2.15/libxml2-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "zlib", "xz", "readline"]
|
||||||
|
build = ["gcc", "make", "pkg-config", "python"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --disable-static --with-history --with-python=/usr/bin/python3"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/linux/linux.toml
Normal file
20
src/repos/core/linux/linux.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "linux"
|
||||||
|
version = "6.19.8"
|
||||||
|
description = "The Linux kernel"
|
||||||
|
url = "https://www.kernel.org/"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = []
|
||||||
|
build = ["gcc", "make", "bc", "flex", "bison", "openssl", "perl"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make INSTALL_MOD_PATH=${PKG} modules_install"""
|
||||||
20
src/repos/core/m4/m4.toml
Normal file
20
src/repos/core/m4/m4.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "m4"
|
||||||
|
version = "1.4.20"
|
||||||
|
description = "GNU macro processor"
|
||||||
|
url = "https://www.gnu.org/software/m4/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/m4/m4-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/make/make.toml
Normal file
20
src/repos/core/make/make.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "make"
|
||||||
|
version = "4.4.1"
|
||||||
|
description = "GNU make build tool"
|
||||||
|
url = "https://www.gnu.org/software/make/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/make/make-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/man-db/man-db.toml
Normal file
20
src/repos/core/man-db/man-db.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "man-db"
|
||||||
|
version = "2.13.1"
|
||||||
|
description = "Manual page browser"
|
||||||
|
url = "https://man-db.nongnu.org/"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://download.savannah.nongnu.org/releases/man-db/man-db-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "groff", "less", "libpipeline"]
|
||||||
|
build = ["gcc", "make", "pkg-config"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --sysconfdir=/etc --disable-setuid --enable-cache-owner=bin --with-browser=/usr/bin/lynx --with-vgrind=/usr/bin/vgrind --with-grap=/usr/bin/grap"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/man-pages/man-pages.toml
Normal file
20
src/repos/core/man-pages/man-pages.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "man-pages"
|
||||||
|
version = "6.16"
|
||||||
|
description = "Linux man pages"
|
||||||
|
url = "https://www.kernel.org/doc/man-pages/"
|
||||||
|
license = "GPL-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://www.kernel.org/pub/linux/docs/man-pages/man-pages-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = []
|
||||||
|
build = []
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} prefix=/usr install"""
|
||||||
20
src/repos/core/meson/meson.toml
Normal file
20
src/repos/core/meson/meson.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "meson"
|
||||||
|
version = "1.10.2"
|
||||||
|
description = "High performance build system"
|
||||||
|
url = "https://mesonbuild.com/"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/mesonbuild/meson/releases/download/${version}/meson-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["python"]
|
||||||
|
build = ["python"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """"""
|
||||||
|
make = """python3 setup.py build"""
|
||||||
|
install = """python3 setup.py install --root=${PKG}"""
|
||||||
20
src/repos/core/mpc/mpc.toml
Normal file
20
src/repos/core/mpc/mpc.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "mpc"
|
||||||
|
version = "1.3.1"
|
||||||
|
description = "Multiple-precision complex number library"
|
||||||
|
url = "https://www.multiprecision.org/mpc/"
|
||||||
|
license = "LGPL-2.1"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/mpc/mpc-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["gmp", "mpfr"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --disable-static"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/mpfr/mpfr.toml
Normal file
20
src/repos/core/mpfr/mpfr.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "mpfr"
|
||||||
|
version = "4.2.2"
|
||||||
|
description = "Multiple-precision floating-point library"
|
||||||
|
url = "https://www.mpfr.org/"
|
||||||
|
license = "LGPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://www.mpfr.org/mpfr-current/mpfr-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["gmp"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --disable-static --enable-thread-safe"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/ncurses/ncurses.toml
Normal file
20
src/repos/core/ncurses/ncurses.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "ncurses"
|
||||||
|
version = "6.5"
|
||||||
|
description = "Terminal handling library"
|
||||||
|
url = "https://invisible-island.net/ncurses/"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://invisible-island.net/datafiles/release/ncurses-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr --mandir=/usr/share/man --with-shared --without-debug --without-normal --with-cxx-shared --enable-pc-files --with-pkg-config-libdir=/usr/lib/pkgconfig"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/ninja/ninja.toml
Normal file
20
src/repos/core/ninja/ninja.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "ninja"
|
||||||
|
version = "1.13.0"
|
||||||
|
description = "Small build system with a focus on speed"
|
||||||
|
url = "https://ninja-build.org/"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/ninja-build/ninja/archive/v${version}/ninja-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make", "python"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """"""
|
||||||
|
make = """python3 configure.py --bootstrap"""
|
||||||
|
install = """install -Dm755 ninja ${PKG}/usr/bin/ninja"""
|
||||||
20
src/repos/core/openssl/openssl.toml
Normal file
20
src/repos/core/openssl/openssl.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "openssl"
|
||||||
|
version = "3.6.1"
|
||||||
|
description = "Cryptography and TLS toolkit"
|
||||||
|
url = "https://www.openssl.org/"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/openssl/openssl/releases/download/openssl-${version}/openssl-${version}.tar.gz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "zlib"]
|
||||||
|
build = ["gcc", "make", "perl"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """./config --prefix=/usr --openssldir=/etc/ssl --libdir=lib shared zlib-dynamic"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} MANSUFFIX=ssl install"""
|
||||||
20
src/repos/core/patch/patch.toml
Normal file
20
src/repos/core/patch/patch.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "patch"
|
||||||
|
version = "2.8"
|
||||||
|
description = "GNU patch utility"
|
||||||
|
url = "https://www.gnu.org/software/patch/"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://ftp.gnu.org/gnu/patch/patch-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "autotools"
|
||||||
|
configure = """./configure --prefix=/usr"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
19
src/repos/core/pcre2/pcre2.toml
Normal file
19
src/repos/core/pcre2/pcre2.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "pcre2"
|
||||||
|
version = "10.45"
|
||||||
|
description = "Perl Compatible Regular Expressions v2"
|
||||||
|
url = "https://github.com/PCRE2Project/pcre2"
|
||||||
|
license = "BSD-3-Clause"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-${version}/pcre2-${version}.tar.bz2"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "zlib", "readline"]
|
||||||
|
build = ["gcc", "make", "cmake"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
configure = """./configure --prefix=/usr --enable-unicode --enable-jit --enable-pcre2-16 --enable-pcre2-32 --enable-pcre2grep-libz --enable-pcre2grep-libbz2 --enable-pcre2test-libreadline --disable-static"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
20
src/repos/core/perl/perl.toml
Normal file
20
src/repos/core/perl/perl.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "perl"
|
||||||
|
version = "5.40.2"
|
||||||
|
description = "Practical Extraction and Report Language"
|
||||||
|
url = "https://www.perl.org/"
|
||||||
|
license = "Artistic-1.0"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
url = "https://www.cpan.org/src/5.0/perl-${version}.tar.xz"
|
||||||
|
sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
run = ["glibc", "zlib"]
|
||||||
|
build = ["gcc", "make"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
system = "custom"
|
||||||
|
configure = """sh Configure -des -Dprefix=/usr -Dvendorprefix=/usr -Dprivlib=/usr/lib/perl5/5.40/core_perl -Darchlib=/usr/lib/perl5/5.40/core_perl -Dsitelib=/usr/lib/perl5/5.40/site_perl -Dsitearch=/usr/lib/perl5/5.40/site_perl -Dvendorlib=/usr/lib/perl5/5.40/vendor_perl -Dvendorarch=/usr/lib/perl5/5.40/vendor_perl -Dman1dir=/usr/share/man/man1 -Dman3dir=/usr/share/man/man3 -Dpager='/usr/bin/less -isR' -Duseshrplib -Dusethreads"""
|
||||||
|
make = """make"""
|
||||||
|
install = """make DESTDIR=${PKG} install"""
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user