nextcloud-install/arch_install.sh

474 lines
14 KiB
Bash

#!/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" <<EOF
$*
EOF
}
occ() {
sudo -u "${APACHE_RUN_USER}" /usr/bin/php-legacy /usr/bin/occ "$@"
}
########################################
# START
########################################
require_root
log "Validating variables"
[[ "${DB_PASS}" != "CHANGE_ME_DB_PASSWORD" ]] || die "Set DB_PASS at the top of the script."
[[ "${NC_ADMIN_PASS}" != "CHANGE_ME_ADMIN_PASSWORD" ]] || die "Set NC_ADMIN_PASS at the top of the script."
if [[ "${ENABLE_SMB_MOUNT}" == "true" ]]; then
[[ -f "${SMB_CREDENTIALS_FILE}" ]] || die "ENABLE_SMB_MOUNT=true but ${SMB_CREDENTIALS_FILE} does not exist."
fi
log "Installing packages"
pacman -Syu --noconfirm
pacman -S --needed --noconfirm \
apache \
mariadb \
nextcloud \
php-legacy \
php-legacy-fpm \
php-legacy-gd \
php-legacy-apcu \
php-legacy-redis \
php-legacy-intl \
php-legacy-sodium \
valkey \
cifs-utils \
smbclient \
curl \
sudo \
unzip \
bzip2 \
tar
log "Creating base directories"
mkdir -p "${NEXTCLOUD_DATA_DIR}"
mkdir -p /var/lib/nextcloud
mkdir -p /var/log/httpd
mkdir -p /run/httpd
mkdir -p "${PHP_CONF_D}"
chown -R "${APACHE_RUN_USER}:${APACHE_RUN_GROUP}" /var/lib/nextcloud
chmod 0750 /var/lib/nextcloud
chmod 0750 "${NEXTCLOUD_DATA_DIR}"
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
for _ in {1..30}; do
if mariadb-admin ping >/dev/null 2>&1; then
break
fi
sleep 1
done
mariadb-admin ping >/dev/null 2>&1 || die "MariaDB did not come up."
mariadb <<SQL
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\`
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
ALTER USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'localhost';
FLUSH PRIVILEGES;
DELETE FROM mysql.user WHERE User='';
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;
SQL
log "Configuring PHP legacy stack"
backup_file "${PHP_INI}"
backup_file "${PHP_FPM_POOL_CONF}"
replace_or_append_ini "memory_limit" "${PHP_MEMORY_LIMIT}" "${PHP_INI}"
replace_or_append_ini "upload_max_filesize" "${PHP_UPLOAD_LIMIT}" "${PHP_INI}"
replace_or_append_ini "post_max_size" "${PHP_UPLOAD_LIMIT}" "${PHP_INI}"
replace_or_append_ini "max_execution_time" "${PHP_MAX_EXECUTION_TIME}" "${PHP_INI}"
replace_or_append_ini "max_input_time" "${PHP_MAX_EXECUTION_TIME}" "${PHP_INI}"
replace_or_append_ini "output_buffering" "Off" "${PHP_INI}"
replace_or_append_ini "date.timezone" "${PHP_TIMEZONE}" "${PHP_INI}"
replace_or_append_ini "cgi.fix_pathinfo" "0" "${PHP_INI}"
# Explicitly enable extensions needed by Nextcloud.
# Arch php-legacy package layout is module-based, so make sure conf.d loads them.
write_ini "${PHP_CONF_D}/20-nextcloud-core.ini" \
"; Core DB and image modules for Nextcloud
extension=mysqli
extension=pdo_mysql
extension=gd
extension=intl
extension=sodium
"
write_ini "${PHP_CONF_D}/20-nextcloud-cache.ini" \
"; Cache modules for Nextcloud
extension=apcu
extension=redis
apc.enable_cli=1
"
write_ini "${PHP_CONF_D}/10-opcache.ini" \
"; Opcache
zend_extension=opcache
opcache.enable=1
opcache.enable_cli=1
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.memory_consumption=192
opcache.save_comments=1
opcache.revalidate_freq=60
"
sed -ri 's|^user\s*=.*|user = http|g' "${PHP_FPM_POOL_CONF}"
sed -ri 's|^group\s*=.*|group = http|g' "${PHP_FPM_POOL_CONF}"
if grep -Eq '^[;[:space:]]*listen\s*=' "${PHP_FPM_POOL_CONF}"; then
sed -ri 's|^[;[:space:]]*listen\s*=.*|listen = /run/php-fpm-legacy/php-fpm.sock|g' "${PHP_FPM_POOL_CONF}"
else
echo "listen = /run/php-fpm-legacy/php-fpm.sock" >> "${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}" <<EOF
Alias /nextcloud "${NEXTCLOUD_WEBROOT}"
<Directory "${NEXTCLOUD_WEBROOT}">
Options FollowSymLinks MultiViews
AllowOverride All
Require all granted
<IfModule mod_dav.c>
Dav off
</IfModule>
SetEnv HOME ${NEXTCLOUD_CONFIG_DIR}
SetEnv HTTP_HOME ${NEXTCLOUD_CONFIG_DIR}
</Directory>
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php-fpm-legacy/php-fpm.sock|fcgi://localhost/"
</FilesMatch>
<IfModule mod_headers.c>
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"
</IfModule>
<Directory "${NEXTCLOUD_WEBROOT}/config">
Require all denied
</Directory>
<Directory "${NEXTCLOUD_WEBROOT}/data">
Require all denied
</Directory>
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."