#!/bin/bash # # AtlasOS Installation Script for Ubuntu 24.04 # Installs AtlasOS storage controller with infrastructure gap consideration # # Usage: sudo ./installer/install.sh [options] # or: sudo ./install.sh [options] (if run from installer/ directory) # # Note: Run this script from the atlas repository root directory # or ensure REPO_DIR points to the repository root # set -e set -o pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Default values INSTALL_DIR="/opt/atlas" DATA_DIR="/var/lib/atlas" CONFIG_DIR="/etc/atlas" SERVICE_USER="atlas" LOG_DIR="/var/log/atlas" BACKUP_DIR="/var/lib/atlas/backups" HTTP_ADDR=":8080" DB_PATH="/var/lib/atlas/atlas.db" BUILD_BINARIES=true SKIP_DEPS=false REPO_DIR="" FIREWALL_ACTIVE=false OFFLINE_BUNDLE_DIR="" OFFLINE_MODE=false # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in --install-dir) INSTALL_DIR="$2" shift 2 ;; --data-dir) DATA_DIR="$2" shift 2 ;; --skip-deps) SKIP_DEPS=true shift ;; --skip-build) BUILD_BINARIES=false shift ;; --http-addr) HTTP_ADDR="$2" shift 2 ;; --repo-dir) REPO_DIR="$2" shift 2 ;; --offline-bundle) OFFLINE_BUNDLE_DIR="$2" OFFLINE_MODE=true shift 2 ;; -h|--help) echo "AtlasOS Installation Script for Ubuntu 24.04" echo "" echo "Usage: sudo ./install.sh [options]" echo "" echo "Options:" echo " --install-dir DIR Installation directory (default: /opt/atlas)" echo " --data-dir DIR Data directory (default: /var/lib/atlas)" echo " --skip-deps Skip dependency installation" echo " --skip-build Skip building binaries (use existing)" echo " --http-addr ADDR HTTP address (default: :8080)" echo " --repo-dir DIR Repository directory (if not in current dir)" echo " --offline-bundle DIR Use offline bundle directory (for airgap installs)" echo " Example: --offline-bundle ./atlas-bundle" echo " -h, --help Show this help message" echo "" echo "Airgap Installation:" echo " 1. On internet-connected system: sudo ./installer/bundle-downloader.sh" echo " 2. Transfer bundle to airgap system" echo " 3. On airgap: sudo ./installer/install.sh --offline-bundle /path/to/bundle" echo "" echo "See docs/AIRGAP_INSTALLATION.md for detailed instructions" exit 0 ;; *) echo "Unknown option: $1" exit 1 ;; esac done # Check if running as root if [[ $EUID -ne 0 ]]; then echo -e "${RED}Error: This script must be run as root (use sudo)${NC}" exit 1 fi # Get script directory early (for path resolution) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # If script is in installer/ subdirectory, get parent (repo root) if [[ "$(basename "$SCRIPT_DIR")" == "installer" ]]; then REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" else REPO_ROOT="$SCRIPT_DIR" fi # Detect distribution and validate Ubuntu 24.04 detect_distro() { if [[ -f /etc/os-release ]]; then . /etc/os-release DISTRO=$ID VERSION=$VERSION_ID CODENAME=$VERSION_CODENAME else echo -e "${RED}Error: Cannot detect Linux distribution${NC}" exit 1 fi # Validate Ubuntu 24.04 if [[ "$DISTRO" != "ubuntu" ]]; then echo -e "${RED}Error: This installer is specific to Ubuntu${NC}" echo " Detected: $DISTRO $VERSION" echo " Please use the generic installer or install manually" exit 1 fi # Check for Ubuntu 24.04 (Noble Numbat) # SECURITY: Use OR (||) to warn if EITHER version or codename doesn't match # This ensures we catch all incompatible systems, not just when both are wrong if [[ "$VERSION" != "24.04" ]] || [[ "$CODENAME" != "noble" ]]; then echo -e "${YELLOW}Warning: This installer is optimized for Ubuntu 24.04 (Noble Numbat)${NC}" echo " Detected: Ubuntu $VERSION ($CODENAME)" echo " Continuing anyway, but some features may not work correctly" read -p "Continue anyway? (y/n) " -n 1 -r echo "" if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi else echo -e "${GREEN}✓ Detected Ubuntu 24.04 (Noble Numbat)${NC}" fi } # Pre-flight checks for infrastructure gaps preflight_checks() { echo -e "${GREEN}Running pre-flight checks...${NC}" local errors=0 local warnings=0 # Check network connectivity echo -n " Checking network connectivity... " if ping -c 1 -W 2 8.8.8.8 &>/dev/null || ping -c 1 -W 2 1.1.1.1 &>/dev/null; then echo -e "${GREEN}✓${NC}" else echo -e "${YELLOW}⚠ No internet connectivity${NC}" warnings=$((warnings + 1)) fi # Check if apt is working echo -n " Checking package manager... " if apt-get update &>/dev/null; then echo -e "${GREEN}✓${NC}" else echo -e "${RED}✗ APT not working${NC}" errors=$((errors + 1)) fi # Check disk space (need at least 2GB free) echo -n " Checking disk space... " AVAILABLE_SPACE=$(df / | tail -1 | awk '{print $4}') if [[ $AVAILABLE_SPACE -gt 2097152 ]]; then # 2GB in KB echo -e "${GREEN}✓ ($(numfmt --to=iec-i --suffix=B $((AVAILABLE_SPACE * 1024))) available)${NC}" else echo -e "${YELLOW}⚠ Low disk space ($(numfmt --to=iec-i --suffix=B $((AVAILABLE_SPACE * 1024))) available)${NC}" warnings=$((warnings + 1)) fi # Check if systemd is available echo -n " Checking systemd... " if systemctl --version &>/dev/null; then echo -e "${GREEN}✓${NC}" else echo -e "${RED}✗ systemd not available${NC}" errors=$((errors + 1)) fi # Check kernel version (Ubuntu 24.04 should have 6.8+) echo -n " Checking kernel version... " KERNEL_VERSION=$(uname -r | cut -d. -f1,2) KERNEL_MAJOR=$(echo $KERNEL_VERSION | cut -d. -f1) KERNEL_MINOR=$(echo $KERNEL_VERSION | cut -d. -f2) if [[ $KERNEL_MAJOR -gt 6 ]] || [[ $KERNEL_MAJOR -eq 6 && $KERNEL_MINOR -ge 8 ]]; then echo -e "${GREEN}✓ ($(uname -r))${NC}" else echo -e "${YELLOW}⚠ Kernel $(uname -r) may not fully support all features${NC}" warnings=$((warnings + 1)) fi # Check if running in container (may need special handling) echo -n " Checking environment... " if [[ -f /.dockerenv ]] || grep -qa container=lxc /proc/1/environ 2>/dev/null; then echo -e "${YELLOW}⚠ Running in container (some features may be limited)${NC}" warnings=$((warnings + 1)) else echo -e "${GREEN}✓${NC}" fi echo "" if [[ $errors -gt 0 ]]; then echo -e "${RED}Pre-flight checks failed with $errors error(s)${NC}" exit 1 elif [[ $warnings -gt 0 ]]; then echo -e "${YELLOW}Pre-flight checks completed with $warnings warning(s)${NC}" else echo -e "${GREEN}All pre-flight checks passed${NC}" fi echo "" } # Fix common infrastructure gaps fix_infrastructure_gaps() { echo -e "${GREEN}Fixing infrastructure gaps...${NC}" # Ensure universe repository is enabled (required for some packages on Ubuntu) echo -n " Checking universe repository... " if ! grep -q "^deb.*universe" /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null; then echo -e "${YELLOW}⚠ Enabling universe repository...${NC}" add-apt-repository -y universe 2>/dev/null || { echo "deb http://archive.ubuntu.com/ubuntu $(lsb_release -cs) universe" >> /etc/apt/sources.list apt-get update } echo -e "${GREEN}✓ Universe repository enabled${NC}" else echo -e "${GREEN}✓${NC}" fi # Ensure multiverse repository is enabled (for some packages) echo -n " Checking multiverse repository... " if ! grep -q "^deb.*multiverse" /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null; then echo -e "${YELLOW}⚠ Enabling multiverse repository...${NC}" add-apt-repository -y multiverse 2>/dev/null || { echo "deb http://archive.ubuntu.com/ubuntu $(lsb_release -cs) multiverse" >> /etc/apt/sources.list apt-get update } echo -e "${GREEN}✓ Multiverse repository enabled${NC}" else echo -e "${GREEN}✓${NC}" fi # Load ZFS kernel module if not loaded echo -n " Checking ZFS kernel module... " if ! lsmod | grep -q "^zfs"; then echo -e "${YELLOW}⚠ Loading ZFS kernel module...${NC}" modprobe zfs 2>/dev/null || { echo -e "${YELLOW} ZFS module not available (will be installed with zfsutils-linux)${NC}" } fi echo -e "${GREEN}✓${NC}" # Ensure ZFS module loads on boot if ! grep -q "^zfs" /etc/modules-load.d/*.conf 2>/dev/null && ! grep -q "^zfs" /etc/modules 2>/dev/null; then echo "zfs" > /etc/modules-load.d/zfs.conf echo -e "${GREEN} ✓ ZFS module will load on boot${NC}" fi # Check and configure firewall (UFW) echo -n " Checking firewall... " if command -v ufw &>/dev/null; then if ufw status | grep -q "Status: active"; then echo -e "${YELLOW}⚠ UFW is active${NC}" echo " Will configure firewall rules after installation" FIREWALL_ACTIVE=true else echo -e "${GREEN}✓ (inactive)${NC}" FIREWALL_ACTIVE=false fi else echo -e "${GREEN}✓ (not installed)${NC}" FIREWALL_ACTIVE=false fi echo -e "${GREEN}Infrastructure gaps fixed${NC}" echo "" } # Install dependencies from offline bundle (airgap mode) install_from_bundle() { if [[ -z "$OFFLINE_BUNDLE_DIR" ]] || [[ ! -d "$OFFLINE_BUNDLE_DIR" ]]; then echo -e "${RED}Error: Offline bundle directory not found: $OFFLINE_BUNDLE_DIR${NC}" exit 1 fi echo -e "${GREEN}Installing dependencies from offline bundle...${NC}" echo " Bundle directory: $OFFLINE_BUNDLE_DIR" # Check if bundle directory contains .deb files DEB_COUNT=$(find "$OFFLINE_BUNDLE_DIR" -name "*.deb" 2>/dev/null | wc -l) if [[ $DEB_COUNT -eq 0 ]]; then echo -e "${RED}Error: No .deb files found in bundle directory${NC}" echo " Please ensure the bundle directory contains downloaded .deb packages" exit 1 fi echo " Found $DEB_COUNT .deb package(s)" # Install all .deb files from bundle directory echo " Installing packages from bundle..." cd "$OFFLINE_BUNDLE_DIR" # Install packages using dpkg, handling dependencies # First pass: install all packages (may fail due to missing deps) dpkg -i *.deb 2>/dev/null || true # Second pass: fix dependencies (if any packages are already installed, this will work) apt-get install -f -y -qq --no-install-recommends || { echo -e "${YELLOW}Warning: Some dependencies may need manual resolution${NC}" } # Verify critical packages echo " Verifying installations..." MISSING_PACKAGES=() for pkg in zfsutils-linux samba nfs-kernel-server sqlite3 postgresql postgresql-contrib libpq-dev golang-go build-essential; do if ! dpkg -l | grep -q "^ii.*$pkg"; then MISSING_PACKAGES+=("$pkg") fi done if [[ ${#MISSING_PACKAGES[@]} -gt 0 ]]; then echo -e "${YELLOW}Warning: Some packages may be missing: ${MISSING_PACKAGES[*]}${NC}" echo " Attempting to install from bundle..." # Try to find and install missing packages for pkg in "${MISSING_PACKAGES[@]}"; do DEB_FILE=$(find "$OFFLINE_BUNDLE_DIR" -name "${pkg}*.deb" | head -1) if [[ -n "$DEB_FILE" ]]; then dpkg -i "$DEB_FILE" 2>/dev/null || true fi done # Fix dependencies again apt-get install -f -y -qq --no-install-recommends || true fi # Check for targetcli-fb or targetcli if ! command -v targetcli &>/dev/null && ! command -v targetcli-fb &>/dev/null; then TARGETCLI_DEB=$(find "$OFFLINE_BUNDLE_DIR" -name "*targetcli*.deb" | head -1) if [[ -n "$TARGETCLI_DEB" ]]; then dpkg -i "$TARGETCLI_DEB" 2>/dev/null || true apt-get install -f -y -qq --no-install-recommends || true fi fi # Create symlink for targetcli if needed if ! command -v targetcli &>/dev/null && command -v targetcli-fb &>/dev/null; then ln -sf $(which targetcli-fb) /usr/local/bin/targetcli 2>/dev/null || true fi # Verify Go installation (may need to install from bundle or binary) if ! command -v go &>/dev/null; then # Check if Go binary is in bundle if [[ -f "$OFFLINE_BUNDLE_DIR/go.tar.gz" ]]; then echo " Installing Go from bundle..." rm -rf /usr/local/go tar -C /usr/local -xzf "$OFFLINE_BUNDLE_DIR/go.tar.gz" ln -sf /usr/local/go/bin/go /usr/local/bin/go ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt else echo -e "${YELLOW}Warning: Go not found in bundle, checking for golang-go package...${NC}" fi fi echo -e "${GREEN}Offline bundle installation complete${NC}" echo "" } # Install dependencies for Ubuntu 24.04 install_dependencies() { # If offline mode, use bundle installation if [[ "$OFFLINE_MODE" == "true" ]]; then install_from_bundle return fi echo -e "${GREEN}Installing dependencies for Ubuntu 24.04...${NC}" # Update package lists echo " Updating package lists..." apt-get update -qq # Install essential build tools first echo " Installing build essentials..." DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ build-essential \ git \ curl \ wget \ ca-certificates \ software-properties-common \ apt-transport-https # Install ZFS utilities (Ubuntu 24.04 specific) echo " Installing ZFS utilities..." DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ zfsutils-linux \ zfs-zed \ zfs-initramfs || { echo -e "${YELLOW}Warning: ZFS installation may require additional setup${NC}" } # Install storage services echo " Installing storage services..." DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ samba \ samba-common-bin \ nfs-kernel-server \ rpcbind # Install iSCSI target (Ubuntu 24.04 uses targetcli-fb) echo " Installing iSCSI target..." if apt-cache show targetcli-fb &>/dev/null; then DEBIAN_FRONTEND=noninteractive apt-get install -y -qq targetcli-fb # Create symlink for compatibility if ! command -v targetcli &>/dev/null && command -v targetcli-fb &>/dev/null; then ln -sf $(which targetcli-fb) /usr/local/bin/targetcli fi else DEBIAN_FRONTEND=noninteractive apt-get install -y -qq targetcli || { echo -e "${YELLOW}Warning: targetcli not available, iSCSI features may be limited${NC}" } fi # Install databases (SQLite for compatibility, PostgreSQL as default) echo " Installing database packages..." DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ sqlite3 \ libsqlite3-dev \ postgresql \ postgresql-contrib \ libpq-dev # Install Go compiler (Ubuntu 24.04 has Go 1.22+) echo " Installing Go compiler..." if ! command -v go &>/dev/null; then DEBIAN_FRONTEND=noninteractive apt-get install -y -qq golang-go || { echo -e "${YELLOW}Warning: Go not in repositories, installing from official source...${NC}" # Fallback: install Go from official source GO_VERSION="1.22.0" wget -q https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz -O /tmp/go.tar.gz rm -rf /usr/local/go tar -C /usr/local -xzf /tmp/go.tar.gz ln -sf /usr/local/go/bin/go /usr/local/bin/go ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt rm /tmp/go.tar.gz } fi # Verify Go installation if command -v go &>/dev/null; then GO_VERSION=$(go version | awk '{print $3}') echo -e "${GREEN} ✓ Go $GO_VERSION installed${NC}" else echo -e "${RED}Error: Go installation failed${NC}" exit 1 fi # Install additional utilities echo " Installing additional utilities..." DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ openssl \ net-tools \ iproute2 echo -e "${GREEN}Dependencies installed successfully${NC}" echo "" } # Create system user create_user() { echo -e "${GREEN}Creating system user...${NC}" if ! id "$SERVICE_USER" &>/dev/null; then useradd -r -s /bin/false -d "$DATA_DIR" "$SERVICE_USER" echo -e "${GREEN}User $SERVICE_USER created${NC}" else echo -e "${YELLOW}User $SERVICE_USER already exists${NC}" fi # Add user to disk group for block device access (required for ZFS) if getent group disk > /dev/null 2>&1; then usermod -a -G disk "$SERVICE_USER" echo -e "${GREEN}Added $SERVICE_USER to disk group${NC}" fi # Create sudoers configuration for ZFS commands echo -e "${GREEN}Configuring sudo for ZFS operations...${NC}" cat > /etc/sudoers.d/atlas-zfs < ./cmd/atlas-api/main.go <<'MAINGO' package main import ( "context" "crypto/tls" "log" "net/http" "os" "os/signal" "time" "gitea.avt.data-center.id/othman.suseno/atlas/internal/httpapp" tlscfg "gitea.avt.data-center.id/othman.suseno/atlas/internal/tls" ) func main() { addr := env("ATLAS_HTTP_ADDR", ":8080") // Support both ATLAS_DB_PATH (SQLite) and ATLAS_DB_CONN (PostgreSQL connection string) dbConn := env("ATLAS_DB_CONN", "") if dbConn == "" { dbConn = env("ATLAS_DB_PATH", "data/atlas.db") } app, err := httpapp.New(httpapp.Config{ Addr: addr, TemplatesDir: "web/templates", StaticDir: "web/static", DatabaseConn: dbConn, }) if err != nil { log.Fatalf("init app: %v", err) } tlsConfig := tlscfg.LoadConfig() if err := tlsConfig.Validate(); err != nil { log.Fatalf("TLS configuration error: %v", err) } var tlsServerConfig interface{} if tlsConfig.Enabled { var err error tlsServerConfig, err = tlsConfig.BuildTLSConfig() if err != nil { log.Fatalf("TLS configuration error: %v", err) } log.Printf("[atlas-api] TLS enabled (cert: %s, key: %s)", tlsConfig.CertFile, tlsConfig.KeyFile) } srv := &http.Server{ Addr: addr, Handler: app.Router(), ReadHeaderTimeout: 5 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, } if tlsServerConfig != nil { if cfg, ok := tlsServerConfig.(*tls.Config); ok { srv.TLSConfig = cfg } } go func() { log.Printf("[atlas-api] listening on %s", addr) var err error if tlsConfig.Enabled { err = srv.ListenAndServeTLS("", "") } else { err = srv.ListenAndServe() } if err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %v", err) } }() stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, os.Kill) <-stop log.Printf("[atlas-api] shutdown requested") app.StopScheduler() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Printf("[atlas-api] shutdown error: %v", err) } else { log.Printf("[atlas-api] shutdown complete") } } func env(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def } MAINGO # Create/update atlas-tui/main.go if [[ ! -f "./cmd/atlas-tui/main.go" ]]; then echo " Creating cmd/atlas-tui/main.go..." else echo " Updating cmd/atlas-tui/main.go..." fi cat > ./cmd/atlas-tui/main.go <<'MAINGO' package main import ( "fmt" "os" "os/signal" "syscall" "gitea.avt.data-center.id/othman.suseno/atlas/internal/tui" ) const ( defaultAPIURL = "http://localhost:8080" ) func main() { apiURL := os.Getenv("ATLAS_API_URL") if apiURL == "" { apiURL = defaultAPIURL } client := tui.NewAPIClient(apiURL) app := tui.NewApp(client) sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) go func() { <-sigChan app.Cleanup() os.Exit(0) }() if err := app.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } } MAINGO echo -e "${GREEN}cmd/ directory structure ready${NC}" # Verify cmd files exist now if [[ ! -f "./cmd/atlas-api/main.go" ]]; then echo -e "${RED}Error: Failed to create cmd/atlas-api/main.go${NC}" exit 1 fi if [[ ! -f "./cmd/atlas-tui/main.go" ]]; then echo -e "${RED}Error: Failed to create cmd/atlas-tui/main.go${NC}" exit 1 fi # Build binaries echo "Building atlas-api..." if ! go build -o "$INSTALL_DIR/bin/atlas-api" ./cmd/atlas-api; then echo -e "${RED}Error: Failed to build atlas-api${NC}" exit 1 fi echo "Building atlas-tui..." if ! go build -o "$INSTALL_DIR/bin/atlas-tui" ./cmd/atlas-tui; then echo -e "${RED}Error: Failed to build atlas-tui${NC}" exit 1 fi # Set permissions chown root:root "$INSTALL_DIR/bin/atlas-api" chown root:root "$INSTALL_DIR/bin/atlas-tui" chmod 755 "$INSTALL_DIR/bin/atlas-api" chmod 755 "$INSTALL_DIR/bin/atlas-tui" # Create symlinks in /usr/local/bin for global access echo -e "${GREEN}Creating symlinks for global access...${NC}" ln -sf "$INSTALL_DIR/bin/atlas-api" /usr/local/bin/atlas-api ln -sf "$INSTALL_DIR/bin/atlas-tui" /usr/local/bin/atlas-tui chmod 755 /usr/local/bin/atlas-api chmod 755 /usr/local/bin/atlas-tui echo -e "${GREEN}Binaries built and installed successfully${NC}" echo " Binaries available at: $INSTALL_DIR/bin/" echo " Global commands: atlas-api, atlas-tui" } # Copy web files (templates and static assets) copy_web_files() { echo -e "${GREEN}Copying web files...${NC}" # Use BUILD_DIR from build_binaries if available, otherwise detect it local source_dir="" # Function to check if directory is valid repo root is_repo_root() { local dir="$1" [[ -f "$dir/go.mod" ]] && [[ -d "$dir/internal" ]] } # Try REPO_DIR first if [[ -n "$REPO_DIR" ]] && is_repo_root "$REPO_DIR"; then source_dir="$(cd "$REPO_DIR" && pwd)" # Try REPO_ROOT (if script is in installer/ subdirectory) elif [[ -n "$REPO_ROOT" ]] && is_repo_root "$REPO_ROOT"; then source_dir="$REPO_ROOT" # Try current directory elif is_repo_root "."; then source_dir="$(pwd)" # Try script directory elif is_repo_root "$SCRIPT_DIR"; then source_dir="$SCRIPT_DIR" # Try parent of script directory elif is_repo_root "$SCRIPT_DIR/.."; then source_dir="$(cd "$SCRIPT_DIR/.." && pwd)" fi if [[ -z "$source_dir" ]]; then echo -e "${YELLOW}Warning: Cannot find repository root, skipping web files copy${NC}" echo " Templates and static files may not be available" return fi # Check if web/templates exists if [[ ! -d "$source_dir/web/templates" ]]; then echo -e "${YELLOW}Warning: web/templates directory not found in $source_dir${NC}" echo " Templates may not be available" else # Create web directories in install directory mkdir -p "$INSTALL_DIR/web/templates" mkdir -p "$INSTALL_DIR/web/static" # Copy templates echo " Copying templates from $source_dir/web/templates..." cp -r "$source_dir/web/templates"/* "$INSTALL_DIR/web/templates/" 2>/dev/null || { echo -e "${YELLOW}Warning: Failed to copy some template files${NC}" } # Copy static files if they exist if [[ -d "$source_dir/web/static" ]]; then echo " Copying static files from $source_dir/web/static..." cp -r "$source_dir/web/static"/* "$INSTALL_DIR/web/static/" 2>/dev/null || { echo -e "${YELLOW}Warning: Failed to copy some static files${NC}" } fi # Set ownership chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR/web" chmod -R 755 "$INSTALL_DIR/web" echo -e "${GREEN}Web files copied successfully${NC}" echo " Templates: $INSTALL_DIR/web/templates" echo " Static: $INSTALL_DIR/web/static" fi } # Create systemd service create_systemd_service() { echo -e "${GREEN}Creating systemd service...${NC}" cat > /etc/systemd/system/atlas-api.service < "$CONFIG_DIR/atlas.conf" < /dev/null; then JWT_SECRET=$(openssl rand -hex 32) echo "ATLAS_JWT_SECRET=$JWT_SECRET" >> "$CONFIG_DIR/atlas.conf" echo -e "${GREEN}JWT secret generated${NC}" else echo -e "${YELLOW}Warning: openssl not found. Please set ATLAS_JWT_SECRET manually${NC}" fi } # Configure firewall (UFW) for Ubuntu 24.04 configure_firewall() { if [[ "$FIREWALL_ACTIVE" != "true" ]]; then return fi echo -e "${GREEN}Configuring firewall rules...${NC}" # Extract port from HTTP_ADDR (default :8080) PORT=$(echo "$HTTP_ADDR" | sed 's/.*://' || echo "8080") if [[ -z "$PORT" ]] || [[ "$PORT" == "$HTTP_ADDR" ]]; then PORT="8080" fi # Allow HTTP port echo " Allowing port $PORT for AtlasOS API..." ufw allow "$PORT/tcp" comment "AtlasOS API" 2>/dev/null || true # Allow SMB/CIFS ports echo " Allowing SMB/CIFS ports..." ufw allow 445/tcp comment "SMB/CIFS" 2>/dev/null || true ufw allow 139/tcp comment "NetBIOS" 2>/dev/null || true # Allow NFS ports echo " Allowing NFS ports..." ufw allow 2049/tcp comment "NFS" 2>/dev/null || true ufw allow 2049/udp comment "NFS" 2>/dev/null || true ufw allow 111/tcp comment "RPC" 2>/dev/null || true ufw allow 111/udp comment "RPC" 2>/dev/null || true # Allow iSCSI ports echo " Allowing iSCSI ports..." ufw allow 3260/tcp comment "iSCSI" 2>/dev/null || true echo -e "${GREEN}Firewall configured${NC}" echo "" } # Setup ZFS with comprehensive checks setup_zfs() { echo -e "${GREEN}Setting up ZFS...${NC}" if ! command -v zpool &> /dev/null; then echo -e "${RED}Error: ZFS utilities not found${NC}" echo " Please install zfsutils-linux" return 1 fi # Load ZFS kernel module echo " Loading ZFS kernel module..." if ! lsmod | grep -q "^zfs"; then modprobe zfs 2>/dev/null || { echo -e "${YELLOW}Warning: Could not load ZFS module${NC}" echo " This may require a system reboot" } else echo -e "${GREEN} ✓ ZFS module loaded${NC}" fi # Ensure ZFS module loads on boot if [[ ! -f /etc/modules-load.d/zfs.conf ]] || ! grep -q "^zfs" /etc/modules-load.d/zfs.conf 2>/dev/null; then echo "zfs" > /etc/modules-load.d/zfs.conf echo -e "${GREEN} ✓ ZFS will load on boot${NC}" fi # Check ZFS pool status echo " Checking ZFS pools..." if zpool list &>/dev/null; then POOL_COUNT=$(zpool list -H | wc -l) if [[ $POOL_COUNT -gt 0 ]]; then echo -e "${GREEN} ✓ Found $POOL_COUNT ZFS pool(s)${NC}" else echo -e "${YELLOW} ⚠ No ZFS pools found (you can create them after installation)${NC}" fi else echo -e "${YELLOW} ⚠ ZFS not fully initialized${NC}" fi echo -e "${GREEN}ZFS setup complete${NC}" echo "" } # Setup Samba with dependency checks setup_samba() { echo -e "${GREEN}Setting up Samba...${NC}" if ! command -v smbd &> /dev/null; then echo -e "${RED}Error: Samba not found${NC}" return 1 fi # Create basic Samba config if it doesn't exist if [[ ! -f /etc/samba/smb.conf ]] || [[ ! -s /etc/samba/smb.conf ]]; then echo " Creating basic Samba configuration..." mkdir -p /etc/samba cat > /etc/samba/smb.conf <<'SAMBAEOF' [global] workgroup = WORKGROUP server string = AtlasOS Storage Server security = user map to guest = Bad User dns proxy = no SAMBAEOF echo -e "${GREEN} ✓ Basic Samba config created${NC}" fi # Enable and start Samba services echo " Enabling Samba services..." systemctl enable smbd 2>/dev/null || true systemctl enable nmbd 2>/dev/null || true # Start services if not running if ! systemctl is-active --quiet smbd; then systemctl start smbd 2>/dev/null || echo -e "${YELLOW} ⚠ Could not start smbd (may need manual start)${NC}" fi echo -e "${GREEN}Samba setup complete${NC}" echo "" } # Setup NFS with dependency checks setup_nfs() { echo -e "${GREEN}Setting up NFS...${NC}" if ! command -v exportfs &> /dev/null; then echo -e "${RED}Error: NFS utilities not found${NC}" return 1 fi # Ensure /etc/exports exists if [[ ! -f /etc/exports ]]; then echo " Creating /etc/exports..." touch /etc/exports echo -e "${GREEN} ✓ /etc/exports created${NC}" fi # Enable and start NFS services echo " Enabling NFS services..." systemctl enable rpcbind 2>/dev/null || true systemctl enable nfs-server 2>/dev/null || true systemctl enable nfs-kernel-server 2>/dev/null || true # Start rpcbind first (required dependency) if ! systemctl is-active --quiet rpcbind; then systemctl start rpcbind 2>/dev/null || echo -e "${YELLOW} ⚠ Could not start rpcbind${NC}" fi # Start NFS server if ! systemctl is-active --quiet nfs-server && ! systemctl is-active --quiet nfs-kernel-server; then systemctl start nfs-server 2>/dev/null || systemctl start nfs-kernel-server 2>/dev/null || \ echo -e "${YELLOW} ⚠ Could not start NFS server (may need manual start)${NC}" fi echo -e "${GREEN}NFS setup complete${NC}" echo "" } # Setup iSCSI with dependency checks setup_iscsi() { echo -e "${GREEN}Setting up iSCSI...${NC}" # Check for targetcli or targetcli-fb TARGETCLI_CMD="" if command -v targetcli &> /dev/null; then TARGETCLI_CMD="targetcli" elif command -v targetcli-fb &> /dev/null; then TARGETCLI_CMD="targetcli-fb" # Create symlink if targetcli doesn't exist if ! command -v targetcli &> /dev/null; then ln -sf $(which targetcli-fb) /usr/local/bin/targetcli 2>/dev/null || true fi fi if [[ -z "$TARGETCLI_CMD" ]]; then echo -e "${RED}Error: targetcli or targetcli-fb not found${NC}" echo " Install with: apt-get install targetcli-fb" return 1 fi echo -e "${GREEN} ✓ Found $TARGETCLI_CMD${NC}" # Check if target service exists if systemctl list-unit-files | grep -q "^target.service"; then # Enable and start iSCSI target service echo " Enabling iSCSI target service..." systemctl enable target 2>/dev/null || true # Start service if not running if ! systemctl is-active --quiet target; then systemctl start target 2>/dev/null || echo -e "${YELLOW} ⚠ Could not start target service (may need manual start)${NC}" fi else echo -e "${YELLOW} ⚠ target.service not found (LIO may not be properly installed)${NC}" fi echo -e "${GREEN}iSCSI setup complete (using $TARGETCLI_CMD)${NC}" echo "" } # Setup PostgreSQL database setup_postgresql() { echo -e "${GREEN}Setting up PostgreSQL database...${NC}" if ! command -v psql &> /dev/null; then echo -e "${YELLOW}Warning: PostgreSQL client not found${NC}" echo " PostgreSQL features may not be available" return 1 fi # Default database credentials DB_NAME="atlas" DB_USER="dbadmin" DB_PASSWORD="Pnd77netM4v3r1cks" # Start PostgreSQL service if not running echo " Starting PostgreSQL service..." if ! systemctl is-active --quiet postgresql; then systemctl start postgresql 2>/dev/null || { echo -e "${YELLOW}Warning: Could not start PostgreSQL service${NC}" echo " You may need to start it manually: systemctl start postgresql" return 1 } fi # Enable PostgreSQL to start on boot systemctl enable postgresql 2>/dev/null || true # Wait a moment for PostgreSQL to be ready sleep 2 # Check if PostgreSQL is accessible if ! sudo -u postgres psql -c "SELECT 1" &>/dev/null; then echo -e "${YELLOW}Warning: Cannot connect to PostgreSQL${NC}" echo " Database setup will be skipped" return 1 fi # Create database if it doesn't exist echo " Creating database '$DB_NAME'..." sudo -u postgres psql -c "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'" | grep -q 1 || { sudo -u postgres psql -c "CREATE DATABASE $DB_NAME;" 2>/dev/null || { echo -e "${YELLOW}Warning: Could not create database (may already exist)${NC}" } } # Create user if it doesn't exist echo " Creating database user '$DB_USER'..." sudo -u postgres psql -c "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" | grep -q 1 || { sudo -u postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';" 2>/dev/null || { echo -e "${YELLOW}Warning: Could not create user (may already exist)${NC}" } } # Grant privileges echo " Granting privileges..." sudo -u postgres psql -d "$DB_NAME" -c "GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;" 2>/dev/null || true sudo -u postgres psql -d "$DB_NAME" -c "ALTER USER $DB_USER WITH SUPERUSER;" 2>/dev/null || true # Grant schema privileges (PostgreSQL 15+ requires this) sudo -u postgres psql -d "$DB_NAME" -c "GRANT ALL ON SCHEMA public TO $DB_USER;" 2>/dev/null || true # Update pg_hba.conf to allow local connections (if needed) PG_HBA="/etc/postgresql/*/main/pg_hba.conf" if ls $PG_HBA &>/dev/null; then if ! grep -q "^local.*all.*$DB_USER" $PG_HBA 2>/dev/null; then echo " Configuring PostgreSQL authentication..." # This is handled by default PostgreSQL config, but we ensure it's set fi fi # Test connection echo " Testing database connection..." if PGPASSWORD="$DB_PASSWORD" psql -h localhost -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1;" &>/dev/null; then echo -e "${GREEN} ✓ PostgreSQL database ready${NC}" echo " Database: $DB_NAME" echo " User: $DB_USER" echo " Connection: postgres://$DB_USER:$DB_PASSWORD@localhost:5432/$DB_NAME?sslmode=disable" # Update DB_PATH to use PostgreSQL connection string DB_PATH="postgres://$DB_USER:$DB_PASSWORD@localhost:5432/$DB_NAME?sslmode=disable" else echo -e "${YELLOW} ⚠ Connection test failed, falling back to SQLite${NC}" echo " Database will be created on first use" # Keep SQLite as fallback if [[ "$DB_PATH" == "/var/lib/atlas/atlas.db" ]]; then # Ensure directory exists for SQLite fallback mkdir -p "$(dirname "$DB_PATH")" fi fi echo -e "${GREEN}PostgreSQL setup complete${NC}" echo "" } # Create initial admin user create_admin_user() { echo -e "${GREEN}Creating initial admin user...${NC}" echo "" echo -e "${YELLOW}Please set up the initial admin user:${NC}" echo " Username: admin" echo " Password: (you will be prompted)" echo "" echo "After starting the service, you can create the admin user via:" echo " curl -X POST http://localhost:8080/api/v1/users \\" echo " -H 'Content-Type: application/json' \\" echo " -d '{\"username\":\"admin\",\"password\":\"your-password\",\"role\":\"administrator\"}'" echo "" echo "Or use the TUI:" echo " $INSTALL_DIR/bin/atlas-tui" echo "" } # Start service start_service() { echo -e "${GREEN}Starting AtlasOS service...${NC}" systemctl enable atlas-api systemctl start atlas-api # Wait a moment for service to start sleep 2 if systemctl is-active --quiet atlas-api; then echo -e "${GREEN}AtlasOS service started successfully${NC}" else echo -e "${RED}Error: Service failed to start${NC}" echo "Check logs with: journalctl -u atlas-api -n 50" exit 1 fi } # Print summary print_summary() { echo "" echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}AtlasOS Installation Complete!${NC}" echo -e "${GREEN}Ubuntu 24.04 (Noble Numbat)${NC}" echo -e "${GREEN}========================================${NC}" echo "" echo "Installation Directory: $INSTALL_DIR" echo "Data Directory: $DATA_DIR" echo "Config Directory: $CONFIG_DIR" echo "Log Directory: $LOG_DIR" echo "" # Extract port from HTTP_ADDR PORT=$(echo "$HTTP_ADDR" | sed 's/.*://' || echo "8080") if [[ -z "$PORT" ]] || [[ "$PORT" == "$HTTP_ADDR" ]]; then PORT="8080" fi echo "Service Status:" systemctl status atlas-api --no-pager -l 2>/dev/null || echo " Service not running" echo "" echo "Useful Commands:" echo " Service: systemctl {start|stop|restart|status} atlas-api" echo " Logs: journalctl -u atlas-api -f" echo " TUI: atlas-tui (or $INSTALL_DIR/bin/atlas-tui)" echo "" echo "Web Interface:" echo " http://localhost:$PORT" echo "" echo "API Documentation:" echo " http://localhost:$PORT/api/docs" echo "" if [[ "$FIREWALL_ACTIVE" == "true" ]]; then echo -e "${YELLOW}Firewall Configuration:${NC}" echo " UFW is active. The following ports have been allowed:" echo " - Port $PORT (AtlasOS API)" echo " - Port 445 (SMB/CIFS)" echo " - Port 139 (NetBIOS)" echo " - Port 2049 (NFS)" echo " - Port 3260 (iSCSI)" echo " To view rules: ufw status" echo "" fi echo -e "${YELLOW}Storage Services Status:${NC}" echo -n " ZFS: " if command -v zpool &>/dev/null && lsmod | grep -q "^zfs"; then echo -e "${GREEN}✓ Ready${NC}" else echo -e "${YELLOW}⚠ Check required${NC}" fi echo -n " Samba: " if systemctl is-active --quiet smbd 2>/dev/null; then echo -e "${GREEN}✓ Running${NC}" else echo -e "${YELLOW}⚠ Not running (start with: systemctl start smbd)${NC}" fi echo -n " NFS: " if systemctl is-active --quiet nfs-server 2>/dev/null || systemctl is-active --quiet nfs-kernel-server 2>/dev/null; then echo -e "${GREEN}✓ Running${NC}" else echo -e "${YELLOW}⚠ Not running (start with: systemctl start nfs-server)${NC}" fi echo -n " iSCSI: " if systemctl is-active --quiet target 2>/dev/null; then echo -e "${GREEN}✓ Running${NC}" else echo -e "${YELLOW}⚠ Not running (start with: systemctl start target)${NC}" fi echo -n " PostgreSQL: " if systemctl is-active --quiet postgresql 2>/dev/null; then echo -e "${GREEN}✓ Running${NC}" if echo "$DB_PATH" | grep -q "postgres://"; then echo " Database: atlas (user: dbadmin)" fi else echo -e "${YELLOW}⚠ Not running (start with: systemctl start postgresql)${NC}" echo " Using SQLite fallback" fi echo "" echo -e "${YELLOW}Next Steps:${NC}" echo "1. Create initial admin user (see instructions above)" echo "2. Configure TLS certificates (optional) - see $CONFIG_DIR/atlas.conf" echo "3. Review configuration in $CONFIG_DIR/atlas.conf" echo "4. Ensure storage services are running if needed" echo "" echo -e "${GREEN}Installation completed successfully!${NC}" echo "" } # Main installation main() { echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}AtlasOS Installation Script${NC}" echo -e "${GREEN}For Ubuntu 24.04 (Noble Numbat)${NC}" echo -e "${GREEN}========================================${NC}" echo "" # Step 1: Detect and validate distribution detect_distro echo "" # Step 2: Pre-flight checks preflight_checks # Step 3: Fix infrastructure gaps fix_infrastructure_gaps # Step 4: Install dependencies if [[ "$SKIP_DEPS" == "false" ]]; then if [[ "$OFFLINE_MODE" == "true" ]]; then echo -e "${GREEN}Offline mode: Installing from bundle${NC}" echo "" fi install_dependencies else echo -e "${YELLOW}Skipping dependency installation${NC}" echo "" fi # Step 5: Create system user and directories create_user create_directories # Step 6: Build binaries build_binaries # Step 6.5: Copy web files (templates and static assets) copy_web_files # Step 6.6: Setup PostgreSQL database (before config creation) setup_postgresql # Step 7: Create configuration create_config generate_jwt_secret # Step 8: Create systemd service create_systemd_service # Step 9: Setup storage services setup_zfs setup_samba setup_nfs setup_iscsi # Step 10: Configure firewall configure_firewall # Step 11: Create admin user info create_admin_user # Step 12: Ask if user wants to start service echo "" read -p "Start AtlasOS service now? (y/n) " -n 1 -r echo "" if [[ $REPLY =~ ^[Yy]$ ]]; then start_service else echo -e "${YELLOW}Service not started. Start manually with: systemctl start atlas-api${NC}" fi # Step 13: Print summary print_summary } # Run main main