| #!/usr/bin/env bash |
| |
| |
| |
| set -euo pipefail |
|
|
| |
| INSTALL_DIR="/opt/ldv" |
| APP_DIR="$INSTALL_DIR/ldv-backend" |
| DATA_DIR="$INSTALL_DIR/data" |
| BACKUP_DIR="/var/backups/ldv" |
| LOG_DIR="/var/log/ldv" |
| APP_USER="www-data" |
| REPO_URL="https://github.com/vadhh/cra.git" |
| ENV_FILE="$APP_DIR/.env" |
| SERVICE_NAME="ldv" |
| NGINX_SITE="ldv" |
| |
|
|
| RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' |
| info() { echo -e "${GREEN}[ldv]${NC} $*"; } |
| warn() { echo -e "${YELLOW}[warn]${NC} $*"; } |
| die() { echo -e "${RED}[error]${NC} $*"; exit 1; } |
|
|
| [[ $EUID -eq 0 ]] || die "Run with sudo: sudo bash deploy/setup.sh" |
|
|
| |
| info "Installing system dependencies..." |
| apt-get update -qq |
| apt-get install -y -qq python3 python3-pip libmagic1 rsync git nginx |
|
|
| |
| if [[ -d "$INSTALL_DIR/.git" ]]; then |
| info "Repo already cloned — pulling latest..." |
| git -C "$INSTALL_DIR" pull |
| else |
| info "Cloning repo to $INSTALL_DIR..." |
| git clone "$REPO_URL" "$INSTALL_DIR" |
| fi |
|
|
| |
| info "Installing Python dependencies..." |
| pip3 install -q -r "$APP_DIR/requirements.txt" |
|
|
| |
| info "Creating directories..." |
| mkdir -p "$DATA_DIR" "$BACKUP_DIR" "$LOG_DIR" |
| chown -R "$APP_USER:$APP_USER" "$DATA_DIR" "$BACKUP_DIR" "$LOG_DIR" "$INSTALL_DIR" |
|
|
| |
| if [[ -f "$ENV_FILE" ]]; then |
| warn ".env already exists at $ENV_FILE — skipping generation." |
| warn "Edit it manually if you need to change values." |
| else |
| info "Generating .env..." |
|
|
| SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))") |
| ENC_KEY=$(cd "$APP_DIR" && python3 manage.py gen-key) |
|
|
| read -rp "Admin email: " ADMIN_EMAIL |
| read -rsp "Admin password: " ADMIN_PASSWORD; echo |
| read -rp "Server domain or IP (e.g. 192.168.1.10 or app.example.com): " SERVER_HOST |
| read -rp "Backup rsync target (leave blank to skip, e.g. user@host:/backups/ldv): " BACKUP_REMOTE |
|
|
| cat > "$ENV_FILE" <<EOF |
| # Auto-generated by setup.sh — $(date -u +%Y-%m-%dT%H:%M:%SZ) |
| |
| LDV_SECRET_KEY=$SECRET_KEY |
| LDV_ENCRYPTION_KEY=$ENC_KEY |
| LDV_PRODUCTION=1 |
| LDV_COOKIE_SECURE=0 |
| LDV_CORS_ORIGINS=http://$SERVER_HOST |
| LDV_DB_PATH=$DATA_DIR/sydeco.db |
| LDV_RETENTION_DAYS=30 |
| LDV_BACKUP_DIR=$BACKUP_DIR |
| LDV_BACKUP_REMOTE=$BACKUP_REMOTE |
| LDV_BACKUP_KEEP_DAYS=30 |
| LDV_MAX_UPLOAD_MB=10 |
| LDV_DOWNLOAD_LINK_TTL=900 |
| LDV_REMOTE_TRANSLATION=0 |
| LDV_DEBUG=0 |
| LDV_ADMIN_EMAIL=$ADMIN_EMAIL |
| LDV_ADMIN_PASSWORD=$ADMIN_PASSWORD |
| EOF |
| chmod 600 "$ENV_FILE" |
| chown "$APP_USER:$APP_USER" "$ENV_FILE" |
| info ".env written to $ENV_FILE" |
| fi |
|
|
| |
| info "Initialising database..." |
| (cd "$APP_DIR" && set -a && source "$ENV_FILE" && set +a && \ |
| python3 -c "import database; database.init_db()" && \ |
| python3 manage.py seed-admin 2>/dev/null || true) |
| info "Database ready." |
|
|
| |
| info "Installing systemd service..." |
| GUNICORN_BIN=$(which gunicorn || echo "/usr/local/bin/gunicorn") |
|
|
| cat > /etc/systemd/system/${SERVICE_NAME}.service <<EOF |
| [Unit] |
| Description=LDV Contract Analyzer |
| After=network.target |
| |
| [Service] |
| User=$APP_USER |
| WorkingDirectory=$APP_DIR |
| EnvironmentFile=$ENV_FILE |
| ExecStart=$GUNICORN_BIN -w 2 -b 127.0.0.1:5000 --timeout 120 \\ |
| --access-logfile $LOG_DIR/access.log \\ |
| --error-logfile $LOG_DIR/error.log \\ |
| app:app |
| Restart=always |
| RestartSec=5 |
| |
| [Install] |
| WantedBy=multi-user.target |
| EOF |
|
|
| systemctl daemon-reload |
| systemctl enable "$SERVICE_NAME" |
| systemctl restart "$SERVICE_NAME" |
| info "Service $SERVICE_NAME started." |
|
|
| |
| info "Configuring nginx..." |
|
|
| |
| SERVER_HOST=${SERVER_HOST:-$(grep LDV_CORS_ORIGINS "$ENV_FILE" | cut -d= -f2 | sed 's|https\?://||')} |
|
|
| cat > /etc/nginx/sites-available/$NGINX_SITE <<EOF |
| server { |
| listen 80; |
| server_name $SERVER_HOST; |
| |
| client_max_body_size 15M; |
| |
| location / { |
| proxy_pass http://127.0.0.1:5000; |
| proxy_set_header Host \$host; |
| proxy_set_header X-Real-IP \$remote_addr; |
| proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; |
| proxy_read_timeout 120s; |
| } |
| } |
| EOF |
|
|
| ln -sf /etc/nginx/sites-available/$NGINX_SITE /etc/nginx/sites-enabled/$NGINX_SITE |
| rm -f /etc/nginx/sites-enabled/default |
| nginx -t |
| systemctl reload nginx |
| info "nginx configured." |
|
|
| |
| info "Installing backup cron..." |
| sed "s|/opt/ldv|$INSTALL_DIR|g" "$INSTALL_DIR/deploy/ldv-backup.cron" \ |
| > /etc/cron.d/ldv-backup |
| chmod 644 /etc/cron.d/ldv-backup |
| info "Backup cron installed (runs nightly at 02:00)." |
|
|
| |
| info "Waiting for server to start..." |
| sleep 3 |
| HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5000/health) |
| if [[ "$HTTP_STATUS" == "200" ]]; then |
| info "Health check passed (HTTP 200)." |
| curl -s http://127.0.0.1:5000/health | python3 -m json.tool |
| else |
| warn "Health check returned HTTP $HTTP_STATUS — check logs:" |
| warn " journalctl -u $SERVICE_NAME -n 50" |
| fi |
|
|
| |
| echo "" |
| echo -e "${GREEN}Setup complete.${NC}" |
| echo "" |
| echo " App running at : http://$SERVER_HOST" |
| echo " Logs : $LOG_DIR/" |
| echo " DB : $DATA_DIR/sydeco.db" |
| echo " Backups : $BACKUP_DIR/" |
| echo " Service : systemctl status $SERVICE_NAME" |
| echo "" |
| echo "Next steps:" |
| echo " 1. Follow docs/staging-runbook.md sections 7-12 to validate" |
| echo " 2. Set LDV_COOKIE_SECURE=1 in $ENV_FILE once you add HTTPS" |
| echo " (add HTTPS with: sudo apt install certbot python3-certbot-nginx)" |
| echo " (then run: sudo certbot --nginx -d $SERVER_HOST)" |
|
|