#!/usr/bin/env bash set -Eeuo pipefail ######################################## # USER CONFIG ######################################## NEXTCLOUD_DOMAIN="cloud.example.com" TRUSTED_DOMAINS=("cloud.example.com" "192.168.1.10") NEXTCLOUD_WEBROOT="/usr/share/webapps/nextcloud" NEXTCLOUD_CONFIG_DIR="/etc/webapps/nextcloud" NEXTCLOUD_DATA_DIR="/var/lib/nextcloud/data" DB_NAME="nextcloud" DB_USER="nextcloud" DB_PASS="CHANGE_ME_DB_PASSWORD" NC_ADMIN_USER="admin" NC_ADMIN_PASS="CHANGE_ME_ADMIN_PASSWORD" PHP_MEMORY_LIMIT="1024M" PHP_UPLOAD_LIMIT="16G" PHP_MAX_EXECUTION_TIME="3600" PHP_TIMEZONE="America/New_York" APACHE_RUN_USER="http" APACHE_RUN_GROUP="http" ENABLE_SMB_MOUNT="false" SMB_REMOTE="//server/share" SMB_MOUNTPOINT="/mnt/nextcloud" SMB_CREDENTIALS_FILE="/root/.smbcredentials" SMB_FSTAB_OPTIONS="rw,credentials=${SMB_CREDENTIALS_FILE},uid=http,gid=http,iocharset=utf8,file_mode=0770,dir_mode=0770,noserverino,nounix,_netdev,x-systemd.automount" ######################################## # INTERNALS ######################################## PHP_ETC_DIR="/etc/php-legacy" PHP_INI="${PHP_ETC_DIR}/php.ini" PHP_CONF_D="${PHP_ETC_DIR}/conf.d" PHP_FPM_POOL_CONF="/etc/php-legacy/php-fpm.d/www.conf" HTTPD_CONF="/etc/httpd/conf/httpd.conf" HTTPD_NEXTCLOUD_CONF="/etc/httpd/conf/extra/nextcloud.conf" HTTPD_WELLKNOWN_CONF="/etc/httpd/conf/extra/nextcloud-wellknown.conf" VALKEY_CONF="/etc/valkey/valkey.conf" log() { echo echo "==== $* ====" } die() { echo "ERROR: $*" >&2 exit 1 } require_root() { [[ "${EUID}" -eq 0 ]] || die "Run this script as root." } backup_file() { local f="$1" [[ -f "$f" ]] && cp -an "$f" "${f}.bak.$(date +%F-%H%M%S)" || true } replace_or_append_ini() { local key="$1" local value="$2" local file="$3" if grep -Eq "^[;[:space:]]*${key}[[:space:]]*=" "$file"; then sed -ri "s|^[;[:space:]]*${key}[[:space:]]*=.*|${key} = ${value}|g" "$file" else echo "${key} = ${value}" >> "$file" fi } ensure_line() { local line="$1" local file="$2" grep -Fqx "$line" "$file" || echo "$line" >> "$file" } write_ini() { local file="$1" shift cat > "$file" </dev/null 2>&1; then break fi sleep 1 done mariadb-admin ping >/dev/null 2>&1 || die "MariaDB did not come up." mariadb <> "${PHP_FPM_POOL_CONF}" fi if grep -Eq '^[;[:space:]]*listen.owner\s*=' "${PHP_FPM_POOL_CONF}"; then sed -ri 's|^[;[:space:]]*listen.owner\s*=.*|listen.owner = http|g' "${PHP_FPM_POOL_CONF}" else echo "listen.owner = http" >> "${PHP_FPM_POOL_CONF}" fi if grep -Eq '^[;[:space:]]*listen.group\s*=' "${PHP_FPM_POOL_CONF}"; then sed -ri 's|^[;[:space:]]*listen.group\s*=.*|listen.group = http|g' "${PHP_FPM_POOL_CONF}" else echo "listen.group = http" >> "${PHP_FPM_POOL_CONF}" fi if grep -Eq '^[;[:space:]]*listen.mode\s*=' "${PHP_FPM_POOL_CONF}"; then sed -ri 's|^[;[:space:]]*listen.mode\s*=.*|listen.mode = 0660|g' "${PHP_FPM_POOL_CONF}" else echo "listen.mode = 0660" >> "${PHP_FPM_POOL_CONF}" fi systemctl enable --now php-fpm-legacy systemctl restart php-fpm-legacy log "Configuring Valkey (TCP localhost only)" backup_file "${VALKEY_CONF}" if grep -Eq '^[#[:space:]]*bind ' "${VALKEY_CONF}"; then sed -ri 's|^[#[:space:]]*bind .*|bind 127.0.0.1 ::1|g' "${VALKEY_CONF}" else echo "bind 127.0.0.1 ::1" >> "${VALKEY_CONF}" fi if grep -Eq '^[#[:space:]]*port ' "${VALKEY_CONF}"; then sed -ri 's|^[#[:space:]]*port .*|port 6379|g' "${VALKEY_CONF}" else echo "port 6379" >> "${VALKEY_CONF}" fi if grep -Eq '^[#[:space:]]*protected-mode ' "${VALKEY_CONF}"; then sed -ri 's|^[#[:space:]]*protected-mode .*|protected-mode yes|g' "${VALKEY_CONF}" else echo "protected-mode yes" >> "${VALKEY_CONF}" fi sed -ri 's|^[#[:space:]]*unixsocket .*|# unixsocket disabled by install script|g' "${VALKEY_CONF}" || true sed -ri 's|^[#[:space:]]*unixsocketperm .*|# unixsocketperm disabled by install script|g' "${VALKEY_CONF}" || true if grep -Eq '^[#[:space:]]*supervised ' "${VALKEY_CONF}"; then sed -ri 's|^[#[:space:]]*supervised .*|supervised systemd|g' "${VALKEY_CONF}" else echo "supervised systemd" >> "${VALKEY_CONF}" fi systemctl daemon-reload systemctl enable --now valkey if ! systemctl is-active --quiet valkey; then journalctl -u valkey.service -n 50 --no-pager || true die "valkey.service failed to start" fi log "Configuring Apache" backup_file "${HTTPD_CONF}" sed -ri 's|^#(LoadModule mpm_event_module modules/mod_mpm_event.so)|\1|g' "${HTTPD_CONF}" sed -ri 's|^#(LoadModule rewrite_module modules/mod_rewrite.so)|\1|g' "${HTTPD_CONF}" sed -ri 's|^#(LoadModule headers_module modules/mod_headers.so)|\1|g' "${HTTPD_CONF}" sed -ri 's|^#(LoadModule env_module modules/mod_env.so)|\1|g' "${HTTPD_CONF}" sed -ri 's|^#(LoadModule dir_module modules/mod_dir.so)|\1|g' "${HTTPD_CONF}" sed -ri 's|^#(LoadModule mime_module modules/mod_mime.so)|\1|g' "${HTTPD_CONF}" sed -ri 's|^#(LoadModule alias_module modules/mod_alias.so)|\1|g' "${HTTPD_CONF}" sed -ri 's|^#(LoadModule setenvif_module modules/mod_setenvif.so)|\1|g' "${HTTPD_CONF}" sed -ri 's|^#(LoadModule proxy_module modules/mod_proxy.so)|\1|g' "${HTTPD_CONF}" sed -ri 's|^#(LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so)|\1|g' "${HTTPD_CONF}" if grep -Eq '^[#[:space:]]*ServerName ' "${HTTPD_CONF}"; then sed -ri "s|^[#[:space:]]*ServerName .*|ServerName ${NEXTCLOUD_DOMAIN}:80|g" "${HTTPD_CONF}" else echo "ServerName ${NEXTCLOUD_DOMAIN}:80" >> "${HTTPD_CONF}" fi cat > "${HTTPD_WELLKNOWN_CONF}" <<'EOF' Alias /.well-known/carddav /nextcloud/remote.php/dav/ Alias /.well-known/caldav /nextcloud/remote.php/dav/ EOF cat > "${HTTPD_NEXTCLOUD_CONF}" < Options FollowSymLinks MultiViews AllowOverride All Require all granted Dav off SetEnv HOME ${NEXTCLOUD_CONFIG_DIR} SetEnv HTTP_HOME ${NEXTCLOUD_CONFIG_DIR} SetHandler "proxy:unix:/run/php-fpm-legacy/php-fpm.sock|fcgi://localhost/" 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-Permitted-Cross-Domain-Policies "none" Header always set X-Robots-Tag "noindex, nofollow" Header always set X-XSS-Protection "1; mode=block" Require all denied Require all denied EOF ensure_line "Include conf/extra/nextcloud-wellknown.conf" "${HTTPD_CONF}" ensure_line "Include conf/extra/nextcloud.conf" "${HTTPD_CONF}" apachectl configtest || die "Apache config test failed." systemctl enable --now httpd log "Preparing Nextcloud config and permissions" mkdir -p "${NEXTCLOUD_CONFIG_DIR}/config" mkdir -p "${NEXTCLOUD_CONFIG_DIR}/apps" mkdir -p "${NEXTCLOUD_CONFIG_DIR}/data" chown -R "${APACHE_RUN_USER}:${APACHE_RUN_GROUP}" "${NEXTCLOUD_CONFIG_DIR}" chmod 0750 "${NEXTCLOUD_CONFIG_DIR}" chmod 0750 "${NEXTCLOUD_CONFIG_DIR}/config" chown -R "${APACHE_RUN_USER}:${APACHE_RUN_GROUP}" /etc/webapps/nextcloud chmod 0750 /etc/webapps/nextcloud chmod 0640 /etc/webapps/nextcloud/config/config.php || true chown -R "${APACHE_RUN_USER}:${APACHE_RUN_GROUP}" "${NEXTCLOUD_DATA_DIR}" find "${NEXTCLOUD_DATA_DIR}" -type d -exec chmod 0750 {} \; find "${NEXTCLOUD_DATA_DIR}" -type f -exec chmod 0640 {} \; 2>/dev/null || true log "Optional SMB mount setup" if [[ "${ENABLE_SMB_MOUNT}" == "true" ]]; then mkdir -p "${SMB_MOUNTPOINT}" if ! grep -Fq "${SMB_MOUNTPOINT} " /etc/fstab; then echo "${SMB_REMOTE} ${SMB_MOUNTPOINT} cifs ${SMB_FSTAB_OPTIONS} 0 0" >> /etc/fstab fi mount -a chown "${APACHE_RUN_USER}:${APACHE_RUN_GROUP}" "${SMB_MOUNTPOINT}" || true fi log "Verifying PHP modules before install" php-legacy -m | grep -qi '^gd$' || die "PHP GD module is still not loaded." php-legacy -m | grep -qi '^mysqli$' || die "PHP mysqli module is still not loaded." php-legacy -m | grep -qi '^PDO$' || die "PHP PDO core is still not loaded." php-legacy -m | grep -qi '^pdo_mysql$' || die "PHP pdo_mysql module is still not loaded." php-legacy -m | grep -qi '^intl$' || die "PHP intl module is still not loaded." php-legacy -m | grep -qi '^redis$' || die "PHP redis module is still not loaded." php-legacy -m | grep -qi '^apcu$' || die "PHP apcu module is still not loaded." systemctl restart php-fpm-legacy systemctl restart httpd log "Installing Nextcloud non-interactively" if [[ ! -f "${NEXTCLOUD_CONFIG_DIR}/config/config.php" ]] || grep -q "CAN_INSTALL" "${NEXTCLOUD_CONFIG_DIR}/config/config.php" 2>/dev/null; then sudo -u "${APACHE_RUN_USER}" /usr/bin/php-legacy /usr/bin/occ maintenance:install \ --database "mysql" \ --database-name "${DB_NAME}" \ --database-user "${DB_USER}" \ --database-pass "${DB_PASS}" \ --admin-user "${NC_ADMIN_USER}" \ --admin-pass "${NC_ADMIN_PASS}" \ --data-dir "${NEXTCLOUD_DATA_DIR}" fi log "Applying Nextcloud config" for i in "${!TRUSTED_DOMAINS[@]}"; do occ config:system:set trusted_domains "${i}" --value="${TRUSTED_DOMAINS[$i]}" done occ config:system:set overwrite.cli.url --value="http://${NEXTCLOUD_DOMAIN}/nextcloud" occ config:system:set htaccess.RewriteBase --value="/nextcloud" occ maintenance:update:htaccess occ config:system:set memcache.local --value='\OC\Memcache\APCu' occ config:system:set memcache.locking --value='\OC\Memcache\Redis' occ config:system:set filelocking.enabled --type=boolean --value=true occ config:system:set redis host --value="127.0.0.1" occ config:system:set redis port --type=integer --value=6379 occ config:system:set default_phone_region --value="US" || true occ config:system:set maintenance_window_start --type=integer --value=1 || true log "Enabling cron" systemctl enable --now nextcloud-cron.service || true systemctl enable --now nextcloud-cron.timer || true log "Final service restarts" systemctl restart mariadb systemctl restart valkey systemctl restart php-fpm-legacy systemctl restart httpd log "Post-install checks" apachectl configtest systemctl --no-pager --full status mariadb | sed -n '1,12p' || true systemctl --no-pager --full status valkey | sed -n '1,12p' || true systemctl --no-pager --full status php-fpm-legacy | sed -n '1,12p' || true systemctl --no-pager --full status httpd | sed -n '1,12p' || true echo echo "==============================================" echo "Nextcloud install completed." echo "URL: http://${NEXTCLOUD_DOMAIN}/nextcloud" echo "Admin user: ${NC_ADMIN_USER}" echo "Data dir: ${NEXTCLOUD_DATA_DIR}" echo "Web root: ${NEXTCLOUD_WEBROOT}" echo "Config dir: ${NEXTCLOUD_CONFIG_DIR}" echo "Valkey: 127.0.0.1:6379" echo "==============================================" echo echo "IMPORTANT:" echo "1) This sets up HTTP only." echo "2) Add TLS separately or put it behind a reverse proxy." echo "3) Edit the variables at the top before running."