Jump to content

PM Software Stack

From Pulsed Media Wiki


PM Software Stack (PMSS) is an open-source server management stack that turns a Debian server into a multi-tenant seedbox. It handles user provisioning, application installation, traffic accounting, resource isolation, and ongoing maintenance through automated cron jobs. PMSS is licensed under the GNU General Public License v3 and hosted on GitHub at https://github.com/MagnaCapax/PMSS under the MagnaCapax organization.

PMSS is free to use for anyone. There are no commercial restrictions. Third parties may use it to provide seedbox services, and minimalistic whitelabeling features are included. The project is primarily written in PHP (49.5%), JavaScript (37.5%), CSS (7.6%), and Shell (4.9%).

Overview

PMSS manages the full lifecycle of a seedbox server. It installs and configures torrent clients, web servers, VPN endpoints, media applications, and supporting tools. It provisions isolated user accounts with disk quotas, traffic limits, and cgroup-based resource controls. Cron-driven watchdogs keep services running. A two-phase update system allows the fleet to be upgraded without breaking existing users.

The stack runs on Debian 10, 11, and 12. Debian 10 is considered stable, Debian 12 is in production, and Debian 13 (trixie) is experimental. Each user gets a dedicated lighttpd process, a dedicated rTorrent (or Deluge/qBittorrent) instance, per-user cgroup resource limits, and rootless Docker.

PMSS supports the following primary torrent clients:

  • rTorrent with ruTorrent web interface (default)
  • Deluge with web UI
  • qBittorrent (qbittorrent-nox) with web UI

Media applications, Usenet tools, cloud sync, VPN, and other services are also supported (see Supported applications).

Production use

Pulsed Media uses PMSS across its entire seedbox fleet. As of early 2026, PMSS manages over 80 production servers across two datacenters in Finland (Helsinki and Kerava), handling hardware ranging from 1 Gbps shared plans on RAID-5 arrays to 10 Gbps dedicated servers with NVMe caching. The same codebase runs on both bare-metal hosts and Proxmox virtual machines.

The fleet spans multiple Debian releases simultaneously (10, 11, and 12 in production), which is why PMSS maintains per-release dpkg baselines and template sets. Third parties also use PMSS to run their own seedbox hosting services under the GPL-3.0 license.

History

PMSS started as an internal tool at Pulsed Media for managing seedbox servers. The install.sh copyright header dates to 2010. Before PMSS, seedbox provisioning was done by hand — each user account set up manually, each torrent client configured individually. PMSS automated this into a repeatable process. The project was later open-sourced under GPL-3.0 through the MagnaCapax GitHub organization, making it available to anyone who wants to run a multi-tenant seedbox server.

Milestones based on the changelog and repository history:

Year Event
2010 Initial development of install.sh bootstrap
2011 Earliest public changelog entries on the wiki
2015 Conversion to per-user lighttpd processes (from a single shared httpd). Public web hosting directories added. ruTorrent updated to 3.7. rTorrent updated to 0.9.6/0.13.6. OpenVPN support added. Sonarr/SABnzbd integration. Cgroup resource control introduced.
2016 Debian 8 (jessie) support. Rclone, Syncthing, FileBot added. FireHOL firewall suite compiled from source. IOstat and SMART monitoring cron jobs added.
2019 rTorrent bumped to 0.9.8/0.13.8. Let's Encrypt automation. WireGuard configuration (later releases). Bonding interface support. ruTorrent updated to 3.9.
2020 Debian 10 production support. Deluge installation and configuration. qBittorrent support. Tracker cleaner for problematic public trackers. FireQOS traffic shaping. ruTorrent updated to 3.10. New file manager.
2021 qBittorrent cron watchdog. Rclone GUI integration. Lighttpd streaming config. Disk I/O and traffic monitoring improvements.
2022 Systemd user slice cgroup limits (CPU weight, IO weight, memory soft/hard limits). Rootless Docker per user. Custom lighttpd config directory (~/.lighttpd/custom.d/). Custom GUI frames. ruTorrent themes from community contributions. MediaInfo repo integration.
2023 Development moved to GitHub. Docker rootless + docker-compose per user. ~/bin added to PATH. Changelog continues on GitHub.
2024-2025 Major refactoring: two-phase updater architecture, dpkg baseline snapshots per Debian release, modular app installers under scripts/lib/update/apps/, cgroup v2 support with dual-path detection, WireGuard installer with hermetic tests, install-media-stack.sh for Servarr/Jellyfin/SABnzbd. 143 development tests. CI via GitHub Actions. Architecture Decision Records (ADRs). Dist-upgrade automation (Debian 10 to 11 to 12).
2026 Continued active development. Over 1100 commits, 9 contributors. Agentic CI pipeline (GitHub Actions tests on every push). 82-server fleet upgraded to Debian 12 via automated dist-upgrade.

Installation

PMSS requires a minimal Debian installation (10, 11, or 12). The installer is run as root.

Quick install (latest release)

Download and run the installer:

wget -qO /tmp/pmss-install.sh https://github.com/MagnaCapax/PMSS/raw/main/install.sh
bash /tmp/pmss-install.sh

The project README also documents a one-liner that pipes wget output directly to bash. See the README for that variant.

Install from git main branch

wget -qO /tmp/pmss-install.sh https://github.com/MagnaCapax/PMSS/raw/main/install.sh
bash /tmp/pmss-install.sh git/main

Pin a specific release

wget -qO /tmp/pmss-install.sh https://github.com/MagnaCapax/PMSS/raw/main/install.sh
bash /tmp/pmss-install.sh release:2023-07-22

What the installer does

install.sh is a thin bootstrapper. Its responsibilities are limited to:

  1. Ensure core tooling is present: PHP CLI, git, curl, wget, ca-certificates, rsync, unzip, vim, build-essential, autoconf, automake, pkg-config, libtool, subversion.
  2. Prompt for hostname configuration (/etc/hostname). The hostname must be a fully-qualified domain name (FQDN) because PMSS reads it for certbot, proftpd, and nginx SSL configuration.
  3. Configure /etc/fstab for disk quotas on /home. PMSS expects /home to be a dedicated filesystem with quota options: usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv1. Performance mount options (nofail,noatime,lazytime,commit=30) are also applied for ext4.
  4. Apply multi-tenant hardening: /proc mounted with hidepid=2 (so users cannot see each other's processes), and systemd.unified_cgroup_hierarchy=0 added to GRUB for rootless Docker compatibility.
  5. Download the PMSS snapshot (git clone or release tarball) and deploy it to /scripts, /etc, and /var.
  6. Hand off to /scripts/update.php for the main configuration work.
  7. Run post-install setup: setupRootCron.php, setupSkelPermissions.php, quotaFix.php, ftpConfig.php.

Installer flags

Flag Description
--hostname=<name> Set hostname without interactive prompt
--skip-hostname Keep the current hostname
--quota-mount=<path> Inject quota options for the specified fstab mount
--skip-quota Skip quota configuration
--non-interactive Skip all interactive prompts (hostname and quota)
--skip-upgrade Skip the initial apt full-upgrade
--dry-run Print the plan without changing the system
--skip-update Stop after staging files; do not run update.php
--scripts-only Pass through to update.php --scripts-only

The installer performs a preflight check requiring at least 2 GiB free disk space on /. If PMSS is already installed, the installer detects existing markers and hands off to update.php directly.

Logs are written to /var/log/pmss-install.log. The update phase logs to /var/log/pmss/update.log and /var/log/pmss-update.jsonl.

Disk space requirements

The installer needs at least 2 GiB free on the root partition. The /home partition should be sized for user data storage. PMSS itself uses approximately 500 MB for scripts, applications, and compiled binaries.

Architecture

Directory layout

PMSS organizes files across several standard Linux locations:

Path Purpose
/scripts/ Main PMSS scripts: user management, update system, utilities
/scripts/cron/ Cron job scripts (watchdogs, monitoring, traffic accounting)
/scripts/util/ Utility scripts (network setup, lighttpd config, Let's Encrypt, user transfer)
/scripts/lib/ Shared PHP libraries (traffic, resources, cgroup, rtorrent config, update modules)
/scripts/lib/update/ Phase 2 update modules (distro detection, package management, app installers, web stack)
/scripts/lib/update/apps/ Application-specific installers (rtorrent, deluge, docker, sonarr, radarr, etc.)
/scripts/lib/update/dpkg/ Per-release dpkg baseline snapshots (selections-debian10.txt, selections-debian11.txt, selections-debian12.txt)
/scripts/lib/tests/ Test suites (development and production tiers)
/scripts/testing/ Test runner scripts (test-php.sh, test-bash.sh, test-all.sh, lint tools)
/etc/seedbox/config/ Configuration templates and runtime config (see Configuration)
/etc/seedbox/runtime/ Runtime state (port assignments, etc.)
/etc/seedbox/skel/ Skeleton files for new user accounts
/etc/skel/ User skeleton: default .bashrc, web GUI files, media stack installer, bin scripts
/var/log/pmss/ Log directory for all PMSS cron jobs and operations
/var/www/ Default web server error pages and status endpoint
/home/<user>/ Per-user home directories containing data, sessions, configs, and web content

Two-phase update system

PMSS updates run in two phases:

Phase 1 (scripts/update.php):

  • Parses the requested source spec (git/<branch>, release[:tag], or a custom repository)
  • Fetches the snapshot (shallow git clone or release tarball)
  • Stages the snapshot by replacing /scripts and refreshing /etc and /var
  • Records the version under /etc/seedbox/config/version and version.meta
  • Re-runs itself if the fetched snapshot updated update.php
  • Invokes phase 2

Phase 2 (scripts/util/update-step2.php):

  • Detects distro name, version, and codename
  • Enforces non-interactive apt settings and finishes pending dpkg configurations
  • Refreshes APT repositories and installs all queued packages (this ordering is mandatory)
  • Prepares the host: cgroups, systemd slices, base permissions, MOTD, locales
  • Runs application installers under scripts/lib/update/apps/*.php
  • Configures the web stack (lighttpd/nginx)
  • Updates every user environment and rescans skeletons
  • Reapplies network templates and security hardening
  • Logs completion markers

Every step flows through a shared runStep() helper that logs to /var/log/pmss-update.log, records JSON events to /var/log/pmss-update.jsonl, and collects profiling metadata.

Update commands

# Update to latest release
/scripts/update.php release

# Update to git main branch
/scripts/update.php git/main

# Deploy scripts only (skip phase 2, useful for hotfixes)
/scripts/update.php git/main --scripts-only

# Dry-run (log without changes)
/scripts/update.php release --dry-run

# Dist-upgrade to Debian 12
/scripts/update.php --dist-upgrade=12

The updater self-refreshes from GitHub at the start of every run. Version specs normalize user input so main, git main, and git/main produce identical results.

Dist-upgrade automation

PMSS can automate Debian major version upgrades (e.g., Debian 10 to 11, or 11 to 12) through the --dist-upgrade flag:

# Upgrade to Debian 12 (from whatever version is currently running)
/scripts/update.php git/main --dist-upgrade=12

# Upgrade one step (e.g., 10 to 11)
/scripts/update.php git/main --dist-upgrade=11

The --dist-upgrade=N parameter sets the maximum target Debian version. PMSS handles source list updates, non-interactive apt configuration, pending dpkg resolution, and the full-upgrade itself. After the OS upgrade completes, PMSS continues into its normal phase 2, which reinstalls and reconfigures all applications for the new Debian release.

The dist-upgrade always runs through update.php as the entry point. This is mandatory because update.php handles script self-update, cron backup, event logging, and phase 2 continuation that a standalone upgrade script would skip. JSON log events (dist_upgrade_start, dist_upgrade_end) are recorded for audit.

Each Debian release has a corresponding APT sources template (template.sources.buster, template.sources.bullseye, template.sources.bookworm, template.sources.trixie) and a dpkg baseline snapshot (selections-debian10.txt through selections-debian12.txt) that define the expected package set. After upgrade, the baseline snapshot for the new release is applied to pull in any packages that differ between versions.

In February 2026, this mechanism was used to upgrade 82 Pulsed Media production servers from Debian 10 to Debian 12 with zero data loss.

Recovery from failed upgrades

If a dist-upgrade fails mid-way (SSH dropped, dpkg hung on config prompts, PHP missing), recovery follows these steps:

  1. Gain shell access via KVM, IPMI, or VNC console
  2. Kill suspended apt/dpkg processes: killall apt apt-get dpkg
  3. Resume dpkg configuration: DEBIAN_FRONTEND=noninteractive dpkg --configure -a
  4. Restore network if interface names changed (check ls /sys/class/net, update /etc/network/interfaces)
  5. Reinstall PHP if missing: apt-get install -y php-cli php-curl php-json php-xml php-mbstring php-zip
  6. Finish the upgrade: apt-get -f install && apt-get full-upgrade -y
  7. Verify services: systemctl status sshd proftpd nginx

ProFTPD sometimes hangs during dist-upgrade on config file prompts. The fix is to force the old config: apt-get install -o Dpkg::Options::="--force-confold" proftpd-basic.

Full recovery procedures are documented in docs/recovery.md.

Config template system

PMSS uses template files stored in /etc/seedbox/config/ to generate service configurations. Templates use placeholder tokens (e.g., ##memoryMax, ##username) that are substituted at provisioning or update time. Generated configs are written to system locations (/etc/nginx/, /etc/proftpd/, user home directories). The template system is idempotent: running the configuration scripts multiple times converges on the same state.

Supported applications

PMSS installs and manages the following applications. Each has a dedicated installer module under scripts/lib/update/apps/ and, where applicable, cron watchdogs and user-facing GUI integration.

Torrent clients

Application Description Installer module Cron watchdog
rTorrent + libtorrent The default torrent client. Compiled from source along with xmlrpc-c. Per-user instances managed via .rtorrent.rc config and SCGI socket. rtorrent.php checkRtorrent.php (every 2 minutes)
ruTorrent Web-based frontend for rTorrent. Served through per-user lighttpd. Includes themes (MaterialDesign default), plugins (RSS, autodl, hddquota, etc.), and a file manager. Deployed via /etc/skel/www/rutorrent/ checkGui.php (every 10 minutes)
Deluge Alternative torrent client with web UI. On Debian 10, built from source with pip; on newer releases, installed from apt packages. Accessed via nginx reverse proxy at /deluge-USERNAME/. deluge.php checkDelugeInstances.php (every minute)
qBittorrent qbittorrent-nox with WebUI. Configured with randomized external port. Reverse-proxied through lighttpd. Installed via packages.php, configured in userConfig.php checkQbittorrentInstances.php (every 2 minutes)

Media applications

PMSS provides two paths for media application installation: a native per-user media stack installer, and rootless Docker containers.

Native media stack (install-media-stack.sh)

The install-media-stack.sh script in /etc/skel/ installs a self-contained media stack in each user's home directory under ~/.bin with configs under ~/.config. It installs:

Application Type Runtime
Radarr Movie manager (Servarr) .NET 8
Sonarr v4 TV show manager (Servarr) .NET 8
Prowlarr Indexer manager (Servarr) .NET 8
Jellyfin Media server .NET 8
SABnzbd Usenet downloader Python (venv)
Cloudplow Cloud upload automation Python (venv)

All apps bind to 127.0.0.1 and are reverse-proxied by per-user lighttpd to https://<host>/public-<user>/<app>/. The installer is idempotent, supports --dry-run, and handles GLIBC compatibility on older Debian releases by pinning Radarr versions.

Usage:

# Install (from local copy)
bash install-media-stack.sh

# Dry-run (verify URLs only)
bash install-media-stack.sh --dry-run

# Pin Radarr version on Debian 11
bash install-media-stack.sh --radarr-pin=v5.10.4.9218

The installer can also be fetched directly from GitHub. See the install-media-stack documentation for all options.

System-level application installers

Phase 2 of the update also installs these applications system-wide:

Application Installer module Description
Sonarr sonarr.php Installed under /opt/Sonarr via GitHub Releases API
Radarr radarr.php Installed under /opt/Radarr via GitHub Releases API
Mono mono.php .NET runtime for legacy Sonarr, installed from Debian packages
FileBot filebot.php Media renaming tool, installed from deb package (v4.9.4)
pyLoad pyload.php Download manager, installed in /opt/pyload virtualenv

Cloud and sync

Application Installer module Description
rclone rclone.php Cloud storage sync. Binary installed from rclone.org. GUI integration with user-controllable start/stop. Cron watchdog: checkRcloneInstances.php (every 2 minutes).
Syncthing syncthing.php Encrypted file sync between devices. Binary installed from PMSS remote package host.
BTSync / Resilio btsync.php BTSync 1.4 and Resilio 2.2 binaries maintained under /usr/bin.
acd_cli acdcli.php Amazon Cloud Drive CLI, installed in /opt/acd_cli virtualenv.
FlexGet python.php RSS/automation tool, installed via pip in a virtualenv.

VPN

Application Description
WireGuard Host-level VPN endpoint at /etc/wireguard/wg0.conf. Keys auto-generated. Per-user peer config via ~/.wireguard-public-key. Cron watchdog: checkWireguard.php (every 5 minutes). Peer refresh: wireguardPeersRefresh.php (every 6 minutes). Each user gets a unique /32 address inside 10.90.90.0/24. Documented in docs/wireguard.md.
OpenVPN EasyRSA-based server/client configs. Client bundles written to /etc/skel/www. Currently disabled on newer releases pending support updates.

Docker

PMSS provisions rootless Docker for each user account. The Docker daemon runs without root privileges under each user's namespace. Details:

  • Docker CE with buildx and compose plugins, installed from Docker's official APT repository
  • User namespaces (subuid/subgid) configured per user
  • DOCKER_HOST environment variable set in ~/.bashrc
  • Storage driver: fuse-overlayfs preferred (written to ~/.config/docker/daemon.json)
  • Watchdog: checkRootlessDocker.php (every 5 minutes)
  • Helper: ~/bin/docker-install-wireguard.sh for linuxserver.io WireGuard container
  • Operator control: /scripts/userDocker.php USER {start|stop|restart|status}

Documentation for running LinuxServer.io containers is included in docs/linuxserver.io.md.

Other tools

Application Description
vnStat Network traffic monitoring, configured for the detected uplink interface
FireHOL Firewall suite, compiled from source (v3.1.6)
iprange IP range tool, compiled from source (v1.0.4)
mediainfo Media file metadata tool, installed from MediaArea repository
certbot Let's Encrypt certificate automation

User management

Adding a user

/scripts/addUser.php USERNAME PASSWORD MAX_RAM_MB DISK_QUOTA_IN_GB [trafficLimitGB]

Parameters:

  • USERNAME -- login name. Normalized to lowercase. Must match [a-z][a-z0-9]{2,7} (3-8 characters, starting with a letter).
  • PASSWORD -- initial password. Use rand for a random password.
  • MAX_RAM_MB -- account memory limit, used for cgroup limits and rTorrent tuning.
  • DISK_QUOTA_IN_GB -- storage quota.
  • trafficLimitGB (optional) -- monthly traffic cap.

On success, addUser.php:

  1. Creates the Unix user and home directory
  2. Assigns an HTTP service port via portManager.php
  3. Writes rTorrent and ruTorrent configuration
  4. Enables quotas and traffic limits
  5. Applies cgroup resource defaults
  6. Starts rTorrent and lighttpd
  7. Emits a summary marker to stdout and /var/log/pmss/addUser.log

A per-user lock file prevents concurrent addUser runs for the same username. Provisioning fails fast if the user already exists or critical steps fail.

Example:

/scripts/addUser.php alice rand 512 100 500

This adds user alice with a random password, 512 MB memory limit, 100 GB disk quota, and 500 GB monthly traffic limit.

Changing user settings

/scripts/util/userConfig.php USERNAME MAX_RAM_MB DISK_QUOTA_IN_GB [TRAFFIC_LIMIT_GB] [CPUWEIGHT] [IOWEIGHT] [CPUQUOTAPCT]

This rewrites rTorrent and ruTorrent configs, applies disk quota changes, updates cgroup limits, and restarts the user's rTorrent process. Optional parameters allow setting CPU weight, IO weight, and CPU quota percentage for the user's systemd slice.

Suspending and unsuspending

/scripts/suspend.php USERNAME
/scripts/unsuspend.php USERNAME

Suspension stops all user processes, disables login, and serves a suspension notice page. PMSS uses an epoch (1970-01-01) account expiry date as the suspension marker. Cron watchdogs skip suspended users.

Terminating a user

/scripts/terminateUser.php USERNAME [--confirm]

Terminates the user account. Kills all user processes, removes lighttpd and nginx configs, releases the assigned port, reverts the cgroup slice, and deletes the home directory. The --confirm flag is required for non-interactive runs.

Recreating a user

/scripts/recreateUser.php USERNAME

Rebuilds a user's environment without losing data. Copies the htpasswd file from the old configuration.

Transferring a user between servers

userTransfer.php migrates a user account from one PMSS server to another over SSH. The script always runs on the destination server and pulls data from the source. It never pushes.

# Basic transfer (same username on both servers)
/scripts/util/userTransfer.php alice source-server.pulsedmedia.com

# Transfer with username change (local alice, remote bob)
/scripts/util/userTransfer.php alice bob source-server.pulsedmedia.com

The password for the remote server can be passed via the PMSS_USER_TRANSFER_PASSWORD environment variable. If not set, the script prompts interactively with echo disabled and requires double-entry confirmation.

How the transfer works

The transfer runs in two phases with multiple passes each:

Main passes (default 31): rsync pulls the user's home directory from the source, excluding volatile files (session data, GUI state, lighttpd configs, traffic counters, cache). Each pass picks up files that changed since the last pass. Random sleep intervals (60-360 seconds by default) space out the passes to avoid saturating the network.

Final passes (default 3): rsync pulls the volatile paths that were excluded during main passes: session/, www/rutorrent/share/, ~/.lighttpd/custom.d/, ~/.local/, and www/public/.

After both phases complete, the script normalizes file ownership via userPermissions.php and creates an rTorrent restart marker so the torrent client picks up the migrated session data.

Transfer options

Option Default Description
--main-passes N 31 Number of bulk rsync passes
--final-passes N 3 Number of volatile-path rsync passes
--sleep-min N 60 Minimum seconds between passes
--sleep-max N 360 Maximum seconds between passes
--no-sleep -- Disable sleep between passes
--dry-run -- Log planned steps without executing
--print-password -- Print password at end (not recommended)

Safety measures

The transfer script has multiple safety checks:

  • Must run as root
  • Refuses to transfer system users (UID < 1000)
  • Refuses if the local home directory is a symlink
  • Validates the home path against /etc/passwd
  • Uses rsync -av without --delete (never removes files on the destination)
  • Temporary Expect scripts are stored in a root-only (mode 0700) scratch directory and cleaned up on exit
  • Passwords are cleared from environment after use
  • Path-escape check before renaming ruTorrent user directories

If the remote and local usernames differ, the script renames the ruTorrent share directory to match the local username after transfer.

All steps are logged to /var/log/pmss/userTransfer.log with timestamped prefixes ([START], [INFO], [WARN], [OK], [SKIP]).

For large accounts (multi-terabyte), transfers can run for days. The PMSS_COMMAND_TIMEOUT environment variable controls the maximum runtime. Set it high (e.g., 172800 for 48 hours) or the script disables it internally for the duration of the transfer.

Other user management tools

Script Description
listUsers.php List managed tenant usernames, one per line
changePw.php Change a user's password (updates per-user htpasswd file)
showTraffic.php Show per-user traffic summary (--json available)
showResources.php Show per-user resource usage
userTorrents.php Count torrents per user (--by-client for breakdown)
userSetting.php View or modify per-user settings
userSlice.php View user systemd slice status
wireguardPeersStatus.php Show WireGuard peer connection status

Web interface

Each PMSS user has a web-accessible interface served by their dedicated lighttpd process, reverse-proxied through nginx. The interface is available at https://SERVER/user-USERNAME/ and protected by HTTP Basic authentication.

GUI tabs and features

The default GUI (/etc/skel/www/) provides:

  • ruTorrent -- Full ruTorrent web interface for rTorrent, with themes (MaterialDesign default) and plugins including RSS, autodl-irssi, hddquota, autotools, unpack, and a retracker plugin.
  • File Manager -- Web-based file browser for downloading files from the seedbox. Enforces HTTPS.
  • Info/Stats -- Server statistics page showing RAM usage, disk quota, traffic consumption with daily charts (Chart.js), and IP information.
  • Welcome -- Overview page with quota warnings, traffic limit meter, announcements RSS feed, download links for OpenVPN/WireGuard configs, and application control buttons (rTorrent restart, lighttpd restart).
  • Deluge -- Deluge web UI tab (shown when enabled via ~/.delugeEnable)
  • qBittorrent -- qBittorrent web UI tab
  • rclone -- rclone web GUI tab with start/stop controls
  • Custom frames -- User-definable tabs via ~/.customFrames file (format: appname|tooltip|label|url, one per line)

Public web hosting

Each user has a public web hosting directory at ~/www/public/, accessible without authentication at https://SERVER/public-USERNAME/. Users can host static files or PHP applications in this directory.

Whitelabeling

PMSS includes basic whitelabeling support for third-party operators who want to run their own seedbox service. The GUI header, footer, welcome text, and announcements RSS feed URL can be customized through configuration files. Custom CSS and JavaScript can be added to the GUI skeleton in /etc/skel/www/. The MOTD template at /etc/seedbox/config/template.motd can be rewritten with the operator's own branding.

Since PMSS is GPL-3.0, the only licensing requirement is that modified versions remain open-source if distributed. Operators who run PMSS on their own servers without distributing the code have no obligation to publish changes.

Custom lighttpd configuration

Users can add custom lighttpd directives in ~/.lighttpd/custom.d/*.conf. These files are included by the main per-user lighttpd config. A lighttpd restart (via the GUI button or SIGUSR1) is required to load changes.

Per-user PHP

Each user gets a per-user php.ini at ~/.lighttpd/php.ini with settings optimized for seedbox use: 512 MiB memory limit, 16 GiB max upload file size, 50 max simultaneous uploads, 90-second max execution time.

Network and traffic management

Traffic accounting

PMSS tracks per-user traffic using iptables owner-match rules. The setupNetwork.php script installs monitoring rules that tag packets by user. Traffic data is collected and aggregated by cron jobs:

  • trafficLog.php -- Captures traffic counters every 5 minutes
  • trafficIngressLog.php -- Captures ingress traffic counters every 5 minutes
  • trafficStats.php -- Aggregates raw logs into monthly statistics (twice per hour)
  • trafficIngressStats.php -- Aggregates ingress logs (twice per hour)
  • trafficLimits.php -- Enforces per-user traffic limits (hourly)

Daily traffic consumption is saved to each user's ~/.trafficData file for display in the GUI stats page. Local network traffic (defined in /etc/seedbox/config/localnet) is tracked separately.

Bandwidth shaping

PMSS uses FireQOS for per-user bandwidth shaping. The setupNetwork.php script generates a FireQOS configuration from the template at /etc/seedbox/config/template.fireqos. Users are assigned balanced priority by default.

Traffic limits can be set per user:

/scripts/util/userTrafficLimit.php USERNAME LIMIT_GB

When a user exceeds their traffic limit, progressive throttling can be applied. This is controlled by the progressiveThrottleEnabled, progressiveThrottleFloorPercent, and progressiveThrottleGracePercent settings in /etc/seedbox/config/network.

Firewall

setupNetwork.php configures iptables rules including:

  • Bogon/martian network drop filters
  • NAT and forwarding for VPN tunnels (WireGuard and OpenVPN)
  • Per-user traffic monitoring rules
  • Connmark-based packet tagging
  • TCP SACK mitigation rules

The network configuration file at /etc/seedbox/config/network defines the interface name and link speeds. The local network file at /etc/seedbox/config/localnet defines internal network ranges (defaults to 185.148.0.0/22).

Security

User isolation

PMSS isolates users at multiple levels:

  • Process visibility: /proc is mounted with hidepid=2 so users cannot see each other's processes.
  • File permissions: /home has o-rw permissions. Each user's home directory is owned by that user with restricted group and other permissions (750). User permissions are managed by userPermissions.php.
  • Per-user lighttpd: Each user has their own lighttpd + php-cgi process pair, running as that user. No shared web server process.
  • Per-user rTorrent/Deluge/qBittorrent: Torrent client instances run under the user's own UID.
  • Rootless Docker: Docker daemons run without root privileges, using user namespace mapping.
  • SCGI socket: rTorrent communicates via a per-user Unix socket (~/.rtorrent.socket) with 770 permissions.

Cgroup resource control

PMSS uses Linux cgroups (via systemd user slices) for per-user resource limits. On Debian 11/12, cgroup v2 (unified) is used. On Debian 10, cgroup v1.

Default limits:

  • MemoryHigh: >= 250 MiB floor, ~10% of total RAM
  • MemoryMax: min(1.5x MemoryHigh, 95% of total RAM)
  • CPUWeight: 200
  • IOWeight: 200
  • TasksMax: 4096
  • CPUQuota: 85% of total logical cores

Root (user-0.slice) is always unlimited. A boot-time check and periodic cron job (cgroupRootCheck.php) enforce this.

Per-host overrides can be set in /etc/seedbox/config/cgroup.policy.php:

<?php
return [
  'cpuWeight'     => 100,
  'ioWeight'      => 100,
  'tasksMax'      => 2048,
  'memoryHighMiB' => 500,
  'memoryMaxMiB'  => 750,
];

Per-user adjustments:

# View current limits
/scripts/util/userConfigCgroup.php alice --status

# Apply custom limits
/scripts/util/userConfigCgroup.php alice --apply --cpu-weight=300 --memory-high=600

# Apply IO throttles for HDD
/scripts/util/userConfigCgroup.php alice --apply --device=/home --io-profile=hdd

# Reset to defaults
/scripts/util/userConfigCgroup.php alice --apply --wipe

IO profiles:

  • hdd: IOWeight=200, read/write bandwidth throttles (5M/10M), IOPS throttles (100/100)
  • nvme: IOWeight=200, no throttles (weights have limited effect on NVMe)
  • bulk: Higher IOWeight (~500), CPUWeight (~300), TasksMax (~8192) for throughput

Process priority management

The root crontab applies ionice and renice rules every minute to ensure fair resource sharing:

Process ionice class renice
rtorrent class 2 (best-effort), priority 3 --
deluged class 2, priority 3 --
qbittorrent-nox class 2, priority 3 --
bash class 1 (realtime), priority 2 --
php class 2, priority 6 --
sftp-server class 2, priority 7 --
proftpd class 2, priority 7 --
rar, unrar, tar, cp, mktorrent class 3 (idle) --
mono -- +17
ffmpeg class 3 (idle) +18
rclone class 3 (idle) +17
xmrig -- +19

Systemd service hardening

systemdServicesGuard.php runs every minute and enforces that unwanted system-wide daemons stay stopped, disabled, and masked. This includes apache2, deluged, deluge-web, exim4, transmission-daemon, and redis-server. These services should only run as per-user instances, not system-wide.

SSH and FTP

PMSS configures sshd via /etc/seedbox/config/template.sshd_config and ProFTPD via /etc/seedbox/config/template.proftpd. ProFTPD is configured with passive ports 60000-65535 and a maximum of 16 connections per user/host.

Cron jobs and automation

PMSS installs a root crontab from /etc/seedbox/config/root.cron. This canonical template is applied on fresh hosts via crontab etc/seedbox/config/root.cron. On existing hosts, setupRootCron.php merges any missing entries without overwriting operator customizations.

The crontab drives the majority of PMSS's runtime behavior. Without it, watchdogs do not run, traffic is not accounted, and crashed services are not restarted. If the crontab is lost or corrupted, restoring it from root.cron recovers normal operation immediately.

The following tables list the cron jobs grouped by function. Intervals shown are the defaults from root.cron; operators can adjust timing to suit their hardware.

Service watchdogs

Script Interval Description
checkRtorrent.php 2 min Monitor rTorrent instances and restart as needed. Also checks for socket file. Kills duplicate instances.
checkLighttpdInstances.php 1 min Confirm each user's lighttpd/php-cgi pair is running. Also checks HTTP 401 response. Restarts missing instances.
checkDelugeInstances.php 1 min Start Deluge instances if enabled via ~/.delugeEnable. Logs actions.
checkQbittorrentInstances.php 2 min Restart qBittorrent if processes exit.
checkRcloneInstances.php 2 min Maintain rclone mount processes.
checkRootlessDocker.php 5 min Restart per-user Docker daemons when they exit. Logs to /var/log/pmss/rootlessDocker.log.
checkWireguard.php 5 min Ensure WireGuard kernel module is loaded and wg-quick@wg0 stays active.
checkGui.php 10 min Verify the management GUI index.php is intact. Copies a fresh one if corrupted (can happen when user exceeds quota).
checkDirectories.php hourly + @reboot Repair expected directory hierarchy if it drifts.
systemdServicesGuard.php 1 min Enforce stop/disable/mask policy for unwanted system daemons.
cgroupRootCheck.php 2 hours + @reboot Ensure root slice is unlimited. Fixes limits if misconfigured.

Monitoring and statistics

Script Interval Description
trafficLog.php 5 min Capture per-user traffic counters from iptables
trafficIngressLog.php 5 min Capture ingress traffic counters
trafficStats.php 30 min Aggregate traffic logs into monthly statistics
trafficIngressStats.php 30 min Aggregate ingress traffic logs
trafficLimits.php hourly Refresh per-user traffic throttling
resourceLog.php 5 min Log per-user resource usage
resourceStats.php 30 min Aggregate resource logs
diskIostat.php 5 min Collect disk I/O metrics (iostat)
cpuStat.php 15 min Record CPU usage statistics
systemStatsLog.php 30 min System-wide statistics snapshot
processSnapshot.php 30 min Process tree snapshots for postmortem analysis
quotaSnapshot.php daily Daily quota usage snapshot
resourceSnapshot.php daily Daily resource usage snapshot
storageHealthSnapshot.php 6 hours Storage health data collection
updateQuotas.php 1 min Refresh user disk quota information

Maintenance

Script Interval Description
userTrackerCleaner.php 2 hours Remove known problematic public trackers from non-private torrents. Per-user opt-out via ~/.trackerCleanerDisable. Backs up originals before modification. Processes at most 500 torrents per user per run (see Tracker cleaner).
cleanupUserDb.php daily (02:30) Prune stale entries from /etc/seedbox/config/users/*.json
certbotRenewalCheck.php 6 hours Check and renew Let's Encrypt certificates
wireguardPeersRefresh.php 6 min Rebuild WireGuard config from user public keys
backupEtc.sh 1st and 15th of month Snapshot /etc into timestamped archives

Reboot tasks

On system boot (@reboot):

  1. checkDirectories.php -- Repair directory hierarchy
  2. setupNetwork.php (1s delay) -- Reapply firewall and traffic rules
  3. checkLighttpdInstances.php (5s delay) -- Start user lighttpd processes
  4. checkRtorrent.php (10s delay) -- Start user rTorrent instances
  5. motdGenerate.php (15s delay) -- Regenerate message of the day
  6. cgroupRootCheck.php (20s delay) -- Ensure root cgroup is unlimited
  7. systemdServicesGuard.php (25s delay) -- Stop unwanted system daemons

RAID integrity checks

PMSS configures quarterly RAID integrity checks, staggered by hostname hash. This replaces Debian's default monthly check (which runs all servers simultaneously). The hostname hash determines both which months the check runs (one of three groups) and the sleep offset (0-5 hours) for start time spread.

Configuration

Config directory

/etc/seedbox/config/ contains all PMSS configuration and templates:

Template files

Template Generates
template.rtorrent.rc rTorrent configuration with placeholder tokens for memory, ports, directories
template.lighttpd Per-user lighttpd config including PHP FastCGI, ruTorrent, rclone, qBittorrent, Deluge proxy, public web directory, rate limiting, streaming
template.nginx-conf Global nginx.conf (keepalive, body size, TLS)
template.nginx-site-default Default nginx site config
template.nginx-site-default-ssl SSL variant for self-signed cert
template.nginx-site-default-ssl-lets-encrypt SSL variant for Let's Encrypt
template.nginx-user Per-user nginx reverse proxy config (proxies to user lighttpd)
template.nginx-user-suspended Suspended user nginx config (serves suspension notice)
template.proftpd ProFTPD config (passive ports, connection limits)
template.sshd_config SSH daemon config
template.deluge.* Deluge auth, core, hostlist, and web configs
template.qbittorrent.conf qBittorrent configuration with randomized external port
template.fireqos FireQOS traffic shaping rules
template.qos QoS configuration
template.openvpn.* OpenVPN server and client configs
template.wireguard.* WireGuard server config and user readme
template.rc.local System rc.local for boot-time tuning
template.systemd.* Systemd service files (boot tuning, services guard)
template.cgroup.user-slice.v1.conf Cgroup v1 user slice template
template.cgroup.user-slice.v2.conf Cgroup v2 user slice template
template.motd Message of the day template
template.logrotate.pmss Log rotation policy for PMSS logs
template.rsyslog-remote.conf Remote syslog forwarding template
template.sources.buster APT sources for Debian 10
template.sources.bullseye APT sources for Debian 11
template.sources.bookworm APT sources for Debian 12
template.sources.trixie APT sources for Debian 13 (experimental)
template.sources.jessie APT sources for Debian 8 (legacy)
template.rutorrent.config ruTorrent configuration (SCGI socket, paths, timeouts)
template.rutorrent.access ruTorrent access control

Runtime configuration files

File Description
network Network interface name and link speeds (e.g., eth0 and 1Gbit)
localnet Local network ranges for separate traffic tracking (one CIDR per line)
cgroup.policy.php Per-host cgroup policy overrides
logging.conf Optional remote syslog forwarding configuration
version Current PMSS version spec and timestamp
version.meta Resolved branch, commit, and log destinations (JSON)
root.cron Canonical root crontab template
rtorrent.resources.json rTorrent resource allocation settings
user.crontab.default Default per-user crontab
user.rtorrent.defaults.dht Default DHT settings for user rTorrent
user.rtorrent.defaults.pex Default PEX settings for user rTorrent
api.* API configuration files (debug, keys, server)

User skeleton

New user accounts are provisioned from /etc/skel/:

  • www/ -- Web GUI (index.php, info.php, stats.php, welcome.php, ruTorrent, file manager, deluge/qbittorrent/rclone pages)
  • www/rutorrent/ -- ruTorrent installation with themes and plugins
  • bin/ -- User scripts (docker-install-wireguard.sh)
  • install-media-stack.sh -- Self-contained media stack installer
  • data/ -- Default download directory
  • watch/ -- Watch directory for automatic torrent loading

Logging

All PMSS cron jobs and operations log to /var/log/pmss/. Log rotation is configured via /etc/logrotate.d/pmss-update (7 daily copies, compressed, copytruncate).

Log files of note:

Log file Source
/var/log/pmss-update.log Phase 2 update (plaintext)
/var/log/pmss-update.jsonl Update events (JSON, one object per step)
/var/log/pmss/update.log Phase 1 bootstrap log
/var/log/pmss/addUser.log User provisioning
/var/log/pmss/users.log Per-user operation log
/var/log/pmss/users.jsonl Per-user events (JSON)
/var/log/pmss/users/<user>.log Individual user log
/var/log/pmss/checkRtorrent.log rTorrent watchdog
/var/log/pmss/checkLighttpdInstances.log Lighttpd watchdog
/var/log/pmss/rootlessDocker.log Docker watchdog
/var/log/pmss/trafficLog.log Traffic data collection
/var/log/pmss/trafficLimits.log Traffic limit enforcement
/var/log/pmss/setupNetwork.log Network/firewall setup
/var/log/pmss/system-stats.log System statistics
/var/log/pmss/process-snapshot.log Process snapshots
/var/log/pmss/quota-daily.log Daily quota snapshots
/var/log/pmss-install.log Initial installation

Optional remote log forwarding via rsyslog is supported. Create /etc/seedbox/config/logging.conf with remote_logging_enabled=1, remote_host, and optionally remote_port and remote_protocol.

Structured logging (JSONL)

The update system writes structured JSON events to /var/log/pmss-update.jsonl, one JSON object per line. Each event includes a timestamp, step name, duration, and outcome. This format is machine-parseable and suitable for log aggregation tools.

Example events from a typical update:

{"ts":"2026-02-14T04:31:12+00:00","step":"apt-flush","duration_ms":42310,"result":"ok"}
{"ts":"2026-02-14T04:31:55+00:00","step":"app:rtorrent","duration_ms":18200,"result":"ok"}
{"ts":"2026-02-14T04:32:14+00:00","step":"user-env-refresh","users":26,"duration_ms":8400,"result":"ok"}

Profiling data can be captured with --profile-output=/path/to/file.json for detailed runtime breakdowns of each update step. This is useful for identifying slow installer modules or package operations that stall.

Per-user operations also produce structured logs. /var/log/pmss/users.jsonl records user provisioning, suspension, termination, and configuration changes with the same one-object-per-line format.

Observability probes

Two CLI tools provide read-only system health checks that can be called from monitoring systems or CI pipelines:

# System test — checks binary versions, config presence, readiness
/scripts/util/systemTest.php
/scripts/util/systemTest.php --json

# Component status — reports per-component health
/scripts/util/componentStatus.php
/scripts/util/componentStatus.php --json

Both support --json output for integration with monitoring dashboards. The probes are read-only and safe to run on production hosts at any time.

The version metadata stored in /etc/seedbox/config/version records the current deployment spec and timestamp (e.g., git/main:2025-01-01@2025-01-02 03:04). The companion version.meta file stores the resolved branch, commit hash, and log destinations in JSON format.

Tracker cleaner

PMSS includes a tracker cleaner that removes known problematic public trackers from non-private torrents to prevent rTorrent stalls on shared hosts. It has been in use since November 2020.

How it works:

  • Only non-private torrents are processed. Torrents with the BitTorrent private flag are skipped entirely.
  • A timestamped backup of every original .torrent is written before any change.
  • Scans at most 500 torrents per run, 2 users per pass.
  • Stops after 30 minutes or 500 modified torrents.
  • Per-user opt-out: create ~/.trackerCleanerDisable.
  • Change log: ~/.trackerCleaner.log and ~/.logs/trackerCleaner.log.
  • Backups: ~/session/backups/YYYY-mm-dd_HHMM/.

The removal list targets specific known-problematic tracker URLs and domains (e.g., tracker.openbittorrent.com, tracker.leechers-paradise.org, tracker.coppersurfer.tk, rarbg.*). The full list is documented in docs/tracker-cleaner.md.

Port management

portManager.php manages HTTP server port assignments for user services:

/scripts/util/portManager.php assign alice lighttpd
/scripts/util/portManager.php view alice lighttpd
/scripts/util/portManager.php release alice lighttpd

Port information is stored under /etc/seedbox/runtime/ports using files named SERVICE-USER. Assignments are guarded by a per-service lock to avoid concurrent collisions.

Hardware transcoding

PMSS documents hardware-accelerated video transcoding for FFmpeg and Jellyfin in docs/hardware-transcoding.md. Intel iGPUs with VAAPI are the best-supported option. Users must be in the render group to access /dev/dri/renderD128.

FFmpeg can be installed entirely in userspace. Static builds can be placed in ~/.bin/ffmpeg. The installer flag --jellyfin-ffmpeg=/home/<user>/.bin/ffmpeg stamps the path in Jellyfin configuration.

System requirements

Operating system

  • Debian 10 (buster) -- stable, in production
  • Debian 11 (bullseye) -- in production
  • Debian 12 (bookworm) -- in production
  • Debian 13 (trixie) -- experimental

Hardware

  • At least 2 GiB free disk space on the root partition for PMSS itself
  • Dedicated /home partition for user data storage (ext4 recommended, with quota support)
  • Network interface (1 Gbps or 10 Gbps)
  • Optional: Intel iGPU for hardware transcoding (VAAPI)

Prerequisites

The installer automatically installs these packages:

  • PHP CLI with xml, zip, mbstring extensions
  • git, rsync, curl, wget, ca-certificates, unzip, vim
  • Build toolchain: build-essential, autoconf, automake, pkg-config, libtool, subversion
  • nano, quota, tzdata

Development

Repository structure

The source code is at https://github.com/MagnaCapax/PMSS.

  • scripts/ -- Main PHP scripts, deployed to /scripts on production hosts
  • etc/ -- Configuration templates and skeleton files, deployed to /etc
  • var/ -- Web content and default pages, deployed to /var
  • docs/ -- Documentation (architecture, update workflow, cron, cgroups, Docker, WireGuard, security, etc.)
  • docs/adr/ -- Architecture Decision Records
  • tests/ -- Test TODO tracker
  • tools/ -- Development tools (phploc, CI scripts, prompts)
  • development/ -- Agent development tooling

Testing

PMSS has two test tiers:

Development tests (php scripts/lib/tests/development/Runner.php):

  • 143 test files covering update spec parsing, cgroup configuration, distro detection, package selection, WireGuard installation, config validation, traffic accounting, resource limit calculation, and many other subsystems
  • Hermetic: no network access, no system mutations, no root required
  • Tests create temporary files and override environment variables to simulate different OS releases, network configurations, and edge cases (e.g., PMSS_OS_RELEASE_PATH to simulate Debian 13 on a Debian 12 development machine, PMSS_WG_DNS_IP to test WireGuard with different DNS servers)
  • Tests can run on any developer machine with PHP CLI — no PMSS installation needed

Production tests (php scripts/lib/tests/production/Runner.php):

  • Read-only probes for live hosts after provisioning
  • Validate package presence, service status, and configuration

Testing scripts

scripts/testing/ provides orchestration scripts:

  • test-php.sh -- PHP syntax lint and development suite
  • test-bash.sh -- Bash syntax check and optional lint/format
  • test-all.sh -- Runs all tests
  • doctrine-lint.sh -- ADR guardrails
  • sharp-edges-lint.sh -- Flags dangerous patterns like raw rm -rf outside wrappers
  • net-edges-lint.sh -- Flags raw network calls outside wrappers
  • phpstan.sh -- Static analysis (opt-in via PMSS_LINT_PHPSTAN=1)

CI

GitHub Actions run PHP lint, development tests, and bash checks on pushes and pull requests. Configuration is in .github/workflows/ci.yml.

Design principles

Several engineering decisions run through the PMSS codebase:

Never break old users. Backwards compatibility is mandatory. When a configuration format changes or a new application replaces an old one, migration code handles the transition automatically during the next update. Users do not need to take any action. If 100% compatibility cannot be ensured, intermediary migration code bridges the gap.

Compile from source when it matters. rTorrent and libtorrent are compiled from source (along with xmlrpc-c) rather than installed from Debian packages. This allows PMSS to ship a consistent, tested version across all Debian releases and apply performance patches. The tradeoff is longer initial install time, but the compiled binaries are cached and only rebuilt when the target version changes.

Idempotent everything. Running update.php twice produces the same result as running it once. The same applies to user provisioning, config generation, and network setup. This makes it safe to re-run the updater after a partial failure without worrying about duplicate state.

Watchdog over daemon. Rather than running a management daemon, PMSS uses cron-based watchdogs that check and repair state on fixed intervals. If rTorrent crashes, checkRtorrent.php restarts it within 2 minutes. If lighttpd dies, checkLighttpdInstances.php catches it within 1 minute. Watchdogs are simpler than daemons: they have no state to corrupt, no sockets to manage, and no crash recovery of their own to worry about.

PHP for management, not for serving. PMSS management scripts are written in PHP because that is what the project started with and it works. PHP CLI has no external dependencies beyond the packages Debian ships. The web-facing components (ruTorrent, file manager, stats pages) also happen to be PHP, but the management layer does not depend on a web server.

Per-user everything. Each user has their own lighttpd, their own torrent client, their own Docker daemon, their own cgroup slice, and their own PHP process. No shared server processes handle multiple users. This isolation model means a misbehaving user cannot affect other users' web interfaces or torrent clients.

Contributing

Contributions are welcome regardless of size. Guidelines from CONTRIBUTING.md:

  1. Discuss significant changes via issue or ADR before implementation
  2. Keep commits small and focused
  3. Update code, tests, docs, and ADRs together
  4. Validate locally before submitting a PR
  5. Never break old users. Backwards compatibility is mandatory. Intermediary migration code is required if 100% compatibility cannot be ensured.

Code style follows Linux kernel conventions adapted for PHP:

  • camelCase for variables, functions, and classes
  • Line width ~120-160 characters
  • Maximum 4 nesting levels per source file
  • Single source files target ~150 lines
  • Descriptive function and variable names with comments on purpose

Contributions may be rewarded with Pulsed Media service credit.

Architecture Decision Records

Significant technical decisions are recorded as ADRs in docs/adr/:

  • ADR-0003: Cgroup v2 adoption and dual-path detection
  • ADR-0004: Shell command guardrails
  • ADR-0005: Trust boundaries for internal tools
  • ADR-0006: Update-step2 user loop and observability
  • ADR-0007: Install bootstrap interactivity and contract
  • ADR-0008: Reject deb822 APT sources migration
  • ADR-0009: Nginx lightweight reverse proxy

CLI probes

Two CLI tools provide read-only system health checks:

  • /scripts/util/systemTest.php -- Checks binary versions, config presence, system readiness. Supports --json output.
  • /scripts/util/componentStatus.php -- Reports component status. Supports --json output.

Documentation

In-repository documentation

The docs/ directory contains:

File Topic
architecture.md System architecture overview, bootstrap flow, key modules
install.md Installer contract and behavior details
update.md Two-phase update workflow, app installer matrix, execution outline
cron.md Cron automation, script catalogue, scheduling tips
cgroup.md Cgroup resource control, per-user utility, policy overrides
userConfig.md Adjusting user settings
addUser.md Adding new users
wireguard.md WireGuard VPN setup, tenant quick start
docker-help.md Rootless Docker basics
linuxserver.io.md Running LinuxServer.io containers on PMSS
install-media-stack.md Native media stack installer documentation
hardware-transcoding.md FFmpeg and Jellyfin hardware transcoding guide
logging.md Central log shipping via rsyslog
tracker-cleaner.md Tracker cleaner documentation
setupNetwork.md Network and firewall configuration
setupLetsEncrypt.md Let's Encrypt certificate setup
portManager.md Port management utility
contracts.md Function and script behavioral contracts
testing.md Testing orchestration and CI
maintenance.md Maintenance checklist
recovery.md Recovery procedures for failed upgrades
dpkg-baseline.md Package baseline capture workflow
security/operational-safety.md Multi-tenant operational safety practices
security/testing.md Security testing documentation

Wiki documentation

Changelog

Current development is tracked on GitHub at https://github.com/MagnaCapax/PMSS/commits/main.

Historical changelog entries (2011-2023) from before the GitHub migration are preserved at PM Software Stack Changelog Archive.

See also

On the blog: