#!/bin/bash # # AtlasOS Installation Script # Installs AtlasOS storage controller on a Linux system # # Usage: sudo ./install.sh [options] # # Note: Run this script from the atlas repository root directory # set -e # 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="" # 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 ;; -h|--help) echo "AtlasOS Installation Script" 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 " -h, --help Show this help message" 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)" # Detect distribution detect_distro() { if [[ -f /etc/os-release ]]; then . /etc/os-release DISTRO=$ID VERSION=$VERSION_ID else echo -e "${RED}Error: Cannot detect Linux distribution${NC}" exit 1 fi } # Install dependencies install_dependencies() { echo -e "${GREEN}Installing dependencies...${NC}" case $DISTRO in ubuntu|debian) apt-get update # Try targetcli-fb first (common on newer Ubuntu/Debian), fallback to targetcli TARGETCLI_PKG="targetcli-fb" if ! apt-cache show targetcli-fb &>/dev/null; then TARGETCLI_PKG="targetcli" fi apt-get install -y \ zfsutils-linux \ samba \ nfs-kernel-server \ $TARGETCLI_PKG \ sqlite3 \ golang-go \ git \ build-essential \ curl ;; fedora|rhel|centos) if command -v dnf &> /dev/null; then dnf install -y \ zfs \ samba \ nfs-utils \ targetcli \ sqlite \ golang \ git \ gcc \ make \ curl else yum install -y \ zfs \ samba \ nfs-utils \ targetcli \ sqlite \ golang \ git \ gcc \ make \ curl fi ;; *) echo -e "${YELLOW}Warning: Unknown distribution. Please install dependencies manually:${NC}" echo " - ZFS utilities" echo " - Samba (SMB/CIFS)" echo " - NFS server" echo " - targetcli or targetcli-fb (iSCSI)" echo " - SQLite" echo " - Go compiler" echo " - Build tools" ;; esac echo -e "${GREEN}Dependencies installed${NC}" } # 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 } # Create directories create_directories() { echo -e "${GREEN}Creating directories...${NC}" mkdir -p "$INSTALL_DIR/bin" mkdir -p "$DATA_DIR" mkdir -p "$CONFIG_DIR" mkdir -p "$LOG_DIR" mkdir -p "$BACKUP_DIR" mkdir -p "$CONFIG_DIR/tls" # Set ownership chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR" chown -R "$SERVICE_USER:$SERVICE_USER" "$LOG_DIR" chown -R "$SERVICE_USER:$SERVICE_USER" "$BACKUP_DIR" chown -R "$SERVICE_USER:$SERVICE_USER" "$CONFIG_DIR" # Set permissions chmod 755 "$INSTALL_DIR" chmod 755 "$INSTALL_DIR/bin" chmod 700 "$DATA_DIR" chmod 700 "$CONFIG_DIR" chmod 750 "$LOG_DIR" chmod 750 "$BACKUP_DIR" echo -e "${GREEN}Directories created${NC}" } # Build binaries build_binaries() { if [[ "$BUILD_BINARIES" == "false" ]]; then echo -e "${YELLOW}Skipping binary build${NC}" return fi echo -e "${GREEN}Building binaries...${NC}" # Check if we're in the right directory # Look for go.mod and internal/ directory (indicates we're in the repo root) BUILD_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 BUILD_DIR="$(cd "$REPO_DIR" && pwd)" # Try current directory elif is_repo_root "."; then BUILD_DIR="$(pwd)" # Try script directory elif is_repo_root "$SCRIPT_DIR"; then BUILD_DIR="$SCRIPT_DIR" # Try parent of script directory elif is_repo_root "$SCRIPT_DIR/.."; then BUILD_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" fi # If still not found, show error if [[ -z "$BUILD_DIR" ]]; then echo -e "${RED}Error: Cannot find atlas repository root${NC}" echo " Current directory: $(pwd)" echo " Script directory: $SCRIPT_DIR" echo "" echo " Looking for directory with:" echo " - go.mod file" echo " - internal/ directory" echo "" echo " Checking current directory:" [[ -f "./go.mod" ]] && echo " ✓ Found go.mod" || echo " ✗ No go.mod" [[ -d "./internal" ]] && echo " ✓ Found internal/" || echo " ✗ No internal/" echo "" echo " Please run installer from the atlas repository root" echo " Or specify path with: --repo-dir /path/to/atlas" exit 1 fi cd "$BUILD_DIR" echo "Building from: $(pwd)" # Create cmd directory structure if it doesn't exist if [[ ! -f "./cmd/atlas-api/main.go" ]]; then echo -e "${YELLOW}cmd/ directory not found, creating...${NC}" mkdir -p ./cmd/atlas-api mkdir -p ./cmd/atlas-tui # Create atlas-api/main.go cat > ./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") dbPath := env("ATLAS_DB_PATH", "data/atlas.db") app, err := httpapp.New(httpapp.Config{ Addr: addr, TemplatesDir: "web/templates", StaticDir: "web/static", DatabasePath: dbPath, }) 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 atlas-tui/main.go 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}Created cmd/ directory structure${NC}" fi # 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" } # 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 } # Setup ZFS (if needed) setup_zfs() { echo -e "${GREEN}Checking ZFS...${NC}" if ! command -v zpool &> /dev/null; then echo -e "${YELLOW}Warning: ZFS not found. Please install ZFS utilities${NC}" return fi # Check if ZFS module is loaded if ! lsmod | grep -q zfs; then echo -e "${YELLOW}Warning: ZFS kernel module not loaded${NC}" echo " Run: modprobe zfs" fi echo -e "${GREEN}ZFS check complete${NC}" } # Setup Samba setup_samba() { echo -e "${GREEN}Setting up Samba...${NC}" if ! command -v smbd &> /dev/null; then echo -e "${YELLOW}Warning: Samba not found${NC}" return fi # Enable and start Samba (if not already) systemctl enable smbd 2>/dev/null || true systemctl enable nmbd 2>/dev/null || true echo -e "${GREEN}Samba setup complete${NC}" } # Setup NFS setup_nfs() { echo -e "${GREEN}Setting up NFS...${NC}" if ! command -v exportfs &> /dev/null; then echo -e "${YELLOW}Warning: NFS not found${NC}" return fi # Enable and start NFS (if not already) systemctl enable nfs-server 2>/dev/null || true systemctl enable rpcbind 2>/dev/null || true echo -e "${GREEN}NFS setup complete${NC}" } # Setup iSCSI 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 -s $(which targetcli-fb) /usr/local/bin/targetcli 2>/dev/null || true fi fi if [[ -z "$TARGETCLI_CMD" ]]; then echo -e "${YELLOW}Warning: targetcli or targetcli-fb not found${NC}" echo " Install with: apt-get install targetcli-fb (Ubuntu/Debian)" return fi # Enable and start iSCSI target (if not already) systemctl enable target 2>/dev/null || true echo -e "${GREEN}iSCSI setup complete (using $TARGETCLI_CMD)${NC}" } # 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}========================================${NC}" echo "" echo "Installation Directory: $INSTALL_DIR" echo "Data Directory: $DATA_DIR" echo "Config Directory: $CONFIG_DIR" echo "Log Directory: $LOG_DIR" echo "" echo "Service Status:" systemctl status atlas-api --no-pager -l || true echo "" echo "Useful Commands:" echo " Service: systemctl {start|stop|restart|status} atlas-api" echo " Logs: journalctl -u atlas-api -f" echo " TUI: $INSTALL_DIR/bin/atlas-tui" echo "" echo "Web Interface:" echo " http://localhost:8080" echo "" echo "API Documentation:" echo " http://localhost:8080/api/docs" echo "" echo -e "${YELLOW}Next Steps:${NC}" echo "1. Create initial admin user (see instructions above)" echo "2. Configure TLS certificates (optional)" echo "3. Review configuration in $CONFIG_DIR/atlas.conf" echo "" } # Main installation main() { echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}AtlasOS Installation Script${NC}" echo -e "${GREEN}========================================${NC}" echo "" detect_distro echo "Detected distribution: $DISTRO $VERSION" echo "" if [[ "$SKIP_DEPS" == "false" ]]; then install_dependencies else echo -e "${YELLOW}Skipping dependency installation${NC}" fi create_user create_directories build_binaries create_config generate_jwt_secret create_systemd_service setup_zfs setup_samba setup_nfs setup_iscsi create_admin_user # 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 print_summary } # Run main main