#!/bin/bash # webappAIO LXC Bootstrap # One-shot setup for a fresh Proxmox LXC: SSH, Laravel API + React frontend, # Mosquitto (Docker), code-server, Claude Code (npm). # # Usage (paste into a fresh, empty LXC as root): # # curl -fsSL https://git.innovator.bh/ghassan/webappAIO/raw/branch/main/bootstrap.sh -o bootstrap.sh # bash bootstrap.sh # # All prompts can be skipped by exporting env vars before running, e.g.: # # APP_NAME=myapp GIT_REPO="" DB_TYPE=sqlite \ # bash bootstrap.sh # # GIT_REPO blank scaffolds a bare Laravel (backend/) + Vite React (frontend/) # pair with the core packages (Sanctum, spatie/laravel-permission, # google2fa-laravel, php-mqtt/laravel-client) installed — the foundation to # build on, not the full RBAC/2FA/Users-module app. Point GIT_REPO at this # repo (https://git.innovator.bh/ghassan/webappAIO.git) to get that app. # # Supported env vars (all optional, will be prompted for if unset and a tty is available): # APP_NAME Project directory name (default: laravel-app) # GIT_REPO Git URL to clone, blank = new project (default: "") # DB_TYPE mysql | sqlite (default: sqlite) # DB_USER MariaDB user (only used if DB_TYPE=mysql) # DB_PASS MariaDB password (only used if DB_TYPE=mysql) # DB_NAME MariaDB database name (only used if DB_TYPE=mysql) # SUPER_ADMIN_EMAIL Seeded Super Admin email (default: superadmin@example.com) # SUPER_ADMIN_USERNAME Seeded Super Admin username (default: superadmin) # SUPER_ADMIN_PASSWORD Seeded Super Admin password (default: random) # INSTALL_CODE_SERVER yes|no (default: yes) # INSTALL_CLAUDE_CODE yes|no (default: yes) # FIX_SSHD yes|no - relax sshd_config for the common LXC issue (default: yes) set -euo pipefail # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- log() { echo -e "\n\033[1;32m==> $*\033[0m"; } warn() { echo -e "\033[1;33m[warn] $*\033[0m"; } # Prompt for a value only if it isn't already set via env var, and only if we # have a real terminal to read from (works even when this script is piped # straight into bash, by reading from /dev/tty instead of stdin). prompt() { local var_name="$1" prompt_text="$2" default_value="${3:-}" silent="${4:-no}" # A var counts as "provided" if it's set at all, even to an intentionally # blank value (e.g. GIT_REPO="" means "scaffold a new project"). if declare -p "$var_name" &>/dev/null; then return fi if [ ! -t 0 ]; then if ! { [ -e /dev/tty ] && exec 0/dev/null; }; then warn "No terminal available to prompt for $var_name, using default: '$default_value'" printf -v "$var_name" '%s' "$default_value" return fi fi local current_value if [ "$silent" = "yes" ]; then read -r -s -p "$prompt_text" current_value echo "" else read -r -p "$prompt_text" current_value fi printf -v "$var_name" '%s' "${current_value:-$default_value}" } if [ "$(id -u)" -ne 0 ]; then echo "This script must be run as root." >&2 exit 1 fi # --------------------------------------------------------------------------- # 0. Gather configuration # --------------------------------------------------------------------------- log "webappAIO LXC Bootstrap" prompt APP_NAME "Project name (directory name) [laravel-app]: " "laravel-app" prompt GIT_REPO "Git repository URL to clone (blank = new project): " "" prompt DB_TYPE "Database type, mysql or sqlite [sqlite]: " "sqlite" if [[ "$DB_TYPE" != "mysql" && "$DB_TYPE" != "sqlite" ]]; then warn "Unrecognized DB_TYPE '$DB_TYPE', defaulting to sqlite." DB_TYPE="sqlite" fi if [ "$DB_TYPE" = "mysql" ]; then prompt DB_USER "MariaDB username [laravel_user]: " "laravel_user" prompt DB_PASS "MariaDB password: " "$(openssl rand -hex 12)" "yes" prompt DB_NAME "MariaDB database name [laravel_db]: " "laravel_db" fi prompt SUPER_ADMIN_EMAIL "Super Admin email [superadmin@example.com]: " "superadmin@example.com" prompt SUPER_ADMIN_USERNAME "Super Admin username [superadmin]: " "superadmin" prompt SUPER_ADMIN_PASSWORD "Super Admin password: " "$(openssl rand -hex 12)" "yes" INSTALL_CODE_SERVER="${INSTALL_CODE_SERVER:-yes}" INSTALL_CLAUDE_CODE="${INSTALL_CLAUDE_CODE:-yes}" FIX_SSHD="${FIX_SSHD:-yes}" IP_ADDR="$(hostname -I 2>/dev/null | awk '{print $1}')" # --------------------------------------------------------------------------- # 1. Base system update + tooling # --------------------------------------------------------------------------- log "Updating system and installing base tools" apt-get update && apt-get upgrade -y apt-get install -y sudo curl git wget unzip # --------------------------------------------------------------------------- # 2. SSH # --------------------------------------------------------------------------- log "Installing and enabling SSH" apt-get install -y openssh-server systemctl enable ssh systemctl start ssh if [ "$FIX_SSHD" = "yes" ]; then if [ -f /etc/ssh/sshd_config.d/turnkey.conf ]; then log "TurnKey Core detected: enabling AllowTcpForwarding" sed -i 's/^AllowTcpForwarding.*/AllowTcpForwarding yes/' /etc/ssh/sshd_config.d/turnkey.conf grep -q '^AllowTcpForwarding' /etc/ssh/sshd_config.d/turnkey.conf || echo "AllowTcpForwarding yes" >> /etc/ssh/sshd_config.d/turnkey.conf else log "Enabling root login over SSH in /etc/ssh/sshd_config" if grep -q '^PermitRootLogin' /etc/ssh/sshd_config; then sed -i 's/^PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config else echo "PermitRootLogin yes" >> /etc/ssh/sshd_config fi fi systemctl restart ssh fi # --------------------------------------------------------------------------- # 3. Docker + Mosquitto (MQTT broker) # --------------------------------------------------------------------------- log "Installing Docker Engine + Compose plugin" if ! command -v docker &>/dev/null; then curl -fsSL https://get.docker.com | sh fi apt-get install -y docker-compose-plugin systemctl enable docker systemctl start docker # --------------------------------------------------------------------------- # 4. Laravel + React stack # --------------------------------------------------------------------------- log "Installing Apache, PHP and common extensions" apt-get install -y apache2 php php-curl php-bcmath php-json php-mbstring php-xml php-tokenizer php-zip php-gd if [ "$DB_TYPE" = "mysql" ]; then log "Installing MariaDB and PHP MySQL extension" apt-get install -y mariadb-server php-mysql else log "Installing PHP SQLite extension" apt-get install -y php-sqlite3 fi log "Installing Composer" curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer log "Installing Node.js LTS" curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - apt-get install -y nodejs if [ "$DB_TYPE" = "mysql" ]; then log "Creating MariaDB database and user" mysql -e "CREATE DATABASE IF NOT EXISTS \`$DB_NAME\`;" mysql -e "CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';" mysql -e "GRANT ALL PRIVILEGES ON \`$DB_NAME\`.* TO '$DB_USER'@'localhost';" mysql -e "FLUSH PRIVILEGES;" fi mkdir -p /var/www cd /var/www if [ -z "$GIT_REPO" ]; then log "Scaffolding new project: $APP_NAME (backend/ + frontend/)" mkdir -p "$APP_NAME" composer create-project --prefer-dist laravel/laravel "$APP_NAME/backend" cd "$APP_NAME/backend" log "Installing core backend packages (Sanctum, RBAC, 2FA, MQTT)" php artisan install:api --without-migration-prompt composer require spatie/laravel-permission pragmarx/google2fa-laravel bacon/bacon-qr-code php-mqtt/laravel-client php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" php artisan vendor:publish --provider="PhpMqtt\Client\MqttClientServiceProvider" php artisan vendor:publish --provider="PragmaRX\Google2FALaravel\ServiceProvider" cd /var/www/"$APP_NAME" npx --yes create-vite@latest frontend -- --template react-ts cd frontend npm install npm install -D tailwindcss @tailwindcss/vite npm install react-router-dom @tanstack/react-query axios mqtt qrcode cd /var/www/"$APP_NAME" else log "Cloning $GIT_REPO into $APP_NAME" git clone "$GIT_REPO" "$APP_NAME" cd "$APP_NAME/backend" composer install cd ../frontend npm install cd /var/www/"$APP_NAME" fi cd "/var/www/$APP_NAME/backend" if [ "$DB_TYPE" = "sqlite" ]; then log "Creating SQLite database file" mkdir -p database touch database/database.sqlite fi log "Setting file permissions" chown -R www-data:www-data "/var/www/$APP_NAME" chmod -R 775 storage bootstrap/cache log "Configuring Apache virtual host" a2enmod rewrite cat > "/etc/apache2/sites-available/$APP_NAME.conf" < ServerAdmin webmaster@localhost DocumentRoot /var/www/$APP_NAME/backend/public AllowOverride All Require all granted ErrorLog \${APACHE_LOG_DIR}/$APP_NAME-error.log CustomLog \${APACHE_LOG_DIR}/$APP_NAME-access.log combined EOF a2dissite 000-default.conf || true a2ensite "$APP_NAME.conf" systemctl reload apache2 systemctl restart apache2 log "Configuring Laravel .env" if [ ! -f .env ] && [ -f .env.example ]; then cp .env.example .env fi if [ -f .env ]; then sed -i "s/^DB_CONNECTION=.*/DB_CONNECTION=$DB_TYPE/" .env if [ "$DB_TYPE" = "mysql" ]; then sed -i "s/^DB_DATABASE=.*/DB_DATABASE=$DB_NAME/" .env sed -i "s/^DB_USERNAME=.*/DB_USERNAME=$DB_USER/" .env sed -i "s/^DB_PASSWORD=.*/DB_PASSWORD=$DB_PASS/" .env else sed -i "s|^DB_DATABASE=.*|DB_DATABASE=$(pwd)/database/database.sqlite|" .env sed -i "s/^DB_USERNAME=.*/DB_USERNAME=/" .env sed -i "s/^DB_PASSWORD=.*/DB_PASSWORD=/" .env fi fi # --------------------------------------------------------------------------- # 5. Mosquitto config + matching backend secrets # --------------------------------------------------------------------------- log "Generating Mosquitto broker credentials" cd "/var/www/$APP_NAME" mkdir -p docker/mosquitto/config if [ ! -f docker/mosquitto/config/mosquitto.conf ]; then cat > docker/mosquitto/config/mosquitto.conf <<'EOF' listener 1883 protocol mqtt listener 9001 protocol websockets allow_anonymous false password_file /mosquitto/config/passwd acl_file /mosquitto/config/acl.conf persistence true persistence_location /mosquitto/data/ log_dest file /mosquitto/log/mosquitto.log log_dest stdout EOF fi if [ ! -f docker/mosquitto/config/acl.conf ]; then cat > docker/mosquitto/config/acl.conf <<'EOF' user laravel_publisher topic readwrite # user role_admin topic read broadcast/system topic read role/admin/alerts topic read module/+/events topic read private/+/notifications user role_manager topic read broadcast/system topic read role/manager/alerts topic read module/+/events topic read private/+/notifications user role_user topic read broadcast/system topic read role/user/alerts topic read private/+/notifications EOF fi if [ ! -f docker-compose.yml ]; then cat > docker-compose.yml <<'EOF' services: mosquitto: image: eclipse-mosquitto:2 ports: - "1883:1883" - "9001:9001" volumes: - ./docker/mosquitto/config:/mosquitto/config:ro - mosquitto-data:/mosquitto/data - mosquitto-log:/mosquitto/log restart: unless-stopped volumes: mosquitto-data: mosquitto-log: EOF fi if [ ! -f docker/mosquitto/config/passwd ]; then MQTT_PUBLISHER_PASSWORD="$(openssl rand -hex 16)" MQTT_ROLE_ADMIN_PASSWORD="$(openssl rand -hex 16)" MQTT_ROLE_MANAGER_PASSWORD="$(openssl rand -hex 16)" MQTT_ROLE_USER_PASSWORD="$(openssl rand -hex 16)" docker run --rm -v "$(pwd)/docker/mosquitto/config:/mosquitto/config" eclipse-mosquitto:2 mosquitto_passwd -b -c /mosquitto/config/passwd laravel_publisher "$MQTT_PUBLISHER_PASSWORD" docker run --rm -v "$(pwd)/docker/mosquitto/config:/mosquitto/config" eclipse-mosquitto:2 mosquitto_passwd -b /mosquitto/config/passwd role_admin "$MQTT_ROLE_ADMIN_PASSWORD" docker run --rm -v "$(pwd)/docker/mosquitto/config:/mosquitto/config" eclipse-mosquitto:2 mosquitto_passwd -b /mosquitto/config/passwd role_manager "$MQTT_ROLE_MANAGER_PASSWORD" docker run --rm -v "$(pwd)/docker/mosquitto/config:/mosquitto/config" eclipse-mosquitto:2 mosquitto_passwd -b /mosquitto/config/passwd role_user "$MQTT_ROLE_USER_PASSWORD" docker run --rm -v "$(pwd)/docker/mosquitto/config:/mosquitto/config" --entrypoint sh eclipse-mosquitto:2 -c "chmod 600 /mosquitto/config/passwd /mosquitto/config/acl.conf && chown mosquitto:mosquitto /mosquitto/config/passwd /mosquitto/config/acl.conf" cat >> backend/.env <> .env < /etc/systemd/system/"$APP_NAME"-queue.service <}/ MQTT broker: mqtt://${IP_ADDR:-}:1883, ws://${IP_ADDR:-}:9001 Super Admin: $SUPER_ADMIN_USERNAME / $SUPER_ADMIN_PASSWORD Frontend dev server: run 'cd /var/www/$APP_NAME/frontend && npm run dev -- --host' then tunnel port 5173. Queue worker: running as systemd unit '$APP_NAME-queue' (needed for MQTT event publishing). $( [ "$INSTALL_CODE_SERVER" = "yes" ] && echo "code-server: run 'code-server /var/www/$APP_NAME' then tunnel port 8080 over SSH" ) $( [ "$INSTALL_CLAUDE_CODE" = "yes" ] && echo "Claude Code: run 'claude' inside /var/www/$APP_NAME to start" ) SUMMARY