#!/usr/bin/env bash # ============================================================================= # PragmaCloud — Instalacijska skripta za lokalni deployment # Podržani sistemi: Ubuntu 20.04+, Debian 11+ # Upotreba: curl -sSL https://install.pragma.ba | bash # ili: bash install.sh # ============================================================================= set -euo pipefail # ── Konstante ───────────────────────────────────────────────────────────────── PRAGMA_DIR="/opt/pragma" DATA_DIR="${PRAGMA_DIR}/tabele" APP_DIR="${PRAGMA_DIR}/app" IMAGE="registry.pragma.ba/pragmacloud:latest" REGISTRY_HOST="registry.pragma.ba" # Javni download s produkcijskog servera (GitHub repo je privatan — raw URL vraca 404, # isti bug vec ranije ispravljen za install.ps1 — vidi CLAUDE.md/memoriju) COMPOSE_URL="https://app.pragma.ba/docker-compose.client.yml" MIN_DOCKER_VERSION="24.0" MIN_RAM_MB=512 # ── Pre-fill parametri (opciono, npr. iz SuperAdmin "Installer" dugmeta — # vidi /superadmin/lokalne-licence) — ako su postavljeni, preskace se prompt ───── PRAGMA_LICENSE_KEY_PREFILL="${PRAGMA_LICENSE_KEY:-}" PRAGMA_REG_USER_PREFILL="${PRAGMA_REG_USER:-}" PRAGMA_REG_PASS_PREFILL="${PRAGMA_REG_PASS:-}" # ── Boje ────────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m' info() { echo -e "${BLUE}→${NC} $*"; } success() { echo -e "${GREEN}✓${NC} $*"; } warn() { echo -e "${YELLOW}!${NC} $*"; } error() { echo -e "${RED}✗${NC} $*" >&2; exit 1; } header() { echo -e "\n${BOLD}${BLUE}$*${NC}"; echo "──────────────────────────────────────────"; } # ── Provjera root ───────────────────────────────────────────────────────────── if [[ $EUID -ne 0 ]]; then error "Pokrenite skriptu kao root: sudo bash install.sh" fi header "PragmaCloud Instalacija" echo "Ova skripta instalira PragmaCloud lokalno koristeći Docker." echo "" # ── Provjera OS-a ───────────────────────────────────────────────────────────── if ! grep -qiE "ubuntu|debian" /etc/os-release 2>/dev/null; then warn "Ovaj OS nije testiran. Nastaviti? [y/N]" read -r confirm; [[ "$confirm" =~ ^[yY] ]] || exit 0 fi # ── Provjera RAM-a ──────────────────────────────────────────────────────────── TOTAL_RAM_MB=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo) if [[ $TOTAL_RAM_MB -lt $MIN_RAM_MB ]]; then warn "Sistem ima samo ${TOTAL_RAM_MB}MB RAM. Preporučeno je minimum ${MIN_RAM_MB}MB." fi # ── Provjera i instalacija Dockera ─────────────────────────────────────────── header "1. Docker" if command -v docker &>/dev/null; then DOCKER_VER=$(docker --version | grep -oP '\d+\.\d+' | head -1) success "Docker ${DOCKER_VER} je već instaliran." else info "Docker nije pronađen. Instalacija..." apt-get update -q apt-get install -y -q ca-certificates curl gnupg lsb-release install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg \ | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/$(. /etc/os-release && echo "$ID") \ $(lsb_release -cs) stable" \ > /etc/apt/sources.list.d/docker.list apt-get update -q apt-get install -y -q docker-ce docker-ce-cli containerd.io docker-compose-plugin systemctl enable --now docker success "Docker instaliran." fi if ! command -v docker compose &>/dev/null && ! command -v docker-compose &>/dev/null; then error "docker compose plugin nije dostupan. Instalirajte ga ručno." fi # ── Kreiranje direktorija ───────────────────────────────────────────────────── header "2. Direktoriji" mkdir -p "$DATA_DIR" "$APP_DIR" chmod 755 "$DATA_DIR" "$APP_DIR" success "Kreiran: ${DATA_DIR}" success "Kreiran: ${APP_DIR}" # ── Konfiguracija ───────────────────────────────────────────────────────────── header "3. Konfiguracija" ENV_FILE="${APP_DIR}/.env" # Ako .env već postoji, pitaj korisnika if [[ -f "$ENV_FILE" ]]; then warn ".env fajl već postoji u ${APP_DIR}." echo -n " Prepisati ga? [y/N] " read -r overwrite if [[ ! "$overwrite" =~ ^[yY] ]]; then info "Konfiguracija zadržana." else rm "$ENV_FILE" fi fi if [[ ! -f "$ENV_FILE" ]]; then # Port echo "" echo -n " Port na koji PragmaCloud treba slušati [8080]: " read -r APP_PORT APP_PORT="${APP_PORT:-8080}" # Licencni ključ — OBAVEZAN. Nepostojeći/prazan ključ server odmah odbija # (Valid=false → Expired → IsBlocked), nema više "TRIAL" fallback-a. if [[ -n "$PRAGMA_LICENSE_KEY_PREFILL" ]]; then LICENSE_KEY="$PRAGMA_LICENSE_KEY_PREFILL" info "Licencni ključ proslijeđen." else LICENSE_KEY="" while [[ -z "$LICENSE_KEY" ]]; do echo "" echo -n " Unesite licencni ključ (obavezno; dobijate od Pragma tima): " read -r LICENSE_KEY if [[ -z "$LICENSE_KEY" ]]; then warn "Licencni ključ je obavezan — bez validnog ključa aplikacija će biti blokirana." fi done fi # Firma naziv (opcionalno, za display) echo "" echo -n " Naziv firme (opcionalno, za prikaz): " read -r FIRMA_NAZIV FIRMA_NAZIV="${FIRMA_NAZIV:-}" # Backend baze podataka — SQLite (zadano), MySQL ili PostgreSQL (lokalni kontejner # na ovoj mašini, isti obrazac kao produkcijski docker-compose.yml). Wizard "Create # new database" (SuperAdmin → New workspace) radi identično produkciji jer # MySqlTenantProvisioner/PostgresTenantProvisioner očekuju servis imenovan tačno # "mysql"/"postgres" — vidi docker-compose. echo "" echo " Backend baze podataka:" echo " [1] SQLite (zadano) — fajl-bazirano, najjednostavnije" echo " [2] MySQL (lokalni kontejner) — pokreće dodatni MySQL servis na ovoj mašini" echo " [3] PostgreSQL (lokalni kontejner) — pokreće dodatni PostgreSQL servis na ovoj mašini" echo -n " Izbor [1]: " read -r BACKEND_CHOICE BACKEND_CHOICE="${BACKEND_CHOICE:-1}" BACKEND_ENV_BLOCK="" if [[ "$BACKEND_CHOICE" == "2" ]]; then info "MySQL backend izabran — generišem kredencijale i pripremam init skriptu..." if command -v openssl &>/dev/null; then MYSQL_ROOT_PW=$(openssl rand -hex 24) MYSQL_APP_PW=$(openssl rand -hex 24) else MYSQL_ROOT_PW=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n') MYSQL_APP_PW=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n') fi mkdir -p "${APP_DIR}/mysql-init" curl -sSL "https://app.pragma.ba/mysql-init/01-grant-pragma-app.sql" \ -o "${APP_DIR}/mysql-init/01-grant-pragma-app.sql" \ || warn "Preuzimanje mysql-init skripte nije uspjelo — MySQL grant će se morati ručno postaviti." BACKEND_ENV_BLOCK=" # MySQL backend — lokalni kontejner (aktivira 'mysql' servis u docker-compose.yml) COMPOSE_PROFILES=mysql MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PW} MYSQL_APP_PASSWORD=${MYSQL_APP_PW}" success "MySQL kredencijali generisani." elif [[ "$BACKEND_CHOICE" == "3" ]]; then info "PostgreSQL backend izabran — generišem kredencijale i pripremam init skriptu..." if command -v openssl &>/dev/null; then POSTGRES_ROOT_PW=$(openssl rand -hex 24) POSTGRES_APP_PW=$(openssl rand -hex 24) else POSTGRES_ROOT_PW=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n') POSTGRES_APP_PW=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n') fi mkdir -p "${APP_DIR}/pg-init" curl -sSL "https://app.pragma.ba/pg-init/01-grant-pragma-app.sh" \ -o "${APP_DIR}/pg-init/01-grant-pragma-app.sh" \ || warn "Preuzimanje pg-init skripte nije uspjelo — PostgreSQL grant će se morati ručno postaviti." chmod +x "${APP_DIR}/pg-init/01-grant-pragma-app.sh" 2>/dev/null || true BACKEND_ENV_BLOCK=" # PostgreSQL backend — lokalni kontejner (aktivira 'postgres' servis u docker-compose.yml) COMPOSE_PROFILES=postgres POSTGRES_ROOT_PASSWORD=${POSTGRES_ROOT_PW} POSTGRES_APP_PASSWORD=${POSTGRES_APP_PW}" success "PostgreSQL kredencijali generisani." else info "SQLite backend (zadano)." fi # Watchtower HTTP API token — omogućava dugme "Ažuriraj sada" u aplikaciji if command -v openssl &>/dev/null; then WATCHTOWER_TOKEN=$(openssl rand -hex 24) else WATCHTOWER_TOKEN=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n') fi cat > "$ENV_FILE" << EOF # PragmaCloud — lokalna konfiguracija # Kreirano: $(date '+%Y-%m-%d %H:%M') # Port na hostu APP_PORT=${APP_PORT} # Folder s SQLite bazama SQLITE_DIR=${DATA_DIR} # Licencni ključ PRAGMA_LICENSE_KEY=${LICENSE_KEY} # Firma (opcionalno) # PRAGMA_FIRMA_NAZIV=${FIRMA_NAZIV} # Watchtower HTTP API token — omogućava dugme "Ažuriraj sada" u aplikaciji # (admin korisnik može odmah pokrenuti pull+restart, bez čekanja na poll interval) WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_TOKEN} ${BACKEND_ENV_BLOCK} # Email notifikacije (opcionalno) # Email__SmtpHost=smtp.gmail.com # Email__SmtpPort=587 # Email__Username= # Email__Password= # Email__From= EOF success ".env kreiran: ${ENV_FILE}" success "Watchtower HTTP API token generisan (omogućava 'Ažuriraj sada' u aplikaciji)." fi # ── Preuzimanje docker-compose.client.yml ───────────────────────────────────── header "4. Docker Compose" COMPOSE_FILE="${APP_DIR}/docker-compose.yml" if [[ ! -f "$COMPOSE_FILE" ]]; then info "Preuzimanje docker-compose.client.yml..." if command -v curl &>/dev/null; then curl -sSL "$COMPOSE_URL" -o "$COMPOSE_FILE" \ || { warn "Preuzimanje nije uspjelo. Kopiram lokalni fajl..."; cp "$(dirname "$0")/docker-compose.client.yml" "$COMPOSE_FILE" 2>/dev/null || error "docker-compose.client.yml nije pronađen."; } else cp "$(dirname "$0")/docker-compose.client.yml" "$COMPOSE_FILE" 2>/dev/null \ || error "Nije moguće pronaći docker-compose.client.yml. Potrebna internet konekcija ili lokalni fajl." fi success "docker-compose.yml kreiran: ${COMPOSE_FILE}" else success "docker-compose.yml već postoji." fi # ── Prijava na privatni Docker registry ────────────────────────────────────── header "5. Docker registry (registry.pragma.ba)" info "Prijava na privatni Pragma registry..." if [[ -n "$PRAGMA_REG_PASS_PREFILL" ]]; then REG_USER="${PRAGMA_REG_USER_PREFILL:-pragma}" REG_PASS="$PRAGMA_REG_PASS_PREFILL" info "Registry kredencijali proslijeđeni (korisnik: $REG_USER)." else echo "" echo " Unesite credentials za Pragma privatni Docker registry." echo " Dobijate ih od Pragma tima zajedno s licencnim ključem." echo "" echo -n " Korisničko ime [pragma]: " read -r REG_USER REG_USER="${REG_USER:-pragma}" echo -n " Lozinka: " read -rs REG_PASS; echo "" fi if [[ -n "$REG_PASS" ]]; then echo "$REG_PASS" | docker login "$REGISTRY_HOST" -u "$REG_USER" --password-stdin \ || { warn "Privatni registry nije dostupan. Pokušaj s GHCR..."; IMAGE="ghcr.io/slavkomatanovic/pragmacloud:latest"; } success "Prijavljen na $REGISTRY_HOST" # Watchtower NE koristi host-ov ~/.docker/config.json direktno (na sistemima s # credential-helperom — pass/secretservice na Linuxu, credsStore na Windows/Mac — # taj fajl samo referencira helper, ne nosi stvarnu lozinku, pa bi Watchtower dobio # "no basic auth credentials" pri pokušaju pull-a). Umjesto toga, generišemo poseban, # samostalan config.json (plain base64 auth) namijenjen isključivo za Watchtower mount. mkdir -p "${APP_DIR}/watchtower-auth" REG_AUTH_B64=$(printf '%s:%s' "$REG_USER" "$REG_PASS" | base64 -w0 2>/dev/null || printf '%s:%s' "$REG_USER" "$REG_PASS" | base64) cat > "${APP_DIR}/watchtower-auth/config.json" << EOF { "auths": { "${REGISTRY_HOST}": { "auth": "${REG_AUTH_B64}" } } } EOF chmod 600 "${APP_DIR}/watchtower-auth/config.json" success "Watchtower registry credentials sačuvani (${APP_DIR}/watchtower-auth/config.json)." else warn "Lozinka nije unesena. Koristim GHCR (javni)." IMAGE="ghcr.io/slavkomatanovic/pragmacloud:latest" mkdir -p "${APP_DIR}/watchtower-auth" echo '{"auths":{}}' > "${APP_DIR}/watchtower-auth/config.json" fi # ── Pokretanje ──────────────────────────────────────────────────────────────── header "6. Pokretanje aplikacije" cd "$APP_DIR" info "Povlačenje Docker image-a (može potrajati pri prvom pokretanju)..." docker compose pull info "Pokretanje kontejnera..." docker compose up -d # Čekanje na healthcheck info "Čekanje da se aplikacija pokrene..." TIMEOUT=60; ELAPSED=0 while [[ $ELAPSED -lt $TIMEOUT ]]; do STATUS=$(docker inspect --format='{{.State.Health.Status}}' pragmacloud 2>/dev/null || echo "starting") if [[ "$STATUS" == "healthy" ]]; then break fi sleep 3; ELAPSED=$((ELAPSED + 3)) echo -n "." done echo "" if [[ "$STATUS" == "healthy" ]]; then success "PragmaCloud je pokrenut i zdrav!" else warn "Aplikacija možda još nije sprema (status: ${STATUS}). Provjerite logove: docker logs pragmacloud" fi # ── Kreiranje systemd servisa (opcionalno) ───────────────────────────────────── header "7. Autostart pri pokretanju servera" SYSTEMD_FILE="/etc/systemd/system/pragmacloud.service" if [[ ! -f "$SYSTEMD_FILE" ]]; then cat > "$SYSTEMD_FILE" << EOF [Unit] Description=PragmaCloud ERP After=docker.service Requires=docker.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=${APP_DIR} ExecStart=/usr/bin/docker compose up -d ExecStop=/usr/bin/docker compose down TimeoutStartSec=120 [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable pragmacloud.service success "Systemd servis kreiran — PragmaCloud će se automatski pokrenuti pri restartu servera." else info "Systemd servis već postoji." fi # ── Finalni izvještaj ───────────────────────────────────────────────────────── PORT=$(grep "^APP_PORT" "$ENV_FILE" | cut -d= -f2 | tr -d ' ' || echo "8080") LOCAL_IP=$(hostname -I | awk '{print $1}') echo "" echo -e "${GREEN}${BOLD}════════════════════════════════════════════════${NC}" echo -e "${GREEN}${BOLD} PragmaCloud je uspješno instaliran! 🎉${NC}" echo -e "${GREEN}${BOLD}════════════════════════════════════════════════${NC}" echo "" echo -e " ${BOLD}Lokalni pristup:${NC} http://localhost:${PORT}" echo -e " ${BOLD}Mrežni pristup:${NC} http://${LOCAL_IP}:${PORT}" echo "" echo -e " ${BOLD}Baze podataka:${NC} ${DATA_DIR}" echo -e " ${BOLD}Konfiguracija:${NC} ${ENV_FILE}" echo "" if grep -q "^MYSQL_ROOT_PASSWORD=" "$ENV_FILE" 2>/dev/null; then echo -e " ${BOLD}MySQL backend:${NC} aktivan (lokalni kontejner, kredencijali u ${ENV_FILE})" echo -e " Nove firme na MySQL-u: SuperAdmin → New workspace → 'Create new database'" echo "" fi if grep -q "^POSTGRES_ROOT_PASSWORD=" "$ENV_FILE" 2>/dev/null; then echo -e " ${BOLD}PostgreSQL backend:${NC} aktivan (lokalni kontejner, kredencijali u ${ENV_FILE})" echo -e " Nove firme na PostgreSQL-u: SuperAdmin → New workspace → 'Create new database'" echo "" fi echo -e " ${BOLD}Korisne komande:${NC}" echo -e " docker logs pragmacloud # logovi aplikacije" echo -e " docker compose -f ${APP_DIR}/docker-compose.yml ps # status" echo -e " systemctl restart pragmacloud # restart" echo "" echo -e "${YELLOW} Backup:${NC} Redovno kopirajte ${DATA_DIR} na sigurnu lokaciju!" echo ""