Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 392e7553c4 | |||
| 19f76f4d9d | |||
| 7dcbff8ece | |||
| 8952589efd |
@ -1,6 +1,6 @@
|
||||
# Linux Patch API - Package Build Guide
|
||||
|
||||
This document provides comprehensive instructions for building production-ready Debian (.deb) and RPM (.rpm) packages for the Linux Patch API.
|
||||
This document provides comprehensive instructions for building production-ready packages for the Linux Patch API across all supported platforms: Debian/Ubuntu (.deb), RHEL/CentOS/Fedora (.rpm), Arch Linux (.pkg.tar.zst), and Alpine Linux (.apk).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@ -173,6 +173,152 @@ rpm -ql linux-patch-api
|
||||
rpm -e linux-patch-api
|
||||
```
|
||||
|
||||
## Building Arch Package (.pkg.tar.zst)
|
||||
|
||||
### Quick Build
|
||||
|
||||
```bash
|
||||
cd /path/to/linux_patch_api
|
||||
|
||||
# Build release binary
|
||||
cargo build --release
|
||||
|
||||
# Build Arch package
|
||||
chmod +x build-arch.sh
|
||||
SKIP_CARGO_BUILD=1 ./build-arch.sh
|
||||
|
||||
# Package will be created in releases/
|
||||
ls -la releases/*.pkg.tar.zst
|
||||
```
|
||||
|
||||
### Detailed Build Process
|
||||
|
||||
```bash
|
||||
# 1. Install build dependencies (Arch Linux)
|
||||
sudo pacman -Syu --noconfirm rust cargo systemd git base-devel gcc
|
||||
|
||||
# 2. Build release binary
|
||||
cargo build --release
|
||||
|
||||
# 3. Run build script
|
||||
chmod +x build-arch.sh
|
||||
./build-arch.sh
|
||||
|
||||
# 4. Verify package contents
|
||||
bsdtar -tf releases/linux-patch-api-*.pkg.tar.zst
|
||||
|
||||
# 5. Verify package info
|
||||
pacman -Qi releases/linux-patch-api-*.pkg.tar.zst
|
||||
```
|
||||
|
||||
### Install Script Hooks
|
||||
|
||||
The Arch package includes an `.install` file (`configs/linux-patch-api.install`) that runs automatically on install:
|
||||
|
||||
- **post_install**: Creates directories, copies example configs, enables systemd service
|
||||
- **post_upgrade**: Reloads systemd daemon
|
||||
- **pre_remove**: Stops and disables service
|
||||
- **post_remove**: Cleans up empty directories
|
||||
|
||||
### Installation Test
|
||||
|
||||
```bash
|
||||
# Install the package
|
||||
sudo pacman -U ./releases/linux-patch-api-*.pkg.tar.zst
|
||||
|
||||
# Verify installation
|
||||
systemctl status linux-patch-api
|
||||
linux-patch-api --version
|
||||
|
||||
# Check installed files
|
||||
pacman -Ql linux-patch-api
|
||||
|
||||
# Verify config files exist
|
||||
ls -la /etc/linux_patch_api/
|
||||
|
||||
# Remove package
|
||||
sudo pacman -R linux-patch-api
|
||||
```
|
||||
|
||||
**Note:** The build script creates a `builduser` for `makepkg` when running as root (typical in CI environments). The `.install` hook handles directory creation, config copying, and service enablement.
|
||||
|
||||
## Building Alpine Package (.apk)
|
||||
|
||||
### Quick Build
|
||||
|
||||
```bash
|
||||
cd /path/to/linux_patch_api
|
||||
|
||||
# Build release binary (MUSL target for Alpine)
|
||||
cargo build --release --target x86_64-unknown-linux-musl
|
||||
|
||||
# Build Alpine package
|
||||
chmod +x build-alpine.sh
|
||||
SKIP_CARGO_BUILD=1 ./build-alpine.sh
|
||||
|
||||
# Package will be created in releases/
|
||||
ls -la releases/*.apk
|
||||
```
|
||||
|
||||
### Detailed Build Process
|
||||
|
||||
```bash
|
||||
# 1. Install build dependencies (Alpine Linux)
|
||||
apk add --no-cache alpine-sdk rust cargo openssl openssl-dev elogind-dev musl-dev abuild gcc
|
||||
|
||||
# 2. Add Rust MUSL target
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
|
||||
# 3. Build release binary
|
||||
cargo build --release --target x86_64-unknown-linux-musl
|
||||
|
||||
# 4. Run build script
|
||||
chmod +x build-alpine.sh
|
||||
./build-alpine.sh
|
||||
|
||||
# 5. Verify package contents
|
||||
apk verify releases/*.apk
|
||||
|
||||
# 6. List package contents
|
||||
tar -tzf releases/*.apk
|
||||
```
|
||||
|
||||
### Install Script Hooks
|
||||
|
||||
The Alpine package includes an install script (`configs/linux-patch-api.apk-install`) that runs automatically on install:
|
||||
|
||||
- **pre_install**: Creates directories, sets ownership and permissions
|
||||
- **post_install**: Copies example configs, adds service to default runlevel
|
||||
- **pre_deinstall**: Stops and removes service from runlevel
|
||||
- **post_deinstall**: Cleans up empty directories
|
||||
|
||||
### Installation Test
|
||||
|
||||
```bash
|
||||
# Install the package
|
||||
sudo apk add --allow-unstable ./releases/linux-patch-api-*.apk
|
||||
|
||||
# Verify installation
|
||||
rc-service linux-patch-api status
|
||||
linux-patch-api --version
|
||||
|
||||
# Check installed files
|
||||
apk info -L linux-patch-api
|
||||
|
||||
# Verify config files exist
|
||||
ls -la /etc/linux_patch_api/
|
||||
|
||||
# Remove package
|
||||
sudo apk del linux-patch-api
|
||||
```
|
||||
|
||||
**Important:** Alpine uses **OpenRC** instead of systemd. Key differences:
|
||||
- Start service: `rc-service linux-patch-api start`
|
||||
- Stop service: `rc-service linux-patch-api stop`
|
||||
- Check status: `rc-service linux-patch-api status`
|
||||
- Service init script: `/etc/init.d/linux-patch-api`
|
||||
- The `abuild` tool generates signing keys automatically for CI builds
|
||||
|
||||
## Using the Interactive Installer
|
||||
|
||||
For manual deployment without package managers:
|
||||
@ -209,15 +355,17 @@ The installer will:
|
||||
| `/var/lib/linux_patch_api/` | Data directory | 755 |
|
||||
| `/var/log/linux_patch_api/` | Log directory | 755 |
|
||||
|
||||
### System User/Group
|
||||
### Service Account
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| User | linux-patch-api |
|
||||
| Group | linux-patch-api |
|
||||
| User | root |
|
||||
| Group | root |
|
||||
| Home | /var/lib/linux_patch_api |
|
||||
| Shell | /usr/sbin/nologin |
|
||||
| Type | System account |
|
||||
| Shell | N/A (systemd service) |
|
||||
| Type | Runs as root (required for package management) |
|
||||
|
||||
**Note:** The service runs as root because package management operations (apt, dnf, apk, pacman) require root privileges. Security is provided by mTLS + IP whitelist, not process isolation.
|
||||
|
||||
## Supported Distributions
|
||||
|
||||
@ -240,6 +388,19 @@ The installer will:
|
||||
| AlmaLinux | 8, 9 | ✅ Supported |
|
||||
| Rocky Linux | 8, 9 | ✅ Supported |
|
||||
|
||||
### Arch Package (.pkg.tar.zst)
|
||||
|
||||
| Distribution | Versions | Status |
|
||||
|--------------|----------|--------|
|
||||
| Arch Linux | Rolling | ✅ Supported |
|
||||
| Manjaro | Rolling | ✅ Supported |
|
||||
|
||||
### Alpine Package (.apk)
|
||||
|
||||
| Distribution | Versions | Status |
|
||||
|--------------|----------|--------|
|
||||
| Alpine Linux | 3.18+ | ✅ Supported |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Debian Package Issues
|
||||
@ -276,9 +437,62 @@ cat ~/rpmbuild/BUILDROOT/*/var/log/rpmbuild.log
|
||||
dnf install -y systemd-devel pkgconfig
|
||||
```
|
||||
|
||||
### Arch Package Issues
|
||||
|
||||
**Error: `makepkg: cannot run as root`**
|
||||
```bash
|
||||
# The build script handles this automatically by creating builduser
|
||||
# If running manually:
|
||||
useradd -m builduser
|
||||
su - builduser -c "cd /path/to/repo && makepkg -f --noconfirm"
|
||||
```
|
||||
|
||||
**Error: `install script not found`**
|
||||
```bash
|
||||
# Ensure linux-patch-api.install is in the same directory as PKGBUILD
|
||||
ls -la configs/linux-patch-api.install
|
||||
# The build script copies it automatically
|
||||
```
|
||||
|
||||
**Error: `Permission denied` on config files**
|
||||
```bash
|
||||
# Verify ownership is root:root
|
||||
ls -la /etc/linux_patch_api/
|
||||
# Fix if needed:
|
||||
sudo chown -R root:root /etc/linux_patch_api/
|
||||
sudo chmod 750 /etc/linux_patch_api /etc/linux_patch_api/certs
|
||||
```
|
||||
|
||||
### Alpine Package Issues
|
||||
|
||||
**Error: `abuild: UNTRUSTED signature`**
|
||||
```bash
|
||||
# The build script handles key generation automatically
|
||||
# If running manually:
|
||||
abuild-keygen -a -n
|
||||
cp /root/.abuild/*.rsa.pub /etc/apk/keys/
|
||||
```
|
||||
|
||||
**Error: `apk add: ERROR: failed to create directory`**
|
||||
```bash
|
||||
# Verify the install script ran correctly
|
||||
ls -la /etc/linux_patch_api/
|
||||
ls -la /var/lib/linux_patch_api/
|
||||
# Manually create if needed:
|
||||
sudo mkdir -p /etc/linux_patch_api/certs /var/lib/linux_patch_api /var/log/linux_patch_api
|
||||
```
|
||||
|
||||
**Error: `rc-service: service not found`**
|
||||
```bash
|
||||
# Verify the init script exists
|
||||
ls -la /etc/init.d/linux-patch-api
|
||||
# Re-add to default runlevel
|
||||
sudo rc-update add linux-patch-api default
|
||||
```
|
||||
|
||||
### Service Issues
|
||||
|
||||
**Service fails to start:**
|
||||
**Service fails to start (systemd):**
|
||||
```bash
|
||||
# Check service status
|
||||
systemctl status linux-patch-api
|
||||
@ -293,6 +507,22 @@ linux-patch-api --config /etc/linux_patch_api/config.yaml --check
|
||||
ls -la /etc/linux_patch_api/certs/
|
||||
```
|
||||
|
||||
**Service fails to start (OpenRC/Alpine):**
|
||||
```bash
|
||||
# Check service status
|
||||
rc-service linux-patch-api status
|
||||
|
||||
# View logs
|
||||
cat /var/log/linux_patch_api/linux-patch-api.log
|
||||
cat /var/log/linux_patch_api/linux-patch-api.err
|
||||
|
||||
# Check configuration
|
||||
linux-patch-api --config /etc/linux_patch_api/config.yaml --check
|
||||
|
||||
# Verify certificates
|
||||
ls -la /etc/linux_patch_api/certs/
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Example
|
||||
@ -383,7 +613,7 @@ jobs:
|
||||
- Packages are signed with maintainer GPG key for production deployments
|
||||
- All maintainer scripts run with `set -e` for fail-fast behavior
|
||||
- Configuration files are marked as conffiles to preserve user modifications
|
||||
- System user has minimal privileges (nologin shell, no home directory)
|
||||
- Service runs as root (required for package management operations)
|
||||
- Directory permissions follow principle of least privilege
|
||||
- TLS certificates should be replaced with CA-signed certs in production
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "linux-patch-api"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
edition = "2021"
|
||||
authors = ["Echo <echo@moon-dragon.us>"]
|
||||
description = "Secure remote package management API for Linux systems"
|
||||
|
||||
164
README.md
164
README.md
@ -185,6 +185,13 @@ For detailed enrollment procedures, see [DEPLOYMENT_GUIDE.md - Self-Enrollment D
|
||||
|
||||
### Package Installation
|
||||
|
||||
All platform packages produce identical installation results:
|
||||
- Creates `/etc/linux_patch_api/`, `/etc/linux_patch_api/certs/`, `/var/lib/linux_patch_api/`, `/var/log/linux_patch_api/`
|
||||
- Copies example configs to live configs if not already present
|
||||
- Enables the service (does not start automatically)
|
||||
- Sets correct permissions (750 on config dirs, 755 on data/log dirs)
|
||||
- Ownership: root:root (service runs as root)
|
||||
|
||||
#### Debian/Ubuntu (.deb)
|
||||
|
||||
```bash
|
||||
@ -197,52 +204,173 @@ apt-get install -f -y
|
||||
# Verify installation
|
||||
systemctl status linux-patch-api
|
||||
linux-patch-api --version
|
||||
|
||||
# Check installed files
|
||||
dpkg -L linux-patch-api
|
||||
|
||||
# Remove package (keeping configs)
|
||||
dpkg -r linux-patch-api
|
||||
|
||||
# Purge package (removing all configs)
|
||||
dpkg -P linux-patch-api
|
||||
```
|
||||
|
||||
**Prerequisites:** `systemd`, `libsystemd0`
|
||||
|
||||
**Post-install:** The package automatically copies example configs, enables the service, and prints next steps. Configure `/etc/linux_patch_api/config.yaml` and place TLS certificates in `/etc/linux_patch_api/certs/` before starting.
|
||||
|
||||
#### RHEL/CentOS/Fedora (.rpm)
|
||||
|
||||
```bash
|
||||
# Install the package
|
||||
rpm -ivh linux-patch-api-1.0.0-1.x86_64.rpm
|
||||
# Install the package (recommended - resolves dependencies automatically)
|
||||
dnf install -y ./linux-patch-api-1.0.0-1.el9.x86_64.rpm
|
||||
|
||||
# Or with yum
|
||||
yum install -y ./linux-patch-api-1.0.0-1.el9.x86_64.rpm
|
||||
|
||||
# Or with rpm (does NOT resolve dependencies)
|
||||
rpm -ivh linux-patch-api-1.0.0-1.el9.x86_64.rpm
|
||||
|
||||
# Verify installation
|
||||
systemctl status linux-patch-api
|
||||
linux-patch-api --version
|
||||
|
||||
# Check installed files
|
||||
rpm -ql linux-patch-api
|
||||
|
||||
# Remove package
|
||||
rpm -e linux-patch-api
|
||||
```
|
||||
|
||||
**Prerequisites (auto-resolved with dnf/yum):** `systemd`, `libsystemd`, `openssl-libs`, `ca-certificates`
|
||||
|
||||
**Post-install:** The package automatically creates directories, copies example configs, enables the service, and prints next steps. Configure `/etc/linux_patch_api/config.yaml` and place TLS certificates in `/etc/linux_patch_api/certs/` before starting.
|
||||
|
||||
**Note:** Use `dnf install` or `yum install` instead of `rpm -ivh` to automatically resolve dependencies. The `rpm -ivh` command will fail if required packages are not already installed.
|
||||
|
||||
#### Arch Linux (.pkg.tar.zst)
|
||||
|
||||
```bash
|
||||
# Install the package
|
||||
sudo pacman -U ./linux-patch-api-1.0.0-1-x86_64.pkg.tar.zst
|
||||
|
||||
# Verify installation
|
||||
systemctl status linux-patch-api
|
||||
linux-patch-api --version
|
||||
|
||||
# Check installed files
|
||||
pacman -Ql linux-patch-api
|
||||
|
||||
# Remove package
|
||||
sudo pacman -R linux-patch-api
|
||||
```
|
||||
|
||||
**Prerequisites:** `systemd` (included by default on Arch)
|
||||
|
||||
**Post-install:** The package automatically creates directories, copies example configs, enables the service, and prints next steps. Configure `/etc/linux_patch_api/config.yaml` and place TLS certificates in `/etc/linux_patch_api/certs/` before starting.
|
||||
|
||||
**Note:** Arch uses systemd by default. The install hook runs `systemctl enable` but does not start the service. You must configure before starting.
|
||||
|
||||
#### Alpine Linux (.apk)
|
||||
|
||||
```bash
|
||||
# Install the package
|
||||
sudo apk add --allow-unstable ./linux-patch-api-1.0.0-r0.apk
|
||||
|
||||
# Verify installation
|
||||
rc-service linux-patch-api status
|
||||
linux-patch-api --version
|
||||
|
||||
# Check installed files
|
||||
apk info -L linux-patch-api
|
||||
|
||||
# Remove package
|
||||
sudo apk del linux-patch-api
|
||||
```
|
||||
|
||||
**Prerequisites:** `openrc` (included by default on Alpine)
|
||||
|
||||
**Post-install:** The package automatically creates directories, copies example configs, adds the service to the default runlevel, and prints next steps. Configure `/etc/linux_patch_api/config.yaml` and place TLS certificates in `/etc/linux_patch_api/certs/` before starting.
|
||||
|
||||
**Important differences from systemd-based systems:**
|
||||
- Alpine uses **OpenRC** instead of systemd. Use `rc-service` commands instead of `systemctl`
|
||||
- Start service: `rc-service linux-patch-api start`
|
||||
- Stop service: `rc-service linux-patch-api stop`
|
||||
- Check status: `rc-service linux-patch-api status`
|
||||
- The service is added to the `default` runlevel automatically on install
|
||||
- Service init script: `/etc/init.d/linux-patch-api`
|
||||
|
||||
### Manual Installation
|
||||
|
||||
For systems without package manager support:
|
||||
|
||||
```bash
|
||||
# Run interactive installer (requires root)
|
||||
./install.sh
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
The installer will:
|
||||
- Detect operating system
|
||||
- Create system user and group
|
||||
- Set up directory structure
|
||||
- Install binary and configuration files
|
||||
- Create directory structure (`/etc/linux_patch_api/`, `/var/lib/linux_patch_api/`, `/var/log/linux_patch_api/`)
|
||||
- Install binary to `/usr/bin/linux-patch-api`
|
||||
- Install example configs
|
||||
- Configure systemd service
|
||||
- Set correct permissions
|
||||
|
||||
### Building from Source
|
||||
|
||||
#### Prerequisites (all platforms)
|
||||
|
||||
- Rust toolchain (stable channel, 1.75+)
|
||||
- OpenSSL development headers
|
||||
- systemd development headers
|
||||
- C compiler (gcc)
|
||||
|
||||
#### Build Debian Package (.deb)
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://gitea.internal/linux-patch-api.git
|
||||
cd linux-patch-api
|
||||
|
||||
# Build release binary
|
||||
cargo build --release --target x86_64-unknown-linux-gnu
|
||||
|
||||
# Build Debian package
|
||||
dpkg-buildpackage -us -uc -b
|
||||
|
||||
# Or build RPM package
|
||||
rpmbuild -ba linux-patch-api.spec
|
||||
# On Debian/Ubuntu
|
||||
apt-get install -y build-essential debhelper pkg-config libsystemd-dev libssl-dev cargo rustc
|
||||
cargo build --release
|
||||
sudo dpkg-buildpackage -us -uc -b
|
||||
```
|
||||
|
||||
#### Build RPM Package (.rpm)
|
||||
|
||||
```bash
|
||||
# On Fedora/RHEL/CentOS
|
||||
dnf install -y rpm-build cargo rust gcc openssl-devel systemd-devel pkgconfig
|
||||
cargo build --release --target x86_64-unknown-linux-gnu
|
||||
chmod +x build-rpm.sh
|
||||
./build-rpm.sh
|
||||
```
|
||||
|
||||
**Note:** The RPM spec includes `BuildRequires` for native RPM build environments. When building in CI containers (where deps are pre-installed via apt-get), these are informational only.
|
||||
|
||||
#### Build Arch Package (.pkg.tar.zst)
|
||||
|
||||
```bash
|
||||
# On Arch Linux/Manjaro
|
||||
pacman -Syu --noconfirm rust cargo systemd git base-devel gcc
|
||||
cargo build --release
|
||||
chmod +x build-arch.sh
|
||||
SKIP_CARGO_BUILD=1 ./build-arch.sh
|
||||
```
|
||||
|
||||
**Note:** The build script creates a `builduser` for `makepkg` when running as root (typical in CI). The `.install` hook handles directory creation, config copying, and service enablement.
|
||||
|
||||
#### Build Alpine Package (.apk)
|
||||
|
||||
```bash
|
||||
# On Alpine Linux 3.18+
|
||||
apk add --no-cache alpine-sdk rust cargo openssl openssl-dev elogind-dev musl-dev abuild gcc
|
||||
cargo build --release --target x86_64-unknown-linux-musl
|
||||
chmod +x build-alpine.sh
|
||||
SKIP_CARGO_BUILD=1 ./build-alpine.sh
|
||||
```
|
||||
|
||||
**Important:** Alpine requires the `x86_64-unknown-linux-musl` target for static linking. The build script handles `abuild` key generation and runs as a `builduser` when executed as root.
|
||||
|
||||
See [BUILD_PACKAGES.md](./BUILD_PACKAGES.md) for detailed build instructions.
|
||||
|
||||
---
|
||||
|
||||
@ -44,8 +44,11 @@ else
|
||||
echo "Skipping cargo build (SKIP_CARGO_BUILD is set)"
|
||||
fi
|
||||
|
||||
# Create package directory in /home/builduser (accessible by builduser)
|
||||
PKGDIR=/home/builduser/apk-package
|
||||
# Get version from Cargo.toml
|
||||
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*=.*"\([^"]*\)".*/\1/')
|
||||
|
||||
# Create package directory structure
|
||||
PKGDIR=$(pwd)/apk-package
|
||||
rm -rf "$PKGDIR"
|
||||
mkdir -p "$PKGDIR"/usr/bin
|
||||
mkdir -p "$PKGDIR"/etc/linux_patch_api/certs
|
||||
@ -65,20 +68,22 @@ chmod 755 "$PKGDIR"/etc/init.d/linux-patch-api
|
||||
cp configs/config.yaml.example "$PKGDIR"/etc/linux_patch_api/config.yaml.example
|
||||
cp configs/whitelist.yaml.example "$PKGDIR"/etc/linux_patch_api/whitelist.yaml.example
|
||||
|
||||
# Copy install script for APKBUILD
|
||||
mkdir -p /home/builduser/repo
|
||||
cp configs/linux-patch-api.apk-install /home/builduser/repo/linux-patch-api.apk-install
|
||||
# Prepare workspace for abuild
|
||||
WORKSPACE_DIR=/home/builduser/repo
|
||||
mkdir -p "$WORKSPACE_DIR"
|
||||
|
||||
# Use /home/builduser as workspace for APKBUILD
|
||||
WORKSPACE_DIR=/home/builduser
|
||||
# Copy install script to workspace (must be co-located with APKBUILD)
|
||||
cp configs/linux-patch-api.apk-install "$WORKSPACE_DIR"/linux-patch-api.apk-install
|
||||
|
||||
# Get version from Cargo.toml
|
||||
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*=.*"\([^"]*\)".*/\1/')
|
||||
# Copy package directory to workspace
|
||||
cp -r "$PKGDIR" "$WORKSPACE_DIR"/apk-package
|
||||
|
||||
# Create APKBUILD
|
||||
# Note: install= must use literal package name, not $pkgname (unquoted heredoc expands variables)
|
||||
# Copy entire repo to workspace for source references
|
||||
cp -r . "$WORKSPACE_DIR"/src/
|
||||
|
||||
# Create APKBUILD in workspace directory (co-located with install script)
|
||||
echo "Creating APKBUILD..."
|
||||
cat > APKBUILD << EOF
|
||||
cat > "$WORKSPACE_DIR"/APKBUILD << EOF
|
||||
pkgname=linux-patch-api
|
||||
pkgver=${VERSION}
|
||||
pkgrel=1
|
||||
@ -99,15 +104,13 @@ package() {
|
||||
install -d "\$pkgdir"/var/lib/linux_patch_api
|
||||
install -d "\$pkgdir"/var/log/linux_patch_api
|
||||
|
||||
cp -r ${WORKSPACE_DIR}/apk-package/usr/bin/* "\$pkgdir"/usr/bin/
|
||||
cp -r ${WORKSPACE_DIR}/apk-package/etc/linux_patch_api/* "\$pkgdir"/etc/linux_patch_api/
|
||||
cp -r ${WORKSPACE_DIR}/apk-package/etc/init.d/* "\$pkgdir"/etc/init.d/
|
||||
install -Dm755 "\$startdir"/apk-package/usr/bin/linux-patch-api "\$pkgdir"/usr/bin/linux-patch-api
|
||||
install -Dm755 "\$startdir"/apk-package/etc/init.d/linux-patch-api "\$pkgdir"/etc/init.d/linux-patch-api
|
||||
install -Dm644 "\$startdir"/apk-package/etc/linux_patch_api/config.yaml.example "\$pkgdir"/etc/linux_patch_api/config.yaml.example
|
||||
install -Dm644 "\$startdir"/apk-package/etc/linux_patch_api/whitelist.yaml.example "\$pkgdir"/etc/linux_patch_api/whitelist.yaml.example
|
||||
}
|
||||
EOF
|
||||
|
||||
# Generate checksums for APKBUILD sources
|
||||
echo "Generating checksums..."
|
||||
|
||||
# Build APK package
|
||||
echo "Building APK package..."
|
||||
|
||||
@ -117,10 +120,8 @@ if [ "$(id -u)" = "0" ]; then
|
||||
adduser -D -s /bin/sh builduser 2>/dev/null || true
|
||||
addgroup builduser abuild 2>/dev/null || usermod -aG abuild builduser
|
||||
|
||||
# Copy repo contents to builduser home (accessible directory)
|
||||
cp -r . /home/builduser/repo/
|
||||
chown -R builduser:builduser /home/builduser/repo/
|
||||
chown -R builduser:builduser /home/builduser/apk-package/
|
||||
# Set ownership of workspace
|
||||
chown -R builduser:builduser "$WORKSPACE_DIR"
|
||||
|
||||
# Set up builduser home directory for abuild
|
||||
mkdir -p /home/builduser/.abuild
|
||||
@ -136,32 +137,25 @@ if [ "$(id -u)" = "0" ]; then
|
||||
echo "PACKAGER_PRIVKEY=\"$KEYFILE\"" > /home/builduser/.abuild/abuild.conf
|
||||
chown builduser:builduser /home/builduser/.abuild/abuild.conf
|
||||
|
||||
# Copy APKBUILD and checksums to builduser home for abuild
|
||||
cp APKBUILD /home/builduser/
|
||||
cp .checksums /home/builduser/ 2>/dev/null || true
|
||||
|
||||
# Install public key BEFORE abuild (fixes UNTRUSTED signature)
|
||||
cp /home/builduser/.abuild/*.rsa.pub /etc/apk/keys/ 2>/dev/null || true
|
||||
|
||||
# Run abuild as builduser in /home/builduser where APKBUILD exists
|
||||
# Run abuild as builduser in workspace directory
|
||||
# Use || true because index update may fail but APK is still created
|
||||
su - builduser -c "cd /home/builduser && abuild checksum && abuild -d -F" || true
|
||||
su - builduser -c "cd $WORKSPACE_DIR && abuild checksum && abuild -d -F" || true
|
||||
|
||||
# Copy APK from builduser packages to releases
|
||||
mkdir -p releases
|
||||
cp /home/builduser/packages/x86_64/*.apk releases/ 2>/dev/null || cp /home/builduser/packages/*.apk releases/ 2>/dev/null || find /home/builduser/packages -name "*.apk" -exec cp {} releases/ \; 2>/dev/null || true
|
||||
else
|
||||
cd "$WORKSPACE_DIR"
|
||||
abuild checksum
|
||||
abuild -F -r
|
||||
cd -
|
||||
mkdir -p releases
|
||||
cp ~/packages/x86_64/*.apk releases/ 2>/dev/null || cp ~/packages/*.apk releases/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Copy to releases directory (fallback for non-root builds)
|
||||
echo ""
|
||||
echo "Copying package to releases/..."
|
||||
mkdir -p releases
|
||||
cp ~/packages/x86_64/*.apk releases/ 2>/dev/null || cp ~/packages/*.apk releases/ 2>/dev/null || find ~/packages -name "*.apk" -exec cp {} releases/ \; 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=== Build Complete ==="
|
||||
echo "Package: releases/linux-patch-api-*.apk"
|
||||
|
||||
@ -22,7 +22,7 @@ else
|
||||
echo "Skipping cargo build (SKIP_CARGO_BUILD is set)"
|
||||
fi
|
||||
|
||||
# Create package directory
|
||||
# Create package directory structure
|
||||
PKGDIR=$(pwd)/arch-package
|
||||
rm -rf "$PKGDIR"
|
||||
mkdir -p "$PKGDIR"/usr/bin
|
||||
@ -42,9 +42,12 @@ cp configs/linux-patch-api.service "$PKGDIR"/usr/lib/systemd/system/
|
||||
cp configs/config.yaml.example "$PKGDIR"/etc/linux_patch_api/config.yaml.example
|
||||
cp configs/whitelist.yaml.example "$PKGDIR"/etc/linux_patch_api/whitelist.yaml.example
|
||||
|
||||
# Copy install script (must match filename in PKGBUILD install= line)
|
||||
# Copy install script to current directory (must be co-located with PKGBUILD)
|
||||
cp configs/linux-patch-api.install linux-patch-api.install
|
||||
|
||||
# Get version from Cargo.toml
|
||||
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*=.*"\([^"]*\)".*/\1/')
|
||||
|
||||
# Create PKGBUILD with quoted heredoc to prevent $pkgdir expansion
|
||||
# $pkgdir must be literal for makepkg to expand at runtime
|
||||
echo "Creating PKGBUILD..."
|
||||
@ -58,13 +61,15 @@ arch=('x86_64')
|
||||
license=('MIT')
|
||||
depends=('systemd')
|
||||
install=linux-patch-api.install
|
||||
source=()
|
||||
backup=(
|
||||
'etc/linux_patch_api/config.yaml'
|
||||
'etc/linux_patch_api/whitelist.yaml'
|
||||
)
|
||||
|
||||
package() {
|
||||
cp -r /home/builduser/repo/arch-package/* "$pkgdir"/
|
||||
# Use $startdir because arch-package is co-located with PKGBUILD, not in sources
|
||||
cp -r "$startdir"/arch-package/* "$pkgdir"/
|
||||
|
||||
# Ensure directories exist with proper structure
|
||||
mkdir -p "$pkgdir"/etc/linux_patch_api/certs
|
||||
@ -73,18 +78,12 @@ package() {
|
||||
}
|
||||
EOF
|
||||
|
||||
# Replace version placeholder with actual version from Cargo.toml
|
||||
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*=.*"\([^"]*\)".*/\1/')
|
||||
# Replace version placeholder with actual version
|
||||
sed -i "s/VERSION_PLACEHOLDER/$VERSION/" PKGBUILD
|
||||
|
||||
echo "PKGBUILD version: $VERSION"
|
||||
|
||||
# Create .SRCINFO
|
||||
echo "Creating .SRCINFO..."
|
||||
|
||||
# Build package
|
||||
echo "Building Arch package..."
|
||||
|
||||
# For CI environments where we may run as root
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
echo "Running as root - creating build user for makepkg..."
|
||||
@ -95,12 +94,22 @@ if [ "$(id -u)" = "0" ]; then
|
||||
cp -r . /home/builduser/repo/
|
||||
chown -R builduser:builduser /home/builduser/repo/
|
||||
|
||||
# Create source tarball for makepkg
|
||||
# makepkg expects sources to be in $srcdir after extraction
|
||||
# We create a tarball of arch-package so %autosetup or prepare can extract it
|
||||
cd /home/builduser/repo
|
||||
|
||||
su - builduser -c "cd /home/builduser/repo && makepkg --printsrcinfo > .SRCINFO"
|
||||
su - builduser -c "cd /home/builduser/repo && makepkg -f --noconfirm"
|
||||
|
||||
# Copy package to releases
|
||||
mkdir -p /home/builduser/repo/releases
|
||||
cp /home/builduser/repo/*.pkg.tar.zst /home/builduser/repo/releases/ 2>/dev/null || true
|
||||
cd -
|
||||
|
||||
# Copy releases back to original directory
|
||||
mkdir -p releases
|
||||
cp /home/builduser/repo/*.pkg.tar.zst releases/
|
||||
cp /home/builduser/repo/releases/*.pkg.tar.zst releases/ 2>/dev/null || true
|
||||
else
|
||||
makepkg --printsrcinfo > .SRCINFO
|
||||
makepkg -f --noconfirm
|
||||
|
||||
@ -1,28 +1,18 @@
|
||||
#!/bin/sh
|
||||
# Alpine Linux install hooks for linux-patch-api
|
||||
# Reference: debian/{preinst,postinst,prerm,postrm}
|
||||
# Matches Debian preinst/postinst behavior: no system user, root:root ownership
|
||||
# Alpine APKBUILD install script format: pre-install, post-install, pre-deinstall, post-deinstall
|
||||
|
||||
# Pre-install: Create user/group and directories before files are laid down
|
||||
# Pre-install: Create directories before files are laid down
|
||||
pre_install() {
|
||||
# Create system group
|
||||
if ! getent group linux-patch-api >/dev/null; then
|
||||
addgroup --system linux-patch-api
|
||||
fi
|
||||
|
||||
# Create system user
|
||||
if ! getent passwd linux-patch-api >/dev/null; then
|
||||
adduser --system --ingroup linux-patch-api --home /var/lib/linux_patch_api --no-create-home --shell /sbin/nologin --gecos "Linux Patch API Service" --disabled-password linux-patch-api
|
||||
fi
|
||||
|
||||
# Create required directories
|
||||
mkdir -p /etc/linux_patch_api/certs
|
||||
mkdir -p /var/lib/linux_patch_api
|
||||
mkdir -p /var/log/linux_patch_api
|
||||
|
||||
# Set proper ownership
|
||||
chown -R linux-patch-api:linux-patch-api /var/lib/linux_patch_api
|
||||
chown -R linux-patch-api:linux-patch-api /var/log/linux_patch_api
|
||||
# Set proper ownership (service runs as root)
|
||||
chown -R root:root /var/lib/linux_patch_api
|
||||
chown -R root:root /var/log/linux_patch_api
|
||||
|
||||
# Set secure permissions
|
||||
chmod 750 /etc/linux_patch_api
|
||||
@ -40,7 +30,7 @@ post_install() {
|
||||
if [ -f "/etc/linux_patch_api/config.yaml.example" ]; then
|
||||
cp /etc/linux_patch_api/config.yaml.example /etc/linux_patch_api/config.yaml
|
||||
chmod 640 /etc/linux_patch_api/config.yaml
|
||||
chown linux-patch-api:linux-patch-api /etc/linux_patch_api/config.yaml
|
||||
chown root:root /etc/linux_patch_api/config.yaml
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -48,7 +38,7 @@ post_install() {
|
||||
if [ -f "/etc/linux_patch_api/whitelist.yaml.example" ]; then
|
||||
cp /etc/linux_patch_api/whitelist.yaml.example /etc/linux_patch_api/whitelist.yaml
|
||||
chmod 640 /etc/linux_patch_api/whitelist.yaml
|
||||
chown linux-patch-api:linux-patch-api /etc/linux_patch_api/whitelist.yaml
|
||||
chown root:root /etc/linux_patch_api/whitelist.yaml
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@ -1,31 +1,15 @@
|
||||
# Arch Linux install hooks for linux-patch-api
|
||||
# Reference: debian/{preinst,postinst,prerm,postrm}
|
||||
# Matches Debian preinst/postinst behavior: no system user, root:root ownership
|
||||
|
||||
post_install() {
|
||||
# Create system group
|
||||
if ! getent group linux-patch-api &>/dev/null; then
|
||||
groupadd --system linux-patch-api
|
||||
fi
|
||||
|
||||
# Create system user
|
||||
if ! getent passwd linux-patch-api &>/dev/null; then
|
||||
useradd --system \
|
||||
--gid linux-patch-api \
|
||||
--home-dir /var/lib/linux_patch_api \
|
||||
--no-create-home \
|
||||
--shell /usr/bin/nologin \
|
||||
--comment "Linux Patch API Service" \
|
||||
linux-patch-api
|
||||
fi
|
||||
|
||||
# Create required directories
|
||||
mkdir -p /etc/linux_patch_api/certs
|
||||
mkdir -p /var/lib/linux_patch_api
|
||||
mkdir -p /var/log/linux_patch_api
|
||||
|
||||
# Set proper ownership
|
||||
chown -R linux-patch-api:linux-patch-api /var/lib/linux_patch_api
|
||||
chown -R linux-patch-api:linux-patch-api /var/log/linux_patch_api
|
||||
# Set proper ownership (service runs as root)
|
||||
chown -R root:root /var/lib/linux_patch_api
|
||||
chown -R root:root /var/log/linux_patch_api
|
||||
|
||||
# Set secure permissions
|
||||
chmod 750 /etc/linux_patch_api
|
||||
@ -37,13 +21,13 @@ post_install() {
|
||||
if [ ! -f "/etc/linux_patch_api/config.yaml" ]; then
|
||||
cp /etc/linux_patch_api/config.yaml.example /etc/linux_patch_api/config.yaml
|
||||
chmod 640 /etc/linux_patch_api/config.yaml
|
||||
chown linux-patch-api:linux-patch-api /etc/linux_patch_api/config.yaml
|
||||
chown root:root /etc/linux_patch_api/config.yaml
|
||||
fi
|
||||
|
||||
if [ ! -f "/etc/linux_patch_api/whitelist.yaml" ]; then
|
||||
cp /etc/linux_patch_api/whitelist.yaml.example /etc/linux_patch_api/whitelist.yaml
|
||||
chmod 640 /etc/linux_patch_api/whitelist.yaml
|
||||
chown linux-patch-api:linux-patch-api /etc/linux_patch_api/whitelist.yaml
|
||||
chown root:root /etc/linux_patch_api/whitelist.yaml
|
||||
fi
|
||||
|
||||
# Reload systemd daemon
|
||||
@ -90,7 +74,6 @@ post_remove() {
|
||||
systemctl daemon-reload 2>/dev/null || true
|
||||
|
||||
# Remove directories only if empty (preserve user data on upgrade/reinstall)
|
||||
# Arch doesn't have purge vs remove distinction like Debian
|
||||
rmdir --ignore-fail-on-non-empty /var/lib/linux_patch_api 2>/dev/null || true
|
||||
rmdir --ignore-fail-on-non-empty /var/log/linux_patch_api 2>/dev/null || true
|
||||
|
||||
|
||||
12
debian/changelog
vendored
12
debian/changelog
vendored
@ -1,3 +1,15 @@
|
||||
linux-patch-api (1.1.9-1) unstable; urgency=low
|
||||
|
||||
* Fix non-Ubuntu packages: align Arch, RPM, Alpine with Debian baseline
|
||||
* Remove system user creation (service runs as root)
|
||||
* Fix ownership to root:root across all platforms
|
||||
* Fix Alpine: co-locate install script with APKBUILD
|
||||
* Fix Arch: correct $startdir path in PKGBUILD
|
||||
* Fix RPM: add runtime deps, comment BuildRequires for CI
|
||||
* Add comprehensive installation docs for all platforms
|
||||
|
||||
-- Echo <echo@moon-dragon.us> Tue, 19 May 2026 21:54:00 -0500
|
||||
|
||||
linux-patch-api (1.1.8-1) unstable; urgency=low
|
||||
|
||||
* Fix FQDN resolution: prioritize hostname -f over /etc/hostname for full domain
|
||||
|
||||
@ -10,19 +10,22 @@ Source0: linux-patch-api-%{version}.tar.gz
|
||||
BuildArch: x86_64
|
||||
|
||||
# Build requirements
|
||||
# NOTE: Building in Debian container (node:18) - apt packages don't register in RPM db
|
||||
# Build tools ARE available (installed via apt-get in ci.yml), just won't validate
|
||||
# NOTE: CI uses rustup to install cargo/rust, so they are NOT available as RPM packages.
|
||||
# Only uncomment BuildRequires for native RPM build environments where cargo/rust
|
||||
# are installed via dnf/yum package manager.
|
||||
# BuildRequires: cargo >= 1.75
|
||||
# BuildRequires: rust >= 1.75
|
||||
# BuildRequires: systemd-rpm-macros # Handling systemd manually
|
||||
# BuildRequires: pkgconfig(systemd)
|
||||
# BuildRequires: gcc
|
||||
# BuildRequires: openssl-devel
|
||||
# BuildRequires: systemd-devel
|
||||
# BuildRequires: pkgconfig(systemd)
|
||||
|
||||
# Runtime requirements
|
||||
Requires: systemd
|
||||
Requires: libsystemd
|
||||
Requires: openssl-libs
|
||||
Requires: ca-certificates
|
||||
|
||||
# Description
|
||||
%description
|
||||
Linux Patch API provides a secure, mTLS-authenticated REST API for
|
||||
remote package management operations including:
|
||||
@ -69,28 +72,16 @@ cp configs/config.yaml.example %{buildroot}/etc/linux_patch_api/config.yaml.exam
|
||||
cp configs/whitelist.yaml.example %{buildroot}/etc/linux_patch_api/whitelist.yaml.example
|
||||
chmod 644 %{buildroot}/etc/linux_patch_api/*.example
|
||||
|
||||
# Pre-installation script
|
||||
# Pre-installation script - create directories (matches Debian preinst)
|
||||
%pre
|
||||
# Create system group
|
||||
getent group linux-patch-api > /dev/null || groupadd --system linux-patch-api
|
||||
|
||||
# Create system user
|
||||
getent passwd linux-patch-api > /dev/null || useradd --system \
|
||||
--gid linux-patch-api \
|
||||
--home-dir /var/lib/linux_patch_api \
|
||||
--no-create-home \
|
||||
--shell /usr/sbin/nologin \
|
||||
--comment "Linux Patch API Service" \
|
||||
linux-patch-api
|
||||
|
||||
# Create required directories
|
||||
mkdir -p /etc/linux_patch_api/certs
|
||||
mkdir -p /var/lib/linux_patch_api
|
||||
mkdir -p /var/log/linux_patch_api
|
||||
|
||||
# Set proper ownership
|
||||
chown -R linux-patch-api:linux-patch-api /var/lib/linux_patch_api
|
||||
chown -R linux-patch-api:linux-patch-api /var/log/linux_patch_api
|
||||
# Set proper ownership (service runs as root)
|
||||
chown -R root:root /var/lib/linux_patch_api
|
||||
chown -R root:root /var/log/linux_patch_api
|
||||
|
||||
# Set secure permissions
|
||||
chmod 750 /etc/linux_patch_api
|
||||
@ -98,19 +89,19 @@ chmod 750 /etc/linux_patch_api/certs
|
||||
chmod 755 /var/lib/linux_patch_api
|
||||
chmod 755 /var/log/linux_patch_api
|
||||
|
||||
# Post-installation script
|
||||
# Post-installation script - copy configs, enable service (matches Debian postinst)
|
||||
%post
|
||||
# Copy example configs if they don't exist
|
||||
if [ ! -f "/etc/linux_patch_api/config.yaml" ]; then
|
||||
cp /etc/linux_patch_api/config.yaml.example /etc/linux_patch_api/config.yaml
|
||||
chmod 640 /etc/linux_patch_api/config.yaml
|
||||
chown linux-patch-api:linux-patch-api /etc/linux_patch_api/config.yaml
|
||||
chown root:root /etc/linux_patch_api/config.yaml
|
||||
fi
|
||||
|
||||
if [ ! -f "/etc/linux_patch_api/whitelist.yaml" ]; then
|
||||
cp /etc/linux_patch_api/whitelist.yaml.example /etc/linux_patch_api/whitelist.yaml
|
||||
chmod 640 /etc/linux_patch_api/whitelist.yaml
|
||||
chown linux-patch-api:linux-patch-api /etc/linux_patch_api/whitelist.yaml
|
||||
chown root:root /etc/linux_patch_api/whitelist.yaml
|
||||
fi
|
||||
|
||||
# Reload systemd daemon
|
||||
@ -171,6 +162,21 @@ fi
|
||||
|
||||
# Changelog
|
||||
%changelog
|
||||
* Tue May 19 2026 Echo <echo@moon-dragon.us> - 1.1.9-1
|
||||
- Fix non-Ubuntu packages: align Arch, RPM, Alpine with Debian baseline
|
||||
- Remove system user creation (service runs as root)
|
||||
- Fix ownership to root:root across all platforms
|
||||
- Fix Alpine: co-locate install script with APKBUILD
|
||||
- Fix Arch: correct $startdir path in PKGBUILD
|
||||
- Fix RPM: add runtime deps, comment BuildRequires for CI
|
||||
- Add comprehensive installation docs for all platforms
|
||||
|
||||
* Tue May 19 2026 Echo <echo@moon-dragon.us> - 1.1.8-1
|
||||
- Fix RPM packaging: runtime deps, match Debian install behavior, comment BuildRequires for CI
|
||||
- Remove system user creation (service runs as root per systemd unit)
|
||||
- Fix ownership to root:root matching Debian package
|
||||
- Add openssl-libs and ca-certificates runtime dependencies
|
||||
|
||||
* Mon May 18 2026 Echo <echo@moon-dragon.us> - 1.1.8-1
|
||||
- Fix FQDN resolution: prioritize hostname -f over /etc/hostname
|
||||
- Fix display_name blank: add hostname field to enrollment request
|
||||
|
||||
34
tasks/fix-non-ubuntu-packages.md
Normal file
34
tasks/fix-non-ubuntu-packages.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Fix Non-Ubuntu Package Builds
|
||||
|
||||
## Goal
|
||||
All platform packages must produce identical install results as the Debian/Ubuntu package.
|
||||
|
||||
## Debian Baseline Behavior
|
||||
- No system user creation (service runs as root)
|
||||
- Directory ownership: root:root
|
||||
- Create dirs: /etc/linux_patch_api/certs, /var/lib/linux_patch_api, /var/log/linux_patch_api
|
||||
- Permissions: 750 on config dirs, 755 on data/log dirs
|
||||
- Copy .example configs to live configs if not present
|
||||
- Enable service (systemd or rc-update)
|
||||
- Print next-steps message
|
||||
|
||||
## Tasks
|
||||
- [x] 1. Fix Arch linux-patch-api.install - removed system user creation, root:root ownership, matches Debian
|
||||
- [x] 2. Fix Arch build-arch.sh - fixed $startdir path, added source=() array
|
||||
- [x] 3. Fix RPM linux-patch-api.spec - uncommented BuildRequires, added runtime deps, removed system user, root:root ownership
|
||||
- [x] 4. Fix Alpine linux-patch-api.apk-install - removed system user creation, root:root ownership, matches Debian
|
||||
- [x] 5. Fix Alpine build-alpine.sh - co-located install script with APKBUILD, used install -Dm commands
|
||||
- [ ] 6. Verify all platforms produce consistent results (needs CI run)
|
||||
|
||||
## Changes Summary
|
||||
|
||||
### Arch Linux
|
||||
- **configs/linux-patch-api.install**: Removed user/group creation, changed ownership to root:root, matches Debian preinst/postinst
|
||||
- **build-arch.sh**: Fixed PKGBUILD package() to use $startdir (not $srcdir), added source=() array
|
||||
|
||||
### RPM (Fedora/RHEL/CentOS)
|
||||
- **linux-patch-api.spec**: Uncommented BuildRequires (cargo, rust, gcc, openssl-devel, systemd-devel, pkgconfig(systemd)), added runtime Requires (openssl-libs, ca-certificates), removed system user creation from %pre, changed ownership to root:root in %pre, matches Debian behavior in %post
|
||||
|
||||
### Alpine Linux
|
||||
- **configs/linux-patch-api.apk-install**: Removed addgroup/adduser, changed ownership to root:root, matches Debian preinst/postinst
|
||||
- **build-alpine.sh**: Restructured to co-locate install script with APKBUILD in workspace directory, used install -Dm commands in package() function, fixed $startdir references
|
||||
Reference in New Issue
Block a user