#!/usr/bin/env bash # Nextcloud + Apache + PHP 8.3 (php-legacy) + MariaDB + Valkey/Redis-compatible cache on Arch Linux # Production-oriented, reverse-proxy aware # # Run as root: # sudo ./arch_install.sh set -Eeuo pipefail IFS=$'\n\t' # ========================= # EDIT THESE VARIABLES # ========================= DOMAIN="domain.com" NC_DIR="/srv/http/nextcloud" NC_DATA_DIR="/srv/nextcloud-data" DB_NAME="nextcloud" DB_USER="nextcloud" DB_PASS="changeMe" ADMIN_USER="admin" ADMIN_PASS="changeMe" # If you're behind Cloudflare Tunnel or another reverse proxy: TRUST_LOCAL_PROXY="true" REAL_IP_HEADER="CF-Connecting-IP" # use X-Forwarded-For if not Cloudflare # Pin a specific release for production stability NC_VERSION="33.0.0" NC_TARBALL_URL="https://download.nextcloud.com/server/releases/nextcloud-${NC_VERSION}.tar.bz2" NC_SHA512_URL="https://download.nextcloud.com/server/releases/nextcloud-${NC_VERSION}.tar.bz2.sha512" # PHP legacy paths/services on Arch PHP_BIN="/usr/bin/php-legacy" PHP_INI="/etc/php-legacy/php.ini" PHP_CONF_DIR="/etc/php-legacy/conf.d" PHP_FPM_WWW_CONF="/etc/php-legacy/php-fpm.d/www.conf" PHP_FPM_SERVICE="php-fpm-legacy" # Valkey provides redis on current Arch REDIS_SERVICE="valkey" REDIS_CONF="/etc/valkey/valkey.conf" REDIS_SOCK="/run/valkey/valkey.sock" # ========================= # HELPER FUNCTIONS # ========================= log() { printf '\n==== %s ====\n' "$1" } die() { printf '\nERROR: %s\n' "$1" >&2 exit 1 } require_root() { [[ $EUID -eq 0 ]] || die "Run this script as root." } check_vars() { local vars=( DOMAIN NC_DIR NC_DATA_DIR DB_NAME DB_USER DB_PASS ADMIN_USER ADMIN_PASS NC_VERSION NC_TARBALL_URL NC_SHA512_URL ) for v in "${vars[@]}"; do [[ -n "${!v}" ]] || die "Variable $v is empty. Edit the script first." done } backup_file() { local file="$1" [[ -f "$file" ]] || return 0 cp -a "$file" "${file}.bak.$(date +%Y%m%d-%H%M%S)" } set_ini_value() { local key="$1" local value="$2" if grep -Eq "^[;[:space:]]*${key}[[:space:]]*=" "$PHP_INI"; then sed -ri "s|^[;[:space:]]*${key}[[:space:]]*=.*|${key} = ${value}|" "$PHP_INI" else printf "\n%s = %s\n" "$key" "$value" >> "$PHP_INI" fi } set_fpm_value() { local key="$1" local value="$2" if grep -Eq "^[;[:space:]]*${key}[[:space:]]*=" "$PHP_FPM_WWW_CONF"; then sed -ri "s|^[;[:space:]]*${key}[[:space:]]*=.*|${key} = ${value}|" "$PHP_FPM_WWW_CONF" else printf "\n%s = %s\n" "$key" "$value" >> "$PHP_FPM_WWW_CONF" fi } write_conf_file() { local file="$1" shift install -d -m 0755 "$PHP_CONF_DIR" printf '%s\n' "$@" > "$file" } verify_php_module() { local module="$1" "$PHP_BIN" -m 2>/dev/null | grep -qx "$module" } # ========================= # PRECHECKS # ========================= require_root check_vars log "Updating system and installing packages" pacman -Syu --noconfirm pacman -S --needed --noconfirm \ apache mariadb valkey cronie \ php-legacy php-legacy-fpm php-legacy-gd php-legacy-intl php-legacy-sodium \ php-legacy-apcu php-legacy-igbinary php-legacy-redis php-legacy-imagick \ php-legacy-mysqli php-legacy-pdo php-legacy-zip \ curl wget tar bzip2 unzip sudo # ========================= # DIRECTORIES # ========================= log "Creating base directories" install -d -m 0755 /srv/http install -d -m 0750 "${NC_DATA_DIR}" # ========================= # MARIADB # ========================= log "Initializing and configuring MariaDB" if [[ ! -d /var/lib/mysql/mysql ]]; then mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql fi systemctl enable --now mariadb mysql <<'SQL' DELETE FROM mysql.user WHERE User=''; DROP DATABASE IF EXISTS test; DELETE FROM mysql.db WHERE Db='test' OR Db LIKE 'test\_%'; FLUSH PRIVILEGES; SQL mysql <> "$REDIS_CONF" fi if grep -Eq '^[#[:space:]]*port[[:space:]]+' "$REDIS_CONF"; then sed -ri 's|^[#[:space:]]*port[[:space:]]+.*|port 6379|' "$REDIS_CONF" else printf '\nport 6379\n' >> "$REDIS_CONF" fi # Disable socket use to avoid path/group drift between redis/valkey packaging changes sed -i '/^[#[:space:]]*unixsocket[[:space:]]\+/d' "$REDIS_CONF" sed -i '/^[#[:space:]]*unixsocketperm[[:space:]]\+/d' "$REDIS_CONF" systemctl enable --now "${REDIS_SERVICE}" systemctl restart "${REDIS_SERVICE}" # ========================= # APACHE # ========================= log "Configuring Apache" HTTPD_CONF="/etc/httpd/conf/httpd.conf" sed -ri 's|^#(LoadModule proxy_module modules/mod_proxy.so)|\1|' "$HTTPD_CONF" sed -ri 's|^#(LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so)|\1|' "$HTTPD_CONF" sed -ri 's|^#(LoadModule rewrite_module modules/mod_rewrite.so)|\1|' "$HTTPD_CONF" sed -ri 's|^#(LoadModule headers_module modules/mod_headers.so)|\1|' "$HTTPD_CONF" sed -ri 's|^#(LoadModule remoteip_module modules/mod_remoteip.so)|\1|' "$HTTPD_CONF" || true sed -ri 's|^#(LoadModule env_module modules/mod_env.so)|\1|' "$HTTPD_CONF" || true sed -ri 's|^#(LoadModule mime_module modules/mod_mime.so)|\1|' "$HTTPD_CONF" || true sed -ri 's|^#(LoadModule setenvif_module modules/mod_setenvif.so)|\1|' "$HTTPD_CONF" || true if ! grep -Eq '^[[:space:]]*ServerName[[:space:]]+' "$HTTPD_CONF"; then printf "\nServerName %s\n" "$DOMAIN" >> "$HTTPD_CONF" fi install -d -m 0755 /etc/httpd/conf/extra if ! grep -Fq "IncludeOptional conf/extra/*.conf" "$HTTPD_CONF"; then printf "\nIncludeOptional conf/extra/*.conf\n" >> "$HTTPD_CONF" fi cat > /etc/httpd/conf/extra/remoteip-nextcloud.conf < /etc/httpd/conf/extra/nextcloud.conf < ServerName ${DOMAIN} DocumentRoot ${NC_DIR} Require all granted AllowOverride All Options FollowSymLinks MultiViews Dav off Header always set Referrer-Policy "no-referrer" Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "SAMEORIGIN" Header always set X-Robots-Tag "noindex, nofollow" Header always set X-Permitted-Cross-Domain-Policies "none" Header always set X-Download-Options "noopen" Header always set X-XSS-Protection "1; mode=block" LimitRequestBody 0 SetEnv HOME ${NC_DIR} SetEnv HTTP_HOME ${NC_DIR} SetHandler "proxy:unix:/run/php-fpm-legacy/php-fpm.sock|fcgi://localhost/" ErrorLog "/var/log/httpd/nextcloud_error.log" CustomLog "/var/log/httpd/nextcloud_access.log" combined EOF httpd -t systemctl enable --now httpd systemctl reload httpd # ========================= # DOWNLOAD NEXTCLOUD # ========================= log "Downloading official Nextcloud release" TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT cd "$TMPDIR" curl -fsSLo nextcloud.tar.bz2 "${NC_TARBALL_URL}" curl -fsSLo nextcloud.tar.bz2.sha512 "${NC_SHA512_URL}" sha512sum -c nextcloud.tar.bz2.sha512 tar -xjf nextcloud.tar.bz2 log "Deploying Nextcloud" rm -rf "${NC_DIR}" mv nextcloud "${NC_DIR}" chown -R http:http "${NC_DIR}" "${NC_DATA_DIR}" find "${NC_DIR}" -type d -exec chmod 0750 {} \; find "${NC_DIR}" -type f -exec chmod 0640 {} \; chmod 0750 "${NC_DATA_DIR}" install -d -o http -g http -m 0750 "${NC_DIR}/config" install -d -o http -g http -m 0750 "${NC_DIR}/apps" # ========================= # INSTALL NEXTCLOUD # ========================= log "Running Nextcloud installer" sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" maintenance:install \ --database "mysql" \ --database-name "${DB_NAME}" \ --database-user "${DB_USER}" \ --database-pass "${DB_PASS}" \ --admin-user "${ADMIN_USER}" \ --admin-pass "${ADMIN_PASS}" \ --data-dir "${NC_DATA_DIR}" # ========================= # REVERSE PROXY / HTTPS # ========================= log "Applying reverse-proxy and HTTPS settings" sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set overwrite.cli.url --value="https://${DOMAIN}" sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set overwriteprotocol --value="https" if [[ "${TRUST_LOCAL_PROXY}" == "true" ]]; then sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set trusted_proxies 0 --value="127.0.0.1" sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set trusted_proxies 1 --value="::1" if [[ "${REAL_IP_HEADER}" == "CF-Connecting-IP" ]]; then sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set forwarded_for_headers 0 --value="HTTP_CF_CONNECTING_IP" else sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set forwarded_for_headers 0 --value="HTTP_X_FORWARDED_FOR" fi fi sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set trusted_domains 1 --value="${DOMAIN}" # ========================= # CACHE / LOCKING # ========================= log "Configuring APCu and Redis" sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set memcache.local --value='\OC\Memcache\APCu' sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set memcache.locking --value='\OC\Memcache\Redis' sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set redis host --value="127.0.0.1" sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set redis port --type=integer --value=6379 sudo -u http "${PHP_BIN}" "${NC_DIR}/occ" config:system:set redis timeout --type=float --value=1.5 # ========================= # CRON # ========================= log "Configuring cron background jobs" systemctl enable --now cronie cat > /etc/cron.d/nextcloud <