ProjectSend Install Script

ProjectSend is a self-hosted file sharing application for users or clients that avoids handing control over to a third-party service. It runs on standard web infrastructure, keeps data local, and focuses on managing uploads, access, and downloads in a straightforward way.

The accompanying install script exists for the same reason. Instead of locking the system to a specific PHP release or assuming yesterday’s defaults still apply, it selects a supported PHP version at install time based on current ProjectSend requirements and available packages. The script installs and configures Apache, MariaDB, PHP, firewall rules, and SELinux settings in a deliberate order, with no hidden steps. Credentials are generated securely, services are enabled cleanly, and the script can be re-run without causing collateral damage.

Requirements / Tested Environment

This setup has been tested using the following components:
Operating System: Rocky Linux 8 and 9 (RHEL-compatible)
Web Server: Apache HTTPD with mod_ssl
Database: MariaDB (MySQL-compatible)
PHP: PHP 8.1 or newer (via Remi repository)
SELinux: Enforcing
Firewall: firewalld enabled

The install script dynamically selects a supported PHP version based on current ProjectSend documentation and available packages, avoiding hard-coded dependencies while remaining compatible with RHEL-based systems.

#!/bin/bash
set -euo pipefail

### CONFIG ###
HOSTNAME="inf-files"
WEBROOT="/var/www/html"
DB_NAME="psdb"
DB_USER="psuser"
DB_PASS_FILE="/root/.ps_db_pass"
TIMEZONE="UTC"

### REQUIRE ROOT ###
if [[ $EUID -ne 0 ]]; then
  echo "Run as root"
  exit 1
fi

echo "Starting ProjectSend installation..."

### HOSTNAME & TIMEZONE ###
hostnamectl set-hostname "$HOSTNAME"
timedatectl set-timezone "$TIMEZONE"

### REPOSITORIES ###
dnf -y install epel-release
dnf -y install https://rpms.remirepo.net/enterprise/remi-release-$(rpm -E %rhel).rpm

### SELECT SUPPORTED PHP VERSION ###
PHP_CANDIDATES=("8.2" "8.1" "8.0" "7.4")
PHP_VERSION=""

for v in "${PHP_CANDIDATES[@]}"; do
    if dnf module list php 2>/dev/null | grep -q "remi-${v}"; then
        PHP_VERSION="$v"
        break
    fi
done

if [[ -z "$PHP_VERSION" ]]; then
    echo "No supported PHP module found."
    exit 1
fi

echo "Using PHP ${PHP_VERSION}"

dnf -y module reset php
dnf -y module enable php:remi-${PHP_VERSION}

### PACKAGES ###
dnf -y install \
    httpd \
    mariadb \
    mariadb-server \
    mod_ssl \
    unzip \
    curl \
    openssl \
    php \
    php-cli \
    php-common \
    php-gd \
    php-json \
    php-ldap \
    php-mysqlnd \
    php-zip \
    php-snmp \
    php-mbstring \
    php-xml

### SERVICES ###
systemctl enable --now httpd mariadb firewalld

### FIREWALL ###
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

### DATABASE PASSWORD ###
if [[ ! -f "$DB_PASS_FILE" ]]; then
    DB_PASS=$(openssl rand -base64 24)
    echo "$DB_PASS" > "$DB_PASS_FILE"
    chmod 600 "$DB_PASS_FILE"
else
    DB_PASS=$(cat "$DB_PASS_FILE")
fi

### DATABASE SETUP ###
mysql <<EOF
CREATE DATABASE IF NOT EXISTS ${DB_NAME};
CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';
FLUSH PRIVILEGES;
EOF

### FETCH LATEST PROJECTSEND ###
LATEST_TAG=$(curl -s https://api.github.com/repos/projectsend/projectsend/releases/latest \
    | grep -oP '"tag_name": "\K[^"]+')

if [[ -z "$LATEST_TAG" ]]; then
    echo "Failed to retrieve latest ProjectSend version."
    exit 1
fi

ARCHIVE="/tmp/projectsend-${LATEST_TAG}.zip"

curl -L -o "$ARCHIVE" \
    "https://github.com/projectsend/projectsend/releases/download/${LATEST_TAG}/projectsend-${LATEST_TAG}.zip"

unzip -o "$ARCHIVE" -d "$WEBROOT"

### CONFIG FILE ###
CONFIG="${WEBROOT}/includes/sys.config.php"
SAMPLE="${WEBROOT}/includes/sys.config.sample.php"

if [[ ! -f "$CONFIG" ]]; then
    cp "$SAMPLE" "$CONFIG"

    sed -i \
        -e "s/define('DB_NAME'.*/define('DB_NAME', '${DB_NAME}');/" \
        -e "s/define('DB_USER'.*/define('DB_USER', '${DB_USER}');/" \
        -e "s/define('DB_PASSWORD'.*/define('DB_PASSWORD', '${DB_PASS}');/" \
        "$CONFIG"
fi

### PERMISSIONS ###
chown -R apache:apache "$WEBROOT"
find "$WEBROOT" -type d -exec chmod 755 {} \;
find "$WEBROOT" -type f -exec chmod 644 {} \;

### SELINUX ###
setsebool -P httpd_can_network_connect on
setsebool -P httpd_unified on
restorecon -FR "$WEBROOT"

### VERIFY PHP ###
php -v | grep -E "PHP (7\.4|8\.0|8\.1|8\.2)" >/dev/null || {
    echo "Unsupported PHP version detected."
    exit 1
}

### COMPLETE ###
echo
echo "Installation complete."
echo "URL: http://$(hostname -f)/"
echo "Database: ${DB_NAME}"
echo "DB User: ${DB_USER}"
echo "DB Password stored at: ${DB_PASS_FILE}"
echo "PHP Version: ${PHP_VERSION}"
echo