Always clone this repo (the full app) and always use SQLite, per updated deployment requirements. Drops now-dead mysql/fresh-scaffold branches accordingly.
397 lines
13 KiB
Bash
397 lines
13 KiB
Bash
#!/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 SUPER_ADMIN_PASSWORD='change-me' \
|
|
# bash bootstrap.sh
|
|
#
|
|
# This script always clones https://git.innovator.bh/ghassan/webappAIO.git
|
|
# (the full RBAC/2FA/MQTT/Users-module app) and always uses SQLite — neither
|
|
# is prompted for.
|
|
#
|
|
# Supported env vars (all optional, will be prompted for if unset and a tty is available):
|
|
# APP_NAME Project directory name (default: laravel-app)
|
|
# 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.
|
|
if declare -p "$var_name" &>/dev/null; then
|
|
return
|
|
fi
|
|
|
|
if [ ! -t 0 ]; then
|
|
if ! { [ -e /dev/tty ] && exec 0</dev/tty 2>/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"
|
|
|
|
# Fixed, not prompted for: this script always deploys the full webappAIO app
|
|
# against SQLite.
|
|
GIT_REPO="https://git.innovator.bh/ghassan/webappAIO.git"
|
|
DB_TYPE="sqlite"
|
|
|
|
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
|
|
|
|
log "Installing PHP SQLite extension"
|
|
apt-get install -y php-sqlite3
|
|
|
|
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
|
|
|
|
mkdir -p /var/www
|
|
cd /var/www
|
|
|
|
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"
|
|
|
|
cd "/var/www/$APP_NAME/backend"
|
|
|
|
log "Creating SQLite database file"
|
|
mkdir -p database
|
|
touch database/database.sqlite
|
|
|
|
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" <<EOF
|
|
<VirtualHost *:80>
|
|
ServerAdmin webmaster@localhost
|
|
DocumentRoot /var/www/$APP_NAME/backend/public
|
|
|
|
<Directory /var/www/$APP_NAME/backend>
|
|
AllowOverride All
|
|
Require all granted
|
|
</Directory>
|
|
|
|
ErrorLog \${APACHE_LOG_DIR}/$APP_NAME-error.log
|
|
CustomLog \${APACHE_LOG_DIR}/$APP_NAME-access.log combined
|
|
</VirtualHost>
|
|
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=sqlite/" .env
|
|
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
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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 <<EOF
|
|
|
|
MQTT_HOST=127.0.0.1
|
|
MQTT_PORT=1883
|
|
MQTT_WS_URL=ws://${IP_ADDR:-localhost}:9001
|
|
MQTT_TLS=false
|
|
MQTT_AUTH_USERNAME=laravel_publisher
|
|
MQTT_AUTH_PASSWORD=$MQTT_PUBLISHER_PASSWORD
|
|
MQTT_ROLE_ADMIN_USERNAME=role_admin
|
|
MQTT_ROLE_ADMIN_PASSWORD=$MQTT_ROLE_ADMIN_PASSWORD
|
|
MQTT_ROLE_MANAGER_USERNAME=role_manager
|
|
MQTT_ROLE_MANAGER_PASSWORD=$MQTT_ROLE_MANAGER_PASSWORD
|
|
MQTT_ROLE_USER_USERNAME=role_user
|
|
MQTT_ROLE_USER_PASSWORD=$MQTT_ROLE_USER_PASSWORD
|
|
EOF
|
|
fi
|
|
|
|
log "Starting Mosquitto"
|
|
docker compose up -d mosquitto
|
|
|
|
cd "/var/www/$APP_NAME/backend"
|
|
|
|
log "Configuring Super Admin seed credentials"
|
|
cat >> .env <<EOF
|
|
|
|
SUPER_ADMIN_EMAIL=$SUPER_ADMIN_EMAIL
|
|
SUPER_ADMIN_USERNAME=$SUPER_ADMIN_USERNAME
|
|
SUPER_ADMIN_PASSWORD=$SUPER_ADMIN_PASSWORD
|
|
|
|
FRONTEND_URL=http://${IP_ADDR:-localhost}:5173
|
|
SANCTUM_STATEFUL_DOMAINS=localhost:5173,localhost,127.0.0.1
|
|
EOF
|
|
|
|
log "Generating application key, running migrations and seeders"
|
|
php artisan key:generate --force
|
|
php artisan migrate --force
|
|
if [ -f database/seeders/DatabaseSeeder.php ]; then
|
|
php artisan db:seed --force
|
|
fi
|
|
|
|
log "Installing a systemd unit for the queue worker (MQTT event publishing)"
|
|
cat > /etc/systemd/system/"$APP_NAME"-queue.service <<EOF
|
|
[Unit]
|
|
Description=$APP_NAME queue worker
|
|
After=network.target
|
|
|
|
[Service]
|
|
User=www-data
|
|
WorkingDirectory=/var/www/$APP_NAME/backend
|
|
ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3
|
|
Restart=always
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
systemctl daemon-reload
|
|
systemctl enable --now "$APP_NAME"-queue.service
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 6. code-server (VS Code in the browser, over SSH)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [ "$INSTALL_CODE_SERVER" = "yes" ]; then
|
|
log "Installing code-server"
|
|
curl -fsSL https://code-server.dev/install.sh | sh
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 7. Claude Code (npm)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [ "$INSTALL_CLAUDE_CODE" = "yes" ]; then
|
|
log "Installing Claude Code via npm"
|
|
npm install -g @anthropic-ai/claude-code
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 8. Summary
|
|
# ---------------------------------------------------------------------------
|
|
|
|
log "Setup complete"
|
|
cat <<SUMMARY
|
|
|
|
Project: $APP_NAME
|
|
Location: /var/www/$APP_NAME (backend/, frontend/)
|
|
Database: $DB_TYPE
|
|
API URL: http://${IP_ADDR:-<container-ip>}/
|
|
MQTT broker: mqtt://${IP_ADDR:-<container-ip>}:1883, ws://${IP_ADDR:-<container-ip>}: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
|