From b4ef76f0d0a1819fd76e0e233b33414da242aa69 Mon Sep 17 00:00:00 2001 From: "othman.suseno" Date: Mon, 15 Dec 2025 16:38:20 +0700 Subject: [PATCH] add installer alpha version --- README.md | 4 +- docs/BACKGROUND_JOBS.md | 2 +- docs/RBAC_PERMISSIONS.md | 150 +++++ docs/openapi.yaml | 4 +- fix-sudoers.sh | 34 ++ install.sh | 592 +++++++++++++++---- internal/httpapp/api_handlers.go | 26 + internal/httpapp/app.go | 39 +- internal/httpapp/auth_middleware.go | 41 +- internal/httpapp/docs_handlers.go | 2 +- internal/httpapp/handlers.go | 66 +++ internal/httpapp/routes.go | 61 ++ internal/httpapp/security_middleware.go | 4 +- internal/httpapp/service_handlers.go | 249 ++++++++ internal/zfs/service.go | 71 ++- web/templates/base.html | 80 ++- web/templates/dashboard.html | 11 +- web/templates/iscsi.html | 380 +++++++++++++ web/templates/login.html | 81 +++ web/templates/management.html | 710 +++++++++++++++++++++++ web/templates/protection.html | 704 +++++++++++++++++++++++ web/templates/shares.html | 379 +++++++++++++ web/templates/storage.html | 725 ++++++++++++++++++++++++ 23 files changed, 4279 insertions(+), 136 deletions(-) create mode 100644 docs/RBAC_PERMISSIONS.md create mode 100755 fix-sudoers.sh create mode 100644 internal/httpapp/service_handlers.go create mode 100644 web/templates/iscsi.html create mode 100644 web/templates/login.html create mode 100644 web/templates/management.html create mode 100644 web/templates/protection.html create mode 100644 web/templates/shares.html create mode 100644 web/templates/storage.html diff --git a/README.md b/README.md index cfdc5b8..fc924b4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# atlasOS +# AtlasOS -atlasOS is an appliance-style storage controller build by Adastra +AtlasOS is an appliance-style storage controller build by Adastra **v1 Focus** - ZFS storage engine diff --git a/docs/BACKGROUND_JOBS.md b/docs/BACKGROUND_JOBS.md index e7cf20d..c23f96a 100644 --- a/docs/BACKGROUND_JOBS.md +++ b/docs/BACKGROUND_JOBS.md @@ -1,6 +1,6 @@ # Background Job System -The atlasOS API includes a background job system that automatically executes snapshot policies and manages long-running operations. +The AtlasOS API includes a background job system that automatically executes snapshot policies and manages long-running operations. ## Architecture diff --git a/docs/RBAC_PERMISSIONS.md b/docs/RBAC_PERMISSIONS.md new file mode 100644 index 0000000..81fab2b --- /dev/null +++ b/docs/RBAC_PERMISSIONS.md @@ -0,0 +1,150 @@ +# Role-Based Access Control (RBAC) - Current Implementation + +## Overview + +AtlasOS implements a three-tier role-based access control system with the following roles: + +1. **Administrator** (`administrator`) - Full system control +2. **Operator** (`operator`) - Storage and service operations +3. **Viewer** (`viewer`) - Read-only access + +## Current Implementation Status + +### ✅ Fully Implemented (Administrator-Only) + +These operations **require Administrator role**: + +- **User Management**: Create, update, delete users, list users +- **Service Management**: Start, stop, restart, reload services, view service logs +- **Maintenance Mode**: Enable/disable maintenance mode + +### ⚠️ Partially Implemented (Authentication Required, No Role Check) + +These operations **require authentication** but **don't check specific roles** (any authenticated user can perform them): + +- **ZFS Operations**: Create/delete pools, datasets, ZVOLs, import/export pools, scrub operations +- **Snapshot Management**: Create/delete snapshots, create/delete snapshot policies +- **Storage Services**: Create/update/delete SMB shares, NFS exports, iSCSI targets +- **Backup & Restore**: Create backups, restore backups + +### ✅ Public (No Authentication Required) + +These endpoints are **publicly accessible**: + +- **Read-Only Operations**: List pools, datasets, ZVOLs, shares, exports, targets, snapshots +- **Dashboard Data**: System statistics and health information +- **Web UI Pages**: All HTML pages (authentication required for mutations via API) + +## Role Definitions + +### Administrator (`administrator`) +- **Full system access** +- Can manage users (create, update, delete) +- Can manage services (start, stop, restart, reload) +- Can enable/disable maintenance mode +- Can perform all storage operations +- Can view audit logs + +### Operator (`operator`) +- **Storage and service operations** (intended) +- Currently: Same as authenticated user (can perform storage operations) +- Should be able to: Create/manage pools, datasets, shares, snapshots +- Should NOT be able to: Manage users, manage services, maintenance mode + +### Viewer (`viewer`) +- **Read-only access** (intended) +- Currently: Can view all public data +- Should be able to: View all system information +- Should NOT be able to: Perform any mutations (create, update, delete) + +## Current Permission Matrix + +| Operation | Administrator | Operator | Viewer | Unauthenticated | +|-----------|--------------|----------|--------|-----------------| +| **User Management** | +| List users | ✅ | ❌ | ❌ | ❌ | +| Create user | ✅ | ❌ | ❌ | ❌ | +| Update user | ✅ | ❌ | ❌ | ❌ | +| Delete user | ✅ | ❌ | ❌ | ❌ | +| **Service Management** | +| View service status | ✅ | ❌ | ❌ | ❌ | +| Start/stop/restart service | ✅ | ❌ | ❌ | ❌ | +| View service logs | ✅ | ❌ | ❌ | ❌ | +| **Storage Operations** | +| List pools/datasets/ZVOLs | ✅ | ✅ | ✅ | ✅ (public) | +| Create pool/dataset/ZVOL | ✅ | ✅* | ❌ | ❌ | +| Delete pool/dataset/ZVOL | ✅ | ✅* | ❌ | ❌ | +| Import/export pool | ✅ | ✅* | ❌ | ❌ | +| **Share Management** | +| List shares/exports/targets | ✅ | ✅ | ✅ | ✅ (public) | +| Create share/export/target | ✅ | ✅* | ❌ | ❌ | +| Update share/export/target | ✅ | ✅* | ❌ | ❌ | +| Delete share/export/target | ✅ | ✅* | ❌ | ❌ | +| **Snapshot Management** | +| List snapshots/policies | ✅ | ✅ | ✅ | ✅ (public) | +| Create snapshot/policy | ✅ | ✅* | ❌ | ❌ | +| Delete snapshot/policy | ✅ | ✅* | ❌ | ❌ | +| **Maintenance Mode** | +| View status | ✅ | ✅ | ✅ | ✅ (public) | +| Enable/disable | ✅ | ❌ | ❌ | ❌ | + +*Currently works but not explicitly restricted - any authenticated user can perform these operations + +## Implementation Details + +### Role Checking + +Roles are checked using the `requireRole()` middleware: + +```go +// Example: Administrator-only endpoint +a.mux.HandleFunc("/api/v1/users", methodHandler( + func(w http.ResponseWriter, r *http.Request) { a.handleListUsers(w, r) }, + func(w http.ResponseWriter, r *http.Request) { + adminRole := models.RoleAdministrator + a.requireRole(adminRole)(http.HandlerFunc(a.handleCreateUser)).ServeHTTP(w, r) + }, + nil, nil, nil, +)) +``` + +### Multiple Roles Support + +The `requireRole()` function accepts multiple roles: + +```go +// Allow both Administrator and Operator +a.requireRole(models.RoleAdministrator, models.RoleOperator)(handler) +``` + +### Current Limitations + +1. **No Operator/Viewer Differentiation**: Most storage operations don't check roles - they only require authentication +2. **Hardcoded Role Checks**: Role permissions are defined in route handlers, not in a centralized permission matrix +3. **No Granular Permissions**: Can't assign specific permissions (e.g., "can create pools but not delete them") + +## Future Improvements + +To properly implement Operator and Viewer roles: + +1. **Add Role Checks to Storage Operations**: + - Allow Operator and Administrator for create/update/delete operations + - Restrict Viewer to read-only (GET requests only) + +2. **Centralize Permission Matrix**: + - Create a permission configuration file or database table + - Map operations to required roles + +3. **Granular Permissions** (Future): + - Allow custom permission sets + - Support resource-level permissions (e.g., "can manage pool X but not pool Y") + +## Testing Roles + +To test different roles: + +1. Create users with different roles via the Management page +2. Login as each user +3. Attempt operations and verify permissions + +**Note**: Currently, most operations work for any authenticated user. Only user management, service management, and maintenance mode are properly restricted to Administrators. diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 328ccc2..f33b797 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1,6 +1,6 @@ openapi: 3.0.3 info: - title: atlasOS Storage Controller API + title: AtlasOS Storage Controller API description: | REST API for managing ZFS storage, storage services (SMB/NFS/iSCSI), snapshots, and system configuration. @@ -17,7 +17,7 @@ info: version: 1.0.0 contact: - name: atlasOS Support + name: AtlasOS Support url: https://github.com/atlasos servers: diff --git a/fix-sudoers.sh b/fix-sudoers.sh new file mode 100755 index 0000000..b4ca478 --- /dev/null +++ b/fix-sudoers.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Quick fix script to add current user to ZFS sudoers for development +# Usage: sudo ./fix-sudoers.sh + +set -e + +CURRENT_USER=$(whoami) +SUDOERS_FILE="/etc/sudoers.d/atlas-zfs" + +echo "Adding $CURRENT_USER to ZFS sudoers for development..." + +# Check if sudoers file exists +if [ ! -f "$SUDOERS_FILE" ]; then + echo "Creating sudoers file..." + cat > "$SUDOERS_FILE" <> "$SUDOERS_FILE" </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 - - # 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}✓ Universe repository enabled${NC}" + else + echo -e "${GREEN}✓${NC}" + fi - echo -e "${GREEN}Dependencies installed${NC}" + # 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 for Ubuntu 24.04 +install_dependencies() { + 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 database + echo " Installing SQLite..." + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq sqlite3 libsqlite3-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 \ + systemd \ + journalctl + + echo -e "${GREEN}Dependencies installed successfully${NC}" + echo "" } # Create system user @@ -560,57 +753,164 @@ generate_jwt_secret() { 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}" +# Configure firewall (UFW) for Ubuntu 24.04 +configure_firewall() { + if [[ "$FIREWALL_ACTIVE" != "true" ]]; then 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" + 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 - echo -e "${GREEN}ZFS check complete${NC}" + # 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 Samba +# 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 "${YELLOW}Warning: Samba not found${NC}" - return + echo -e "${RED}Error: Samba not found${NC}" + return 1 fi - # Enable and start Samba (if not already) + # 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 +# Setup NFS with dependency checks 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 + echo -e "${RED}Error: NFS utilities not found${NC}" + return 1 fi - # Enable and start NFS (if not already) - systemctl enable nfs-server 2>/dev/null || true + # 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 +# Setup iSCSI with dependency checks setup_iscsi() { echo -e "${GREEN}Setting up iSCSI...${NC}" @@ -622,20 +922,34 @@ setup_iscsi() { 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 + ln -sf $(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 + echo -e "${RED}Error: targetcli or targetcli-fb not found${NC}" + echo " Install with: apt-get install targetcli-fb" + return 1 fi - # Enable and start iSCSI target (if not already) - systemctl enable target 2>/dev/null || true + 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 "" } # Create initial admin user @@ -681,6 +995,7 @@ 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" @@ -688,24 +1003,81 @@ print_summary() { 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 || true + 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: $INSTALL_DIR/bin/atlas-tui" + echo " TUI: atlas-tui (or $INSTALL_DIR/bin/atlas-tui)" echo "" + echo "Web Interface:" - echo " http://localhost:8080" + echo " http://localhost:$PORT" echo "" + echo "API Documentation:" - echo " http://localhost:8080/api/docs" + 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 "" + echo -e "${YELLOW}Next Steps:${NC}" echo "1. Create initial admin user (see instructions above)" - echo "2. Configure TLS certificates (optional)" + 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 "" } @@ -713,34 +1085,55 @@ print_summary() { 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 "Detected distribution: $DISTRO $VERSION" 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 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 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 - # Ask if user wants to start service + # Step 12: Ask if user wants to start service echo "" read -p "Start AtlasOS service now? (y/n) " -n 1 -r echo "" @@ -750,6 +1143,7 @@ main() { echo -e "${YELLOW}Service not started. Start manually with: systemctl start atlas-api${NC}" fi + # Step 13: Print summary print_summary } diff --git a/internal/httpapp/api_handlers.go b/internal/httpapp/api_handlers.go index 6863abc..d9ced1a 100644 --- a/internal/httpapp/api_handlers.go +++ b/internal/httpapp/api_handlers.go @@ -36,6 +36,10 @@ func (a *App) handleListPools(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } + // Ensure we always return an array, not null + if pools == nil { + pools = []models.Pool{} + } writeJSON(w, http.StatusOK, pools) } @@ -215,6 +219,10 @@ func (a *App) handleListDatasets(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } + // Ensure we always return an array, not null + if datasets == nil { + datasets = []models.Dataset{} + } writeJSON(w, http.StatusOK, datasets) } @@ -398,6 +406,10 @@ func (a *App) handleListSnapshots(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } + // Ensure we always return an array, not null + if snapshots == nil { + snapshots = []models.Snapshot{} + } writeJSON(w, http.StatusOK, snapshots) } @@ -485,6 +497,10 @@ func (a *App) handleListSnapshotPolicies(w http.ResponseWriter, r *http.Request) } else { policies = a.snapshotPolicy.List() } + // Ensure we always return an array, not null + if policies == nil { + policies = []models.SnapshotPolicy{} + } writeJSON(w, http.StatusOK, policies) } @@ -1322,6 +1338,10 @@ func (a *App) handleCreateUser(w http.ResponseWriter, r *http.Request) { req.Role = models.RoleViewer // Default role } + // Normalize role to lowercase for comparison + roleStr := strings.ToLower(string(req.Role)) + req.Role = models.Role(roleStr) + // Validate role if req.Role != models.RoleAdministrator && req.Role != models.RoleOperator && req.Role != models.RoleViewer { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid role"}) @@ -1376,6 +1396,12 @@ func (a *App) handleUpdateUser(w http.ResponseWriter, r *http.Request) { return } + // Normalize role to lowercase if provided + if req.Role != "" { + roleStr := strings.ToLower(string(req.Role)) + req.Role = models.Role(roleStr) + } + // Validate role if provided if req.Role != "" && req.Role != models.RoleAdministrator && req.Role != models.RoleOperator && req.Role != models.RoleViewer { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid role"}) diff --git a/internal/httpapp/app.go b/internal/httpapp/app.go index 381ca3b..f95344f 100644 --- a/internal/httpapp/app.go +++ b/internal/httpapp/app.go @@ -55,11 +55,40 @@ type App struct { } func New(cfg Config) (*App, error) { + // Resolve paths relative to executable or current working directory if cfg.TemplatesDir == "" { - cfg.TemplatesDir = "web/templates" + // Try multiple locations for templates + possiblePaths := []string{ + "web/templates", + "./web/templates", + "/opt/atlas/web/templates", + } + for _, path := range possiblePaths { + if _, err := os.Stat(path); err == nil { + cfg.TemplatesDir = path + break + } + } + if cfg.TemplatesDir == "" { + cfg.TemplatesDir = "web/templates" // Default fallback + } } if cfg.StaticDir == "" { - cfg.StaticDir = "web/static" + // Try multiple locations for static files + possiblePaths := []string{ + "web/static", + "./web/static", + "/opt/atlas/web/static", + } + for _, path := range possiblePaths { + if _, err := os.Stat(path); err == nil { + cfg.StaticDir = path + break + } + } + if cfg.StaticDir == "" { + cfg.StaticDir = "web/static" // Default fallback + } } tmpl, err := parseTemplates(cfg.TemplatesDir) @@ -228,6 +257,12 @@ func parseTemplates(dir string) (*template.Template, error) { funcs := template.FuncMap{ "nowRFC3339": func() string { return time.Now().Format(time.RFC3339) }, + "getContentTemplate": func(data map[string]any) string { + if ct, ok := data["ContentTemplate"].(string); ok && ct != "" { + return ct + } + return "content" + }, } t := template.New("root").Funcs(funcs) diff --git a/internal/httpapp/auth_middleware.go b/internal/httpapp/auth_middleware.go index 286d597..d46fa0f 100644 --- a/internal/httpapp/auth_middleware.go +++ b/internal/httpapp/auth_middleware.go @@ -17,7 +17,7 @@ const ( // authMiddleware validates JWT tokens and extracts user info func (a *App) authMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Skip auth for public endpoints + // Skip auth for public endpoints (includes web UI pages and read-only GET endpoints) if a.isPublicEndpoint(r.URL.Path) { next.ServeHTTP(w, r) return @@ -101,14 +101,27 @@ func (a *App) requireRole(allowedRoles ...models.Role) func(http.Handler) http.H func (a *App) isPublicEndpoint(path string) bool { publicPaths := []string{ "/healthz", + "/health", "/metrics", "/api/v1/auth/login", "/api/v1/auth/logout", - "/", // Dashboard (can be made protected later) + "/", // Dashboard + "/login", // Login page + "/storage", // Storage management page + "/shares", // Shares page + "/iscsi", // iSCSI page + "/protection", // Data Protection page + "/management", // System Management page + "/api/docs", // API documentation + "/api/openapi.yaml", // OpenAPI spec } for _, publicPath := range publicPaths { - if path == publicPath || strings.HasPrefix(path, publicPath+"/") { + if path == publicPath { + return true + } + // Also allow paths that start with public paths (for sub-pages) + if strings.HasPrefix(path, publicPath+"/") { return true } } @@ -118,6 +131,28 @@ func (a *App) isPublicEndpoint(path string) bool { return true } + // Make read-only GET endpoints public for web UI (but require auth for mutations) + // This allows the UI to display data without login, but operations require auth + publicReadOnlyPaths := []string{ + "/api/v1/dashboard", // Dashboard data + "/api/v1/disks", // List disks + "/api/v1/pools", // List pools (GET only) + "/api/v1/pools/available", // List available pools + "/api/v1/datasets", // List datasets (GET only) + "/api/v1/zvols", // List ZVOLs (GET only) + "/api/v1/shares/smb", // List SMB shares (GET only) + "/api/v1/exports/nfs", // List NFS exports (GET only) + "/api/v1/iscsi/targets", // List iSCSI targets (GET only) + "/api/v1/snapshots", // List snapshots (GET only) + "/api/v1/snapshot-policies", // List snapshot policies (GET only) + } + + for _, publicPath := range publicReadOnlyPaths { + if path == publicPath { + return true + } + } + return false } diff --git a/internal/httpapp/docs_handlers.go b/internal/httpapp/docs_handlers.go index a7df2dd..12893b1 100644 --- a/internal/httpapp/docs_handlers.go +++ b/internal/httpapp/docs_handlers.go @@ -12,7 +12,7 @@ func (a *App) handleAPIDocs(w http.ResponseWriter, r *http.Request) { html := ` - atlasOS API Documentation + AtlasOS API Documentation