Add PnPaaS application and browser installer
This commit is contained in:
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
APP_SOURCE="$SCRIPT_DIR/app"
|
||||
TARGET="/var/www/html/pnpaas"
|
||||
DB_NAME="pnpaas"
|
||||
DB_USER="pnpaas"
|
||||
DB_HOST="localhost"
|
||||
ADMIN_USER="admin"
|
||||
ADMIN_EMAIL=""
|
||||
APP_URL=""
|
||||
PORTAINER_URL=""
|
||||
DRY_RUN=0
|
||||
|
||||
usage() {
|
||||
printf '%s\n' \
|
||||
'Usage: sudo ./install.sh [options]' \
|
||||
' --target PATH Application target (default: /var/www/html/pnpaas)' \
|
||||
' --db-name NAME MariaDB database (default: pnpaas)' \
|
||||
' --db-user NAME MariaDB application user (default: pnpaas)' \
|
||||
' --db-host HOST MariaDB host (default: localhost)' \
|
||||
' --admin-user NAME Initial administrator username (default: admin)' \
|
||||
' --admin-email EMAIL Initial administrator email' \
|
||||
' --app-url URL Public HTTPS URL, e.g. https://example/pnpaas' \
|
||||
' --portainer-url URL Backend Portainer URL' \
|
||||
' --check Validate prerequisites without changes' \
|
||||
' --help Show this help'
|
||||
}
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--target) TARGET="$2"; shift 2;;
|
||||
--db-name) DB_NAME="$2"; shift 2;;
|
||||
--db-user) DB_USER="$2"; shift 2;;
|
||||
--db-host) DB_HOST="$2"; shift 2;;
|
||||
--admin-user) ADMIN_USER="$2"; shift 2;;
|
||||
--admin-email) ADMIN_EMAIL="$2"; shift 2;;
|
||||
--app-url) APP_URL="$2"; shift 2;;
|
||||
--portainer-url) PORTAINER_URL="$2"; shift 2;;
|
||||
--check) DRY_RUN=1; shift;;
|
||||
--help) usage; exit 0;;
|
||||
*) printf 'Unknown option: %s\n' "$1" >&2; usage >&2; exit 2;;
|
||||
esac
|
||||
done
|
||||
|
||||
fail() { printf 'ERROR: %s\n' "$1" >&2; exit 1; }
|
||||
require_command() { command -v "$1" >/dev/null 2>&1 || fail "Required command not found: $1"; }
|
||||
sql_escape() { local value="$1"; printf "%s" "${value//\'/\'\'}"; }
|
||||
|
||||
[[ "$(id -u)" -eq 0 ]] || fail 'Run this installer as root, for example: sudo ./install.sh'
|
||||
[[ -d "$APP_SOURCE" ]] || fail "Application source not found: $APP_SOURCE"
|
||||
require_command php
|
||||
require_command mariadb
|
||||
require_command openssl
|
||||
require_command apache2ctl
|
||||
php -m | grep -qi '^pdo_mysql$' || fail 'PHP extension pdo_mysql is required.'
|
||||
php -m | grep -qi '^curl$' || fail 'PHP extension curl is required.'
|
||||
php -m | grep -qi '^mbstring$' || fail 'PHP extension mbstring is required.'
|
||||
[[ "$DB_NAME" =~ ^[A-Za-z0-9_]{1,64}$ ]] || fail 'Database name must contain only letters, numbers, and underscores.'
|
||||
[[ "$DB_USER" =~ ^[A-Za-z0-9_]{1,32}$ ]] || fail 'Database user must contain only letters, numbers, and underscores.'
|
||||
[[ "$DB_HOST" =~ ^[A-Za-z0-9_.:-]{1,253}$ ]] || fail 'Database host contains invalid characters.'
|
||||
[[ "$ADMIN_USER" =~ ^[A-Za-z0-9_.-]{3,80}$ ]] || fail 'Admin username must contain 3-80 ASCII letters, numbers, dot, dash, or underscore.'
|
||||
[[ -z "$APP_URL" || "$APP_URL" == https://* ]] || fail '--app-url must use HTTPS.'
|
||||
[[ -z "$PORTAINER_URL" || "$PORTAINER_URL" == https://* ]] || fail '--portainer-url must use HTTPS.'
|
||||
|
||||
if [[ -n "$ADMIN_EMAIL" ]]; then
|
||||
php -r 'exit(filter_var($argv[1], FILTER_VALIDATE_EMAIL) ? 0 : 1);' "$ADMIN_EMAIL" || fail 'Invalid administrator email address.'
|
||||
fi
|
||||
|
||||
if ((DRY_RUN)); then
|
||||
printf 'Prerequisite check passed. No files, services, or database objects were changed.\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[[ -n "$ADMIN_EMAIL" ]] || read -r -p 'Administrator email: ' ADMIN_EMAIL
|
||||
php -r 'exit(filter_var($argv[1], FILTER_VALIDATE_EMAIL) ? 0 : 1);' "$ADMIN_EMAIL" || fail 'Invalid administrator email address.'
|
||||
read -r -s -p 'Initial administrator password (empty = generate one): ' ADMIN_PASSWORD; printf '\n'
|
||||
if [[ -z "$ADMIN_PASSWORD" ]]; then
|
||||
ADMIN_PASSWORD="$(openssl rand -hex 24)"
|
||||
GENERATED_ADMIN_PASSWORD=1
|
||||
else
|
||||
GENERATED_ADMIN_PASSWORD=0
|
||||
fi
|
||||
[[ ${#ADMIN_PASSWORD} -ge 12 ]] || fail 'Administrator password must be at least 12 characters.'
|
||||
DB_PASSWORD="$(openssl rand -hex 32)"
|
||||
ADMIN_HASH="$(php -r 'echo password_hash($argv[1], PASSWORD_DEFAULT);' "$ADMIN_PASSWORD")"
|
||||
|
||||
install -d -m 0755 "$TARGET"
|
||||
cp -a "$APP_SOURCE/." "$TARGET/"
|
||||
rm -f "$TARGET/.env"
|
||||
|
||||
SQL_TMP="$(mktemp)"
|
||||
cleanup() { rm -f "$SQL_TMP"; }
|
||||
trap cleanup EXIT
|
||||
{
|
||||
printf 'CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n' "$(sql_escape "$DB_NAME")"
|
||||
printf "CREATE USER IF NOT EXISTS '%s'@'%s' IDENTIFIED BY '%s';\n" "$(sql_escape "$DB_USER")" "$(sql_escape "$DB_HOST")" "$(sql_escape "$DB_PASSWORD")"
|
||||
printf "ALTER USER '%s'@'%s' IDENTIFIED BY '%s';\n" "$(sql_escape "$DB_USER")" "$(sql_escape "$DB_HOST")" "$(sql_escape "$DB_PASSWORD")"
|
||||
printf 'GRANT ALL PRIVILEGES ON `%s`.* TO '\''%s'\''@'\''%s'\'';\n' "$(sql_escape "$DB_NAME")" "$(sql_escape "$DB_USER")" "$(sql_escape "$DB_HOST")"
|
||||
printf 'FLUSH PRIVILEGES;\n'
|
||||
} > "$SQL_TMP"
|
||||
mariadb < "$SQL_TMP"
|
||||
mariadb "$DB_NAME" < "$TARGET/database/schema.sql"
|
||||
for migration in "$TARGET"/database/migrations/*.sql; do
|
||||
mariadb "$DB_NAME" < "$migration"
|
||||
done
|
||||
|
||||
ADMIN_SQL_TMP="$(mktemp)"
|
||||
trap 'rm -f "$SQL_TMP" "$ADMIN_SQL_TMP"' EXIT
|
||||
printf "INSERT INTO users (username, email, password_hash, role, status) VALUES ('%s','%s','%s','admin','active') ON DUPLICATE KEY UPDATE email=VALUES(email), password_hash=VALUES(password_hash), role='admin', status='active';\n" \
|
||||
"$(sql_escape "$ADMIN_USER")" "$(sql_escape "$ADMIN_EMAIL")" "$(sql_escape "$ADMIN_HASH")" > "$ADMIN_SQL_TMP"
|
||||
mariadb "$DB_NAME" < "$ADMIN_SQL_TMP"
|
||||
|
||||
umask 027
|
||||
cat > "$TARGET/.env" <<EOF
|
||||
PNPAAS_ADMIN_USER=$ADMIN_USER
|
||||
PNPAAS_ADMIN_PASSWORD_HASH=$ADMIN_HASH
|
||||
PNPAAS_DB_DSN=mysql:host=$DB_HOST;dbname=$DB_NAME;charset=utf8mb4
|
||||
PNPAAS_DB_USER=$DB_USER
|
||||
PNPAAS_DB_PASSWORD=$DB_PASSWORD
|
||||
PNPAAS_APP_URL=$APP_URL
|
||||
PNPAAS_FORCE_SECURE_COOKIES=1
|
||||
PORTAINER_URL=$PORTAINER_URL
|
||||
EOF
|
||||
chown root:www-data "$TARGET/.env"
|
||||
chmod 640 "$TARGET/.env"
|
||||
chown -R root:root "$TARGET"
|
||||
chown root:www-data "$TARGET/.env"
|
||||
chmod 640 "$TARGET/.env"
|
||||
|
||||
APACHE_CONF="/etc/apache2/conf-available/pnpaas.conf"
|
||||
cat > "$APACHE_CONF" <<EOF
|
||||
<Directory $TARGET>
|
||||
Options -Indexes
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
<FilesMatch "^(?:\\.pnpaas-setup-token\\.sha256|\\.env(?:\\..*)?|.*\\.(?:sql|log|ini|conf|sh|bak|old|orig)|config(?:\\..*)?)$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
</Directory>
|
||||
<DirectoryMatch "$TARGET/(?:includes|database)">
|
||||
Require all denied
|
||||
</DirectoryMatch>
|
||||
EOF
|
||||
if command -v a2enconf >/dev/null 2>&1; then a2enconf pnpaas >/dev/null; fi
|
||||
apache2ctl configtest >/dev/null
|
||||
systemctl reload apache2
|
||||
|
||||
printf '\nPnPaaS installation completed.\nTarget: %s\nDatabase: %s\nAdministrator: %s\n' "$TARGET" "$DB_NAME" "$ADMIN_USER"
|
||||
if ((GENERATED_ADMIN_PASSWORD)); then
|
||||
printf 'Initial administrator password (displayed once): %s\n' "$ADMIN_PASSWORD"
|
||||
fi
|
||||
printf 'Next steps: configure Portainer and legal-document URLs in %s/.env, then configure HTTPS.\n' "$TARGET"
|
||||
Reference in New Issue
Block a user