modify sonarqube

This commit is contained in:
2026-01-21 12:31:24 +00:00
parent 94e565257d
commit 7b7e171ca3
1725 changed files with 383274 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1 @@
a4040ea4ab486d4671772e4b832b4be5741bf58764fc731873b585df1d805e66 calypso-appliance-1.0.0-airgap.tar.gz

View File

@@ -0,0 +1,59 @@
# Calypso Appliance - Airgap Installation Bundle
**Version:** 1.0.0
**Target OS:** Ubuntu Server 24.04 LTS
**Installation Type:** Offline/Airgap
## Bundle Contents
- **binaries/**: Pre-built Calypso binaries
- **frontend/**: Built frontend assets
- **packages/debs/**: All required DEB packages with dependencies (59 packages + ~200-300 dependencies)
- **scripts/**: Installation scripts
- **services/**: Systemd service files
- **migrations/**: Database migration files
- **configs/**: Configuration templates
- **third_party/**: Optional third-party binaries (Go, Node.js)
## Installation
### Prerequisites
- Ubuntu Server 24.04 LTS
- Root or sudo access
- Minimum 10GB free disk space
- Kernel headers installed (for kernel modules)
### Quick Install
```bash
cd /path/to/bundle
sudo ./install-airgap.sh
```
The installer will:
1. Install all packages from bundled DEB files
2. Install Calypso binaries and frontend
3. Setup database and configuration
4. Install systemd services
### Post-Installation
1. Review configuration: `/etc/calypso/calypso.yaml`
2. Install kernel modules (ZFS, SCST, mhVTL) if needed
3. Start services: `systemctl start calypso-api`
4. Enable services: `systemctl enable calypso-api`
## Verification
Check service status:
```bash
systemctl status calypso-api
curl http://localhost:8080/api/v1/health
```
## Support
For issues, check:
- Installation logs: `/var/log/calypso/install.log`
- Service logs: `journalctl -u calypso-api`

Binary file not shown.

View File

@@ -0,0 +1,64 @@
# AtlasOS - Calypso API Configuration Template
# This file will be copied to /etc/calypso/config.yaml during installation
# Environment variables from /etc/calypso/secrets.env will be used for sensitive values
server:
port: 8080
host: "0.0.0.0"
read_timeout: 15s
write_timeout: 15s
idle_timeout: 60s
# Response caching configuration
cache:
enabled: true # Enable response caching
default_ttl: 5m # Default cache TTL (5 minutes)
max_age: 300 # Cache-Control max-age in seconds (5 minutes)
database:
host: "localhost"
port: 5432
user: "calypso"
password: "" # Set via CALYPSO_DB_PASSWORD environment variable
database: "calypso"
ssl_mode: "disable"
# Connection pool optimization
max_connections: 25
max_idle_conns: 5
conn_max_lifetime: 5m
auth:
jwt_secret: "" # Set via CALYPSO_JWT_SECRET environment variable
token_lifetime: 24h
argon2:
memory: 65536 # 64 MB
iterations: 3
parallelism: 4
salt_length: 16
key_length: 32
logging:
level: "info" # debug, info, warn, error
format: "json" # json or text
# CORS configuration
cors:
allowed_origins:
- "http://localhost:3000"
- "http://localhost:5173"
allowed_methods:
- "GET"
- "POST"
- "PUT"
- "DELETE"
- "PATCH"
allowed_headers:
- "Content-Type"
- "Authorization"
allow_credentials: true
# Rate limiting
rate_limit:
enabled: true
requests_per_minute: 100
authenticated_requests_per_minute: 200

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
//# sourceMappingURL=terminal-vendor-l0sNRNKZ.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"terminal-vendor-l0sNRNKZ.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,29 @@
<!doctype html>
<html class="dark" lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AtlasOS - Calypso</title>
<!-- Material Symbols -->
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap"
rel="stylesheet"
/>
<script type="module" crossorigin src="/assets/index-ChZMKDzx.js"></script>
<link rel="modulepreload" crossorigin href="/assets/react-vendor-Dvs2KPqW.js">
<link rel="modulepreload" crossorigin href="/assets/query-vendor-BeRQtQod.js">
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-CJpcXyE0.js">
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-C4xvlrdo.js">
<link rel="modulepreload" crossorigin href="/assets/chart-vendor-CnBPFalK.js">
<link rel="stylesheet" crossorigin href="/assets/index-BY0QR2-n.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,215 @@
#!/bin/bash
#
# Calypso Appliance - Airgap Installer
# Installs Calypso from bundled files without internet connection
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUNDLE_DIR="$SCRIPT_DIR"
VERSION="${VERSION:-1.0.0}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "\n${BLUE}==>${NC} $1"; }
# Check root
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root (use sudo)"
exit 1
fi
# Check OS
if [ ! -f /etc/os-release ]; then
log_error "Cannot detect OS"
exit 1
fi
. /etc/os-release
if [[ "$ID" != "ubuntu" ]] || [[ "$VERSION_ID" != "24.04" ]]; then
log_warn "This installer is designed for Ubuntu 24.04 LTS"
log_warn "Detected: $ID $VERSION_ID"
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
log_step "Calypso Appliance - Airgap Installation"
log_info "Bundle directory: $BUNDLE_DIR"
# 1. Install packages from bundle
log_step "Installing system packages from bundle..."
if [ -d "$BUNDLE_DIR/packages/debs" ] && [ "$(ls -A $BUNDLE_DIR/packages/debs/*.deb 2>/dev/null)" ]; then
log_info "Installing packages from bundle..."
# Create local apt repository
DEBS_DIR="$BUNDLE_DIR/packages/debs"
REPO_DIR="/tmp/calypso-local-repo"
mkdir -p "$REPO_DIR"
cp "$DEBS_DIR"/*.deb "$REPO_DIR/" 2>/dev/null || true
# Create Packages.gz if not exists
cd "$REPO_DIR"
if [ ! -f Packages.gz ]; then
dpkg-scanpackages . /dev/null 2>/dev/null | gzip -9c > Packages.gz
fi
# Add to sources.list temporarily
echo "deb [trusted=yes] file://$REPO_DIR ./" > /tmp/calypso-local.list
mv /tmp/calypso-local.list /etc/apt/sources.list.d/calypso-local.list
# Update apt cache
apt-get update -qq
# Install packages from list
PACKAGE_LIST="$BUNDLE_DIR/packages/package-list.txt"
PACKAGES=$(grep -v '^#' "$PACKAGE_LIST" | grep -v '^$' | tr '\n' ' ')
log_info "Installing packages..."
apt-get install -y --allow-unauthenticated $PACKAGES || {
log_warn "Some packages failed to install, trying individual installation..."
while IFS= read -r package || [ -n "$package" ]; do
[[ "$package" =~ ^#.*$ ]] && continue
[[ -z "$package" ]] && continue
apt-get install -y --allow-unauthenticated "$package" 2>/dev/null || log_warn "Failed: $package"
done < "$PACKAGE_LIST"
}
# Cleanup
rm -f /etc/apt/sources.list.d/calypso-local.list
apt-get update -qq
log_info "✓ Packages installed from bundle"
else
log_warn "Package DEB files not found in bundle"
log_warn "You need to install packages manually"
log_info "Package list: $BUNDLE_DIR/packages/package-list.txt"
read -p "Install packages from system repositories? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
apt-get update
xargs -a "$BUNDLE_DIR/packages/package-list.txt" -r apt-get install -y
fi
fi
# 2. Install Go from bundle (if available)
if [ -f "$BUNDLE_DIR/third_party/go.tar.gz" ]; then
log_step "Installing Go from bundle..."
rm -rf /usr/local/go
tar -C /usr/local -xzf "$BUNDLE_DIR/third_party/go.tar.gz"
export PATH=$PATH:/usr/local/go/bin
if ! grep -q "/usr/local/go/bin" /etc/profile; then
echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile
fi
log_info "✓ Go installed"
fi
# 3. Install Node.js from bundle (if available)
if [ -f "$BUNDLE_DIR/third_party/nodejs.tar.xz" ]; then
log_step "Installing Node.js from bundle..."
rm -rf /usr/local/node
mkdir -p /usr/local/node
tar -C /usr/local/node -xJf "$BUNDLE_DIR/third_party/nodejs.tar.xz" --strip-components=1
export PATH=$PATH:/usr/local/node/bin
if ! grep -q "/usr/local/node/bin" /etc/profile; then
echo 'export PATH=$PATH:/usr/local/node/bin' >> /etc/profile
fi
log_info "✓ Node.js installed"
fi
# 4. Create filesystem structure
log_step "Creating filesystem structure..."
INSTALL_PREFIX="/opt/adastra/calypso"
CONFIG_DIR="/etc/calypso"
DATA_DIR="/srv/calypso"
LOG_DIR="/var/log/calypso"
LIB_DIR="/var/lib/calypso"
mkdir -p "$INSTALL_PREFIX/releases/${VERSION}"/{bin,web,migrations,scripts}
mkdir -p "$INSTALL_PREFIX/third_party"
mkdir -p "$CONFIG_DIR"/{tls,integrations,system,scst,nfs,samba,clamav}
mkdir -p "$DATA_DIR"/{db,backups,object,shares,vtl,iscsi,uploads,cache,_system,quarantine}
mkdir -p "$LOG_DIR" "$LIB_DIR" "/run/calypso"
# Create calypso user
if ! id "calypso" &>/dev/null; then
useradd -r -s /bin/false -d "$LIB_DIR" -c "Calypso Appliance" calypso
fi
# 5. Install binaries
log_step "Installing Calypso binaries..."
cp "$BUNDLE_DIR/binaries/calypso-api" "$INSTALL_PREFIX/releases/${VERSION}/bin/"
chmod +x "$INSTALL_PREFIX/releases/${VERSION}/bin/calypso-api"
# Install frontend
cp -r "$BUNDLE_DIR/frontend"/* "$INSTALL_PREFIX/releases/${VERSION}/web/"
# Install migrations
cp -r "$BUNDLE_DIR/migrations"/* "$INSTALL_PREFIX/releases/${VERSION}/migrations/" 2>/dev/null || true
# Install scripts
cp -r "$BUNDLE_DIR/scripts"/* "$INSTALL_PREFIX/releases/${VERSION}/scripts/" 2>/dev/null || true
chmod +x "$INSTALL_PREFIX/releases/${VERSION}/scripts"/*.sh 2>/dev/null || true
# Create symlink
ln -sfn "releases/${VERSION}" "$INSTALL_PREFIX/current"
# Set ownership
chown -R calypso:calypso "$INSTALL_PREFIX" "$LIB_DIR" "$LOG_DIR" "$DATA_DIR" 2>/dev/null || chown -R root:root "$INSTALL_PREFIX" "$LIB_DIR" "$LOG_DIR" "$DATA_DIR"
log_info "✓ Binaries installed"
# 6. Install systemd services
log_step "Installing systemd services..."
if [ -d "$BUNDLE_DIR/services" ]; then
cp "$BUNDLE_DIR/services"/*.service /etc/systemd/system/ 2>/dev/null || true
systemctl daemon-reload
log_info "✓ Systemd services installed"
fi
# 7. Setup database
log_step "Setting up database..."
if systemctl is-active --quiet postgresql 2>/dev/null || systemctl is-enabled --quiet postgresql 2>/dev/null; then
sudo -u postgres createdb calypso 2>/dev/null || true
sudo -u postgres createuser calypso 2>/dev/null || true
sudo -u postgres psql -c "ALTER USER calypso WITH PASSWORD 'calypso_change_me';" 2>/dev/null || true
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE calypso TO calypso;" 2>/dev/null || true
log_info "✓ Database setup complete"
else
log_warn "PostgreSQL not running. Please start it and run database setup manually."
fi
# 8. Generate configuration
log_step "Generating configuration..."
if [ -f "$BUNDLE_DIR/configs/config.yaml.template" ]; then
cp "$BUNDLE_DIR/configs/config.yaml.template" "$CONFIG_DIR/calypso.yaml"
# Generate secrets
if command -v openssl &> /dev/null; then
JWT_SECRET=$(openssl rand -hex 32)
sed -i "s/CHANGE_ME_JWT_SECRET/$JWT_SECRET/g" "$CONFIG_DIR/calypso.yaml"
fi
log_info "✓ Configuration generated"
fi
log_step "Installation Complete!"
log_info ""
log_info "Next steps:"
log_info "1. Review configuration: $CONFIG_DIR/calypso.yaml"
log_info "2. Install kernel modules (ZFS, SCST, mhVTL) if needed"
log_info "3. Start services: systemctl start calypso-api"
log_info "4. Enable services: systemctl enable calypso-api"
log_info ""
log_info "Calypso API will be available at: http://localhost:8080"

View File

@@ -0,0 +1,229 @@
#!/bin/bash
#
# AtlasOS - Calypso Appliance Installer
# Complete installation script for Calypso backup appliance
# Target OS: Ubuntu Server 24.04 LTS
#
# Usage: sudo ./installer/alpha/install.sh [options]
#
# Options:
# --version VERSION Install specific version (default: auto-detect)
# --skip-deps Skip system dependencies installation
# --skip-zfs Skip ZFS installation
# --skip-scst Skip SCST installation
# --skip-mhvtl Skip MHVTL installation
# --skip-bacula Skip Bacula installation
# --config-only Only setup configuration, don't install binaries
#
set -euo pipefail
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALLER_DIR="$SCRIPT_DIR"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "\n${BLUE}==>${NC} $1"
}
# Configuration
CALYPSO_VERSION="${CALYPSO_VERSION:-1.0.0-alpha}"
INSTALL_PREFIX="/opt/adastra/calypso"
CONFIG_DIR="/etc/calypso"
DATA_DIR="/srv/calypso"
LOG_DIR="/var/log/calypso"
LIB_DIR="/var/lib/calypso"
RUN_DIR="/run/calypso"
# Flags
SKIP_DEPS=false
SKIP_ZFS=false
SKIP_SCST=false
SKIP_MHVTL=false
SKIP_BACULA=false
CONFIG_ONLY=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--version)
CALYPSO_VERSION="$2"
shift 2
;;
--skip-deps)
SKIP_DEPS=true
shift
;;
--skip-zfs)
SKIP_ZFS=true
shift
;;
--skip-scst)
SKIP_SCST=true
shift
;;
--skip-mhvtl)
SKIP_MHVTL=true
shift
;;
--skip-bacula)
SKIP_BACULA=true
shift
;;
--config-only)
CONFIG_ONLY=true
shift
;;
*)
log_error "Unknown option: $1"
exit 1
;;
esac
done
# Check if running as root
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root (use sudo)"
exit 1
fi
# Detect OS
if ! grep -q "Ubuntu" /etc/os-release 2>/dev/null; then
log_warn "This installer is designed for Ubuntu. Proceeding anyway..."
fi
log_info "=========================================="
log_info "AtlasOS - Calypso Appliance Installer"
log_info "Version: ${CALYPSO_VERSION}"
log_info "=========================================="
log_info ""
# Source helper scripts
source "$INSTALLER_DIR/scripts/helpers.sh"
source "$INSTALLER_DIR/scripts/filesystem.sh"
source "$INSTALLER_DIR/scripts/dependencies.sh"
source "$INSTALLER_DIR/scripts/components.sh"
source "$INSTALLER_DIR/scripts/application.sh"
source "$INSTALLER_DIR/scripts/database.sh"
source "$INSTALLER_DIR/scripts/services.sh"
source "$INSTALLER_DIR/scripts/configuration.sh"
source "$INSTALLER_DIR/scripts/configure-services.sh"
source "$INSTALLER_DIR/scripts/post-install.sh"
# Main installation function
main() {
log_step "Starting Calypso Appliance Installation"
# Pre-flight checks
log_step "Pre-flight Checks"
check_prerequisites
# Create filesystem structure
log_step "Creating Filesystem Structure"
create_filesystem_structure
# Install system dependencies
if [[ "$SKIP_DEPS" == "false" ]]; then
log_step "Installing System Dependencies"
install_system_dependencies
else
log_info "Skipping system dependencies installation"
fi
# Install components
if [[ "$SKIP_ZFS" == "false" ]]; then
log_step "Installing ZFS"
install_zfs || log_warn "ZFS installation failed or already installed"
fi
if [[ "$SKIP_SCST" == "false" ]]; then
log_step "Installing SCST"
install_scst || log_warn "SCST installation failed or already installed"
fi
if [[ "$SKIP_MHVTL" == "false" ]]; then
log_step "Installing MHVTL"
install_mhvtl || log_warn "MHVTL installation failed or already installed"
fi
if [[ "$SKIP_BACULA" == "false" ]]; then
log_step "Installing Bacula (Optional)"
install_bacula || log_warn "Bacula installation skipped or failed"
fi
# File sharing services are installed in dependencies step
# ClamAV is installed in dependencies step
# Build and install application
if [[ "$CONFIG_ONLY" == "false" ]]; then
log_step "Building and Installing Application"
build_and_install_application
else
log_info "Skipping application build (config-only mode)"
fi
# Setup database
log_step "Setting Up Database"
setup_database
# Setup configuration
log_step "Setting Up Configuration"
setup_configuration
# Install systemd services
log_step "Installing Systemd Services"
install_systemd_services
# Configure file sharing and antivirus services
log_step "Configuring File Sharing and Antivirus Services"
configure_all_services
# Final verification
log_step "Verifying Installation"
verify_installation
# Post-installation setup
log_step "Post-Installation Setup"
post_install_setup
# Print summary
print_installation_summary
}
# Run main installation
main
log_info ""
log_info "=========================================="
log_info "Installation Complete!"
log_info "=========================================="
log_info ""
log_info "Next steps:"
log_info "1. Configure /etc/calypso/config.yaml"
log_info "2. Set environment variables in /etc/calypso/secrets.env"
log_info "3. Start services: sudo systemctl start calypso-api"
log_info "4. Access web UI: http://<server-ip>:3000"
log_info "5. Login with default admin credentials (check above)"
log_info ""

View File

@@ -0,0 +1,213 @@
-- AtlasOS - Calypso
-- Initial Database Schema
-- Version: 1.0
-- Users table
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(255),
is_active BOOLEAN NOT NULL DEFAULT true,
is_system BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
last_login_at TIMESTAMP
);
-- Roles table
CREATE TABLE IF NOT EXISTS roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
is_system BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Permissions table
CREATE TABLE IF NOT EXISTS permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
resource VARCHAR(100) NOT NULL,
action VARCHAR(100) NOT NULL,
description TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- User roles junction table
CREATE TABLE IF NOT EXISTS user_roles (
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
assigned_at TIMESTAMP NOT NULL DEFAULT NOW(),
assigned_by UUID REFERENCES users(id),
PRIMARY KEY (user_id, role_id)
);
-- Role permissions junction table
CREATE TABLE IF NOT EXISTS role_permissions (
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
permission_id UUID NOT NULL REFERENCES permissions(id) ON DELETE CASCADE,
granted_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY (role_id, permission_id)
);
-- Sessions table
CREATE TABLE IF NOT EXISTS sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash VARCHAR(255) NOT NULL UNIQUE,
ip_address INET,
user_agent TEXT,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
last_activity_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Audit log table
CREATE TABLE IF NOT EXISTS audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
username VARCHAR(255),
action VARCHAR(100) NOT NULL,
resource_type VARCHAR(100) NOT NULL,
resource_id VARCHAR(255),
method VARCHAR(10),
path TEXT,
ip_address INET,
user_agent TEXT,
request_body JSONB,
response_status INTEGER,
error_message TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Tasks table (for async operations)
CREATE TABLE IF NOT EXISTS tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type VARCHAR(100) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
progress INTEGER NOT NULL DEFAULT 0,
message TEXT,
error_message TEXT,
created_by UUID REFERENCES users(id),
started_at TIMESTAMP,
completed_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
metadata JSONB
);
-- Alerts table
CREATE TABLE IF NOT EXISTS alerts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
severity VARCHAR(20) NOT NULL,
source VARCHAR(100) NOT NULL,
title VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
resource_type VARCHAR(100),
resource_id VARCHAR(255),
is_acknowledged BOOLEAN NOT NULL DEFAULT false,
acknowledged_by UUID REFERENCES users(id),
acknowledged_at TIMESTAMP,
resolved_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
metadata JSONB
);
-- System configuration table
CREATE TABLE IF NOT EXISTS system_config (
key VARCHAR(255) PRIMARY KEY,
value TEXT NOT NULL,
description TEXT,
is_encrypted BOOLEAN NOT NULL DEFAULT false,
updated_by UUID REFERENCES users(id),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active);
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(token_hash);
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log(created_at);
CREATE INDEX IF NOT EXISTS idx_audit_log_resource ON audit_log(resource_type, resource_id);
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
CREATE INDEX IF NOT EXISTS idx_tasks_type ON tasks(type);
CREATE INDEX IF NOT EXISTS idx_tasks_created_by ON tasks(created_by);
CREATE INDEX IF NOT EXISTS idx_alerts_severity ON alerts(severity);
CREATE INDEX IF NOT EXISTS idx_alerts_acknowledged ON alerts(is_acknowledged);
CREATE INDEX IF NOT EXISTS idx_alerts_created_at ON alerts(created_at);
-- Insert default system roles
INSERT INTO roles (name, description, is_system) VALUES
('admin', 'Full system access and configuration', true),
('operator', 'Day-to-day operations and monitoring', true),
('readonly', 'Read-only access for monitoring and reporting', true)
ON CONFLICT (name) DO NOTHING;
-- Insert default permissions
INSERT INTO permissions (name, resource, action, description) VALUES
-- System permissions
('system:read', 'system', 'read', 'View system information'),
('system:write', 'system', 'write', 'Modify system configuration'),
('system:manage', 'system', 'manage', 'Full system management'),
-- Storage permissions
('storage:read', 'storage', 'read', 'View storage information'),
('storage:write', 'storage', 'write', 'Modify storage configuration'),
('storage:manage', 'storage', 'manage', 'Full storage management'),
-- Tape permissions
('tape:read', 'tape', 'read', 'View tape library information'),
('tape:write', 'tape', 'write', 'Perform tape operations'),
('tape:manage', 'tape', 'manage', 'Full tape management'),
-- iSCSI permissions
('iscsi:read', 'iscsi', 'read', 'View iSCSI configuration'),
('iscsi:write', 'iscsi', 'write', 'Modify iSCSI configuration'),
('iscsi:manage', 'iscsi', 'manage', 'Full iSCSI management'),
-- IAM permissions
('iam:read', 'iam', 'read', 'View users and roles'),
('iam:write', 'iam', 'write', 'Modify users and roles'),
('iam:manage', 'iam', 'manage', 'Full IAM management'),
-- Audit permissions
('audit:read', 'audit', 'read', 'View audit logs'),
-- Monitoring permissions
('monitoring:read', 'monitoring', 'read', 'View monitoring data'),
('monitoring:write', 'monitoring', 'write', 'Acknowledge alerts')
ON CONFLICT (name) DO NOTHING;
-- Assign permissions to roles
-- Admin gets all permissions
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r, permissions p
WHERE r.name = 'admin'
ON CONFLICT DO NOTHING;
-- Operator gets read and write (but not manage) for most resources
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r, permissions p
WHERE r.name = 'operator'
AND p.action IN ('read', 'write')
AND p.resource IN ('storage', 'tape', 'iscsi', 'monitoring')
ON CONFLICT DO NOTHING;
-- ReadOnly gets only read permissions
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r, permissions p
WHERE r.name = 'readonly'
AND p.action = 'read'
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,227 @@
-- AtlasOS - Calypso
-- Storage and Tape Component Schema
-- Version: 2.0
-- ZFS pools table
CREATE TABLE IF NOT EXISTS zfs_pools (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
raid_level VARCHAR(50) NOT NULL, -- stripe, mirror, raidz, raidz2, raidz3
disks TEXT[] NOT NULL, -- array of device paths
size_bytes BIGINT NOT NULL,
used_bytes BIGINT NOT NULL DEFAULT 0,
compression VARCHAR(50) NOT NULL DEFAULT 'lz4', -- off, lz4, zstd, gzip
deduplication BOOLEAN NOT NULL DEFAULT false,
auto_expand BOOLEAN NOT NULL DEFAULT false,
scrub_interval INTEGER NOT NULL DEFAULT 30, -- days
is_active BOOLEAN NOT NULL DEFAULT true,
health_status VARCHAR(50) NOT NULL DEFAULT 'online', -- online, degraded, faulted, offline
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id)
);
-- Disk repositories table
CREATE TABLE IF NOT EXISTS disk_repositories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
volume_group VARCHAR(255) NOT NULL,
logical_volume VARCHAR(255) NOT NULL,
size_bytes BIGINT NOT NULL,
used_bytes BIGINT NOT NULL DEFAULT 0,
filesystem_type VARCHAR(50),
mount_point TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
warning_threshold_percent INTEGER NOT NULL DEFAULT 80,
critical_threshold_percent INTEGER NOT NULL DEFAULT 90,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id)
);
-- Physical disks table
CREATE TABLE IF NOT EXISTS physical_disks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
device_path VARCHAR(255) NOT NULL UNIQUE,
vendor VARCHAR(255),
model VARCHAR(255),
serial_number VARCHAR(255),
size_bytes BIGINT NOT NULL,
sector_size INTEGER,
is_ssd BOOLEAN NOT NULL DEFAULT false,
health_status VARCHAR(50) NOT NULL DEFAULT 'unknown',
health_details JSONB,
is_used BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Volume groups table
CREATE TABLE IF NOT EXISTS volume_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
size_bytes BIGINT NOT NULL,
free_bytes BIGINT NOT NULL,
physical_volumes TEXT[],
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- SCST iSCSI targets table
CREATE TABLE IF NOT EXISTS scst_targets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
iqn VARCHAR(512) NOT NULL UNIQUE,
target_type VARCHAR(50) NOT NULL, -- 'disk', 'vtl', 'physical_tape'
name VARCHAR(255) NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
single_initiator_only BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id)
);
-- SCST LUN mappings table
CREATE TABLE IF NOT EXISTS scst_luns (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
target_id UUID NOT NULL REFERENCES scst_targets(id) ON DELETE CASCADE,
lun_number INTEGER NOT NULL,
device_name VARCHAR(255) NOT NULL,
device_path VARCHAR(512) NOT NULL,
handler_type VARCHAR(50) NOT NULL, -- 'vdisk', 'sg', 'tape'
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(target_id, lun_number)
);
-- SCST initiator groups table
CREATE TABLE IF NOT EXISTS scst_initiator_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
target_id UUID NOT NULL REFERENCES scst_targets(id) ON DELETE CASCADE,
group_name VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(target_id, group_name)
);
-- SCST initiators table
CREATE TABLE IF NOT EXISTS scst_initiators (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_id UUID NOT NULL REFERENCES scst_initiator_groups(id) ON DELETE CASCADE,
iqn VARCHAR(512) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(group_id, iqn)
);
-- Physical tape libraries table
CREATE TABLE IF NOT EXISTS physical_tape_libraries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
serial_number VARCHAR(255),
vendor VARCHAR(255),
model VARCHAR(255),
changer_device_path VARCHAR(512),
changer_stable_path VARCHAR(512),
slot_count INTEGER,
drive_count INTEGER,
is_active BOOLEAN NOT NULL DEFAULT true,
discovered_at TIMESTAMP NOT NULL DEFAULT NOW(),
last_inventory_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Physical tape drives table
CREATE TABLE IF NOT EXISTS physical_tape_drives (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
library_id UUID NOT NULL REFERENCES physical_tape_libraries(id) ON DELETE CASCADE,
drive_number INTEGER NOT NULL,
device_path VARCHAR(512),
stable_path VARCHAR(512),
vendor VARCHAR(255),
model VARCHAR(255),
serial_number VARCHAR(255),
drive_type VARCHAR(50), -- 'LTO-8', 'LTO-9', etc.
status VARCHAR(50) NOT NULL DEFAULT 'unknown', -- 'idle', 'loading', 'ready', 'error'
current_tape_barcode VARCHAR(255),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(library_id, drive_number)
);
-- Physical tape slots table
CREATE TABLE IF NOT EXISTS physical_tape_slots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
library_id UUID NOT NULL REFERENCES physical_tape_libraries(id) ON DELETE CASCADE,
slot_number INTEGER NOT NULL,
barcode VARCHAR(255),
tape_present BOOLEAN NOT NULL DEFAULT false,
tape_type VARCHAR(50),
last_updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(library_id, slot_number)
);
-- Virtual tape libraries table
CREATE TABLE IF NOT EXISTS virtual_tape_libraries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
mhvtl_library_id INTEGER,
backing_store_path TEXT NOT NULL,
slot_count INTEGER NOT NULL DEFAULT 10,
drive_count INTEGER NOT NULL DEFAULT 2,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id)
);
-- Virtual tape drives table
CREATE TABLE IF NOT EXISTS virtual_tape_drives (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
library_id UUID NOT NULL REFERENCES virtual_tape_libraries(id) ON DELETE CASCADE,
drive_number INTEGER NOT NULL,
device_path VARCHAR(512),
stable_path VARCHAR(512),
status VARCHAR(50) NOT NULL DEFAULT 'idle',
current_tape_id UUID,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(library_id, drive_number)
);
-- Virtual tapes table
CREATE TABLE IF NOT EXISTS virtual_tapes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
library_id UUID NOT NULL REFERENCES virtual_tape_libraries(id) ON DELETE CASCADE,
barcode VARCHAR(255) NOT NULL,
slot_number INTEGER,
image_file_path TEXT NOT NULL,
size_bytes BIGINT NOT NULL DEFAULT 0,
used_bytes BIGINT NOT NULL DEFAULT 0,
tape_type VARCHAR(50) NOT NULL DEFAULT 'LTO-8',
status VARCHAR(50) NOT NULL DEFAULT 'idle', -- 'idle', 'in_drive', 'exported'
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(library_id, barcode)
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_disk_repositories_name ON disk_repositories(name);
CREATE INDEX IF NOT EXISTS idx_disk_repositories_active ON disk_repositories(is_active);
CREATE INDEX IF NOT EXISTS idx_physical_disks_device_path ON physical_disks(device_path);
CREATE INDEX IF NOT EXISTS idx_scst_targets_iqn ON scst_targets(iqn);
CREATE INDEX IF NOT EXISTS idx_scst_targets_type ON scst_targets(target_type);
CREATE INDEX IF NOT EXISTS idx_scst_luns_target_id ON scst_luns(target_id);
CREATE INDEX IF NOT EXISTS idx_scst_initiators_group_id ON scst_initiators(group_id);
CREATE INDEX IF NOT EXISTS idx_physical_tape_libraries_name ON physical_tape_libraries(name);
CREATE INDEX IF NOT EXISTS idx_physical_tape_drives_library_id ON physical_tape_drives(library_id);
CREATE INDEX IF NOT EXISTS idx_physical_tape_slots_library_id ON physical_tape_slots(library_id);
CREATE INDEX IF NOT EXISTS idx_virtual_tape_libraries_name ON virtual_tape_libraries(name);
CREATE INDEX IF NOT EXISTS idx_virtual_tape_drives_library_id ON virtual_tape_drives(library_id);
CREATE INDEX IF NOT EXISTS idx_virtual_tapes_library_id ON virtual_tapes(library_id);
CREATE INDEX IF NOT EXISTS idx_virtual_tapes_barcode ON virtual_tapes(barcode);

View File

@@ -0,0 +1,22 @@
-- Migration: Object Storage Configuration
-- Description: Creates table for storing MinIO object storage configuration
-- Date: 2026-01-09
CREATE TABLE IF NOT EXISTS object_storage_config (
id SERIAL PRIMARY KEY,
dataset_path VARCHAR(255) NOT NULL UNIQUE,
mount_point VARCHAR(512) NOT NULL,
pool_name VARCHAR(255) NOT NULL,
dataset_name VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_object_storage_config_pool_name ON object_storage_config(pool_name);
CREATE INDEX IF NOT EXISTS idx_object_storage_config_updated_at ON object_storage_config(updated_at);
COMMENT ON TABLE object_storage_config IS 'Stores MinIO object storage configuration, linking to ZFS datasets';
COMMENT ON COLUMN object_storage_config.dataset_path IS 'Full ZFS dataset path (e.g., pool/dataset)';
COMMENT ON COLUMN object_storage_config.mount_point IS 'Mount point path for the dataset';
COMMENT ON COLUMN object_storage_config.pool_name IS 'ZFS pool name';
COMMENT ON COLUMN object_storage_config.dataset_name IS 'ZFS dataset name';

View File

@@ -0,0 +1,174 @@
-- AtlasOS - Calypso
-- Performance Optimization: Database Indexes
-- Version: 3.0
-- Description: Adds indexes for frequently queried columns to improve query performance
-- ============================================================================
-- Authentication & Authorization Indexes
-- ============================================================================
-- Users table indexes
-- Username is frequently queried during login
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
-- Email lookups
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
-- Active user lookups
CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active) WHERE is_active = true;
-- Sessions table indexes
-- Token hash lookups are very frequent (every authenticated request)
CREATE INDEX IF NOT EXISTS idx_sessions_token_hash ON sessions(token_hash);
-- User session lookups
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
-- Expired session cleanup (index on expires_at for efficient cleanup queries)
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
-- User roles junction table
-- Lookup roles for a user (frequent during permission checks)
CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id);
-- Lookup users with a role
CREATE INDEX IF NOT EXISTS idx_user_roles_role_id ON user_roles(role_id);
-- Role permissions junction table
-- Lookup permissions for a role
CREATE INDEX IF NOT EXISTS idx_role_permissions_role_id ON role_permissions(role_id);
-- Lookup roles with a permission
CREATE INDEX IF NOT EXISTS idx_role_permissions_permission_id ON role_permissions(permission_id);
-- ============================================================================
-- Audit & Monitoring Indexes
-- ============================================================================
-- Audit log indexes
-- Time-based queries (most common audit log access pattern)
CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log(created_at DESC);
-- User activity queries
CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id);
-- Resource-based queries
CREATE INDEX IF NOT EXISTS idx_audit_log_resource ON audit_log(resource_type, resource_id);
-- Alerts table indexes
-- Time-based ordering (default ordering in ListAlerts)
CREATE INDEX IF NOT EXISTS idx_alerts_created_at ON alerts(created_at DESC);
-- Severity filtering
CREATE INDEX IF NOT EXISTS idx_alerts_severity ON alerts(severity);
-- Source filtering
CREATE INDEX IF NOT EXISTS idx_alerts_source ON alerts(source);
-- Acknowledgment status
CREATE INDEX IF NOT EXISTS idx_alerts_acknowledged ON alerts(is_acknowledged) WHERE is_acknowledged = false;
-- Resource-based queries
CREATE INDEX IF NOT EXISTS idx_alerts_resource ON alerts(resource_type, resource_id);
-- Composite index for common filter combinations
CREATE INDEX IF NOT EXISTS idx_alerts_severity_acknowledged ON alerts(severity, is_acknowledged, created_at DESC);
-- ============================================================================
-- Task Management Indexes
-- ============================================================================
-- Tasks table indexes
-- Status filtering (very common in task queries)
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
-- Created by user
CREATE INDEX IF NOT EXISTS idx_tasks_created_by ON tasks(created_by);
-- Time-based queries
CREATE INDEX IF NOT EXISTS idx_tasks_created_at ON tasks(created_at DESC);
-- Status + time composite (common query pattern)
CREATE INDEX IF NOT EXISTS idx_tasks_status_created_at ON tasks(status, created_at DESC);
-- Failed tasks lookup (for alerting)
CREATE INDEX IF NOT EXISTS idx_tasks_failed_recent ON tasks(status, created_at DESC) WHERE status = 'failed';
-- ============================================================================
-- Storage Indexes
-- ============================================================================
-- Disk repositories indexes
-- Active repository lookups
CREATE INDEX IF NOT EXISTS idx_disk_repositories_is_active ON disk_repositories(is_active) WHERE is_active = true;
-- Name lookups
CREATE INDEX IF NOT EXISTS idx_disk_repositories_name ON disk_repositories(name);
-- Volume group lookups
CREATE INDEX IF NOT EXISTS idx_disk_repositories_vg ON disk_repositories(volume_group);
-- Physical disks indexes
-- Device path lookups (for sync operations)
CREATE INDEX IF NOT EXISTS idx_physical_disks_device_path ON physical_disks(device_path);
-- ============================================================================
-- SCST Indexes
-- ============================================================================
-- SCST targets indexes
-- IQN lookups (frequent during target operations)
CREATE INDEX IF NOT EXISTS idx_scst_targets_iqn ON scst_targets(iqn);
-- Active target lookups
CREATE INDEX IF NOT EXISTS idx_scst_targets_is_active ON scst_targets(is_active) WHERE is_active = true;
-- SCST LUNs indexes
-- Target + LUN lookups (very frequent)
CREATE INDEX IF NOT EXISTS idx_scst_luns_target_lun ON scst_luns(target_id, lun_number);
-- Device path lookups
CREATE INDEX IF NOT EXISTS idx_scst_luns_device_path ON scst_luns(device_path);
-- SCST initiator groups indexes
-- Target + group name lookups
CREATE INDEX IF NOT EXISTS idx_scst_initiator_groups_target ON scst_initiator_groups(target_id);
-- Group name lookups
CREATE INDEX IF NOT EXISTS idx_scst_initiator_groups_name ON scst_initiator_groups(group_name);
-- SCST initiators indexes
-- Group + IQN lookups
CREATE INDEX IF NOT EXISTS idx_scst_initiators_group_iqn ON scst_initiators(group_id, iqn);
-- Active initiator lookups
CREATE INDEX IF NOT EXISTS idx_scst_initiators_is_active ON scst_initiators(is_active) WHERE is_active = true;
-- ============================================================================
-- Tape Library Indexes
-- ============================================================================
-- Physical tape libraries indexes
-- Serial number lookups (for discovery)
CREATE INDEX IF NOT EXISTS idx_physical_tape_libraries_serial ON physical_tape_libraries(serial_number);
-- Active library lookups
CREATE INDEX IF NOT EXISTS idx_physical_tape_libraries_is_active ON physical_tape_libraries(is_active) WHERE is_active = true;
-- Physical tape drives indexes
-- Library + drive number lookups (very frequent)
CREATE INDEX IF NOT EXISTS idx_physical_tape_drives_library_drive ON physical_tape_drives(library_id, drive_number);
-- Status filtering
CREATE INDEX IF NOT EXISTS idx_physical_tape_drives_status ON physical_tape_drives(status);
-- Physical tape slots indexes
-- Library + slot number lookups
CREATE INDEX IF NOT EXISTS idx_physical_tape_slots_library_slot ON physical_tape_slots(library_id, slot_number);
-- Barcode lookups
CREATE INDEX IF NOT EXISTS idx_physical_tape_slots_barcode ON physical_tape_slots(barcode) WHERE barcode IS NOT NULL;
-- Virtual tape libraries indexes
-- MHVTL library ID lookups
CREATE INDEX IF NOT EXISTS idx_virtual_tape_libraries_mhvtl_id ON virtual_tape_libraries(mhvtl_library_id);
-- Active library lookups
CREATE INDEX IF NOT EXISTS idx_virtual_tape_libraries_is_active ON virtual_tape_libraries(is_active) WHERE is_active = true;
-- Virtual tape drives indexes
-- Library + drive number lookups (very frequent)
CREATE INDEX IF NOT EXISTS idx_virtual_tape_drives_library_drive ON virtual_tape_drives(library_id, drive_number);
-- Status filtering
CREATE INDEX IF NOT EXISTS idx_virtual_tape_drives_status ON virtual_tape_drives(status);
-- Current tape lookups
CREATE INDEX IF NOT EXISTS idx_virtual_tape_drives_current_tape ON virtual_tape_drives(current_tape_id) WHERE current_tape_id IS NOT NULL;
-- Virtual tapes indexes
-- Library + slot number lookups
CREATE INDEX IF NOT EXISTS idx_virtual_tapes_library_slot ON virtual_tapes(library_id, slot_number);
-- Barcode lookups
CREATE INDEX IF NOT EXISTS idx_virtual_tapes_barcode ON virtual_tapes(barcode) WHERE barcode IS NOT NULL;
-- Status filtering
CREATE INDEX IF NOT EXISTS idx_virtual_tapes_status ON virtual_tapes(status);
-- ============================================================================
-- Statistics Update
-- ============================================================================
-- Update table statistics for query planner
ANALYZE;

View File

@@ -0,0 +1,31 @@
-- AtlasOS - Calypso
-- Add ZFS Pools Table
-- Version: 4.0
-- Note: This migration adds the zfs_pools table that was added to migration 002
-- but may not have been applied if migration 002 was run before the table was added
-- ZFS pools table
CREATE TABLE IF NOT EXISTS zfs_pools (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
raid_level VARCHAR(50) NOT NULL, -- stripe, mirror, raidz, raidz2, raidz3
disks TEXT[] NOT NULL, -- array of device paths
size_bytes BIGINT NOT NULL,
used_bytes BIGINT NOT NULL DEFAULT 0,
compression VARCHAR(50) NOT NULL DEFAULT 'lz4', -- off, lz4, zstd, gzip
deduplication BOOLEAN NOT NULL DEFAULT false,
auto_expand BOOLEAN NOT NULL DEFAULT false,
scrub_interval INTEGER NOT NULL DEFAULT 30, -- days
is_active BOOLEAN NOT NULL DEFAULT true,
health_status VARCHAR(50) NOT NULL DEFAULT 'online', -- online, degraded, faulted, offline
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id)
);
-- Create index on name for faster lookups
CREATE INDEX IF NOT EXISTS idx_zfs_pools_name ON zfs_pools(name);
CREATE INDEX IF NOT EXISTS idx_zfs_pools_created_by ON zfs_pools(created_by);
CREATE INDEX IF NOT EXISTS idx_zfs_pools_health_status ON zfs_pools(health_status);

View File

@@ -0,0 +1,35 @@
-- AtlasOS - Calypso
-- Add ZFS Datasets Table
-- Version: 5.0
-- Description: Stores ZFS dataset metadata in database for faster queries and consistency
-- ZFS datasets table
CREATE TABLE IF NOT EXISTS zfs_datasets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(512) NOT NULL UNIQUE, -- Full dataset name (e.g., pool/dataset)
pool_id UUID NOT NULL REFERENCES zfs_pools(id) ON DELETE CASCADE,
pool_name VARCHAR(255) NOT NULL, -- Denormalized for faster queries
type VARCHAR(50) NOT NULL, -- filesystem, volume, snapshot
mount_point TEXT, -- Mount point path (null for volumes)
used_bytes BIGINT NOT NULL DEFAULT 0,
available_bytes BIGINT NOT NULL DEFAULT 0,
referenced_bytes BIGINT NOT NULL DEFAULT 0,
compression VARCHAR(50) NOT NULL DEFAULT 'lz4', -- off, lz4, zstd, gzip
deduplication VARCHAR(50) NOT NULL DEFAULT 'off', -- off, on, verify
quota BIGINT DEFAULT -1, -- -1 for unlimited, bytes otherwise
reservation BIGINT NOT NULL DEFAULT 0, -- Reserved space in bytes
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id)
);
-- Create indexes for faster lookups
CREATE INDEX IF NOT EXISTS idx_zfs_datasets_pool_id ON zfs_datasets(pool_id);
CREATE INDEX IF NOT EXISTS idx_zfs_datasets_pool_name ON zfs_datasets(pool_name);
CREATE INDEX IF NOT EXISTS idx_zfs_datasets_name ON zfs_datasets(name);
CREATE INDEX IF NOT EXISTS idx_zfs_datasets_type ON zfs_datasets(type);
CREATE INDEX IF NOT EXISTS idx_zfs_datasets_created_by ON zfs_datasets(created_by);
-- Composite index for common queries (list datasets by pool)
CREATE INDEX IF NOT EXISTS idx_zfs_datasets_pool_type ON zfs_datasets(pool_id, type);

View File

@@ -0,0 +1,50 @@
-- AtlasOS - Calypso
-- Add ZFS Shares and iSCSI Export Tables
-- Version: 6.0
-- Description: Separate tables for filesystem shares (NFS/SMB) and volume iSCSI exports
-- ZFS Filesystem Shares Table (for NFS/SMB)
CREATE TABLE IF NOT EXISTS zfs_shares (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dataset_id UUID NOT NULL REFERENCES zfs_datasets(id) ON DELETE CASCADE,
share_type VARCHAR(50) NOT NULL, -- 'nfs', 'smb', 'both'
nfs_enabled BOOLEAN NOT NULL DEFAULT false,
nfs_options TEXT, -- e.g., "rw,sync,no_subtree_check"
nfs_clients TEXT[], -- Allowed client IPs/networks
smb_enabled BOOLEAN NOT NULL DEFAULT false,
smb_share_name VARCHAR(255), -- SMB share name
smb_path TEXT, -- SMB share path (usually same as mount_point)
smb_comment TEXT,
smb_guest_ok BOOLEAN NOT NULL DEFAULT false,
smb_read_only BOOLEAN NOT NULL DEFAULT false,
smb_browseable BOOLEAN NOT NULL DEFAULT true,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id),
UNIQUE(dataset_id) -- One share config per dataset
);
-- ZFS Volume iSCSI Exports Table
CREATE TABLE IF NOT EXISTS zfs_iscsi_exports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dataset_id UUID NOT NULL REFERENCES zfs_datasets(id) ON DELETE CASCADE,
target_id UUID REFERENCES scst_targets(id) ON DELETE SET NULL, -- Link to SCST target
lun_number INTEGER, -- LUN number in the target
device_path TEXT, -- /dev/zvol/pool/volume path
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users(id),
UNIQUE(dataset_id) -- One iSCSI export per volume
);
-- Create indexes
CREATE INDEX IF NOT EXISTS idx_zfs_shares_dataset_id ON zfs_shares(dataset_id);
CREATE INDEX IF NOT EXISTS idx_zfs_shares_type ON zfs_shares(share_type);
CREATE INDEX IF NOT EXISTS idx_zfs_shares_active ON zfs_shares(is_active);
CREATE INDEX IF NOT EXISTS idx_zfs_iscsi_exports_dataset_id ON zfs_iscsi_exports(dataset_id);
CREATE INDEX IF NOT EXISTS idx_zfs_iscsi_exports_target_id ON zfs_iscsi_exports(target_id);
CREATE INDEX IF NOT EXISTS idx_zfs_iscsi_exports_active ON zfs_iscsi_exports(is_active);

View File

@@ -0,0 +1,3 @@
-- Add vendor column to virtual_tape_libraries table
ALTER TABLE virtual_tape_libraries ADD COLUMN IF NOT EXISTS vendor VARCHAR(255);

View File

@@ -0,0 +1,45 @@
-- Add user groups feature
-- Groups table
CREATE TABLE IF NOT EXISTS groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
is_system BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- User groups junction table
CREATE TABLE IF NOT EXISTS user_groups (
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
group_id UUID NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
assigned_at TIMESTAMP NOT NULL DEFAULT NOW(),
assigned_by UUID REFERENCES users(id),
PRIMARY KEY (user_id, group_id)
);
-- Group roles junction table (groups can have roles)
CREATE TABLE IF NOT EXISTS group_roles (
group_id UUID NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
granted_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY (group_id, role_id)
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_groups_name ON groups(name);
CREATE INDEX IF NOT EXISTS idx_user_groups_user_id ON user_groups(user_id);
CREATE INDEX IF NOT EXISTS idx_user_groups_group_id ON user_groups(group_id);
CREATE INDEX IF NOT EXISTS idx_group_roles_group_id ON group_roles(group_id);
CREATE INDEX IF NOT EXISTS idx_group_roles_role_id ON group_roles(role_id);
-- Insert default system groups
INSERT INTO groups (name, description, is_system) VALUES
('wheel', 'System administrators group', true),
('operators', 'System operators group', true),
('backup', 'Backup operators group', true),
('auditors', 'Auditors group', true),
('storage_admins', 'Storage administrators group', true),
('services', 'Service accounts group', true)
ON CONFLICT (name) DO NOTHING;

View File

@@ -0,0 +1,34 @@
-- AtlasOS - Calypso
-- Backup Jobs Schema
-- Version: 9.0
-- Backup jobs table
CREATE TABLE IF NOT EXISTS backup_jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
job_id INTEGER NOT NULL UNIQUE, -- Bareos job ID
job_name VARCHAR(255) NOT NULL,
client_name VARCHAR(255) NOT NULL,
job_type VARCHAR(50) NOT NULL, -- 'Backup', 'Restore', 'Verify', 'Copy', 'Migrate'
job_level VARCHAR(50) NOT NULL, -- 'Full', 'Incremental', 'Differential', 'Since'
status VARCHAR(50) NOT NULL, -- 'Running', 'Completed', 'Failed', 'Canceled', 'Waiting'
bytes_written BIGINT NOT NULL DEFAULT 0,
files_written INTEGER NOT NULL DEFAULT 0,
duration_seconds INTEGER,
started_at TIMESTAMP,
ended_at TIMESTAMP,
error_message TEXT,
storage_name VARCHAR(255),
pool_name VARCHAR(255),
volume_name VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_backup_jobs_job_id ON backup_jobs(job_id);
CREATE INDEX IF NOT EXISTS idx_backup_jobs_job_name ON backup_jobs(job_name);
CREATE INDEX IF NOT EXISTS idx_backup_jobs_client_name ON backup_jobs(client_name);
CREATE INDEX IF NOT EXISTS idx_backup_jobs_status ON backup_jobs(status);
CREATE INDEX IF NOT EXISTS idx_backup_jobs_started_at ON backup_jobs(started_at DESC);
CREATE INDEX IF NOT EXISTS idx_backup_jobs_job_type ON backup_jobs(job_type);

View File

@@ -0,0 +1,39 @@
-- AtlasOS - Calypso
-- Add Backup Permissions
-- Version: 10.0
-- Insert backup permissions
INSERT INTO permissions (name, resource, action, description) VALUES
('backup:read', 'backup', 'read', 'View backup jobs and history'),
('backup:write', 'backup', 'write', 'Create and manage backup jobs'),
('backup:manage', 'backup', 'manage', 'Full backup management')
ON CONFLICT (name) DO NOTHING;
-- Assign backup permissions to roles
-- Admin gets all backup permissions (explicitly assign since admin query in 001 only runs once)
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r, permissions p
WHERE r.name = 'admin'
AND p.resource = 'backup'
ON CONFLICT DO NOTHING;
-- Operator gets read and write permissions for backup
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r, permissions p
WHERE r.name = 'operator'
AND p.resource = 'backup'
AND p.action IN ('read', 'write')
ON CONFLICT DO NOTHING;
-- ReadOnly gets only read permission for backup
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r, permissions p
WHERE r.name = 'readonly'
AND p.resource = 'backup'
AND p.action = 'read'
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,209 @@
-- AtlasOS - Calypso
-- PostgreSQL Function to Sync Jobs from Bacula to Calypso
-- Version: 11.0
--
-- This function syncs jobs from Bacula database (Job table) to Calypso database (backup_jobs table)
-- Uses dblink extension to query Bacula database from Calypso database
--
-- Prerequisites:
-- 1. dblink extension must be installed: CREATE EXTENSION IF NOT EXISTS dblink;
-- 2. User must have access to both databases
-- 3. Connection parameters must be configured in the function
-- Create function to sync jobs from Bacula to Calypso
CREATE OR REPLACE FUNCTION sync_bacula_jobs(
bacula_db_name TEXT DEFAULT 'bacula',
bacula_host TEXT DEFAULT 'localhost',
bacula_port INTEGER DEFAULT 5432,
bacula_user TEXT DEFAULT 'calypso',
bacula_password TEXT DEFAULT ''
)
RETURNS TABLE(
jobs_synced INTEGER,
jobs_inserted INTEGER,
jobs_updated INTEGER,
errors INTEGER
) AS $$
DECLARE
conn_str TEXT;
jobs_count INTEGER := 0;
inserted_count INTEGER := 0;
updated_count INTEGER := 0;
error_count INTEGER := 0;
job_record RECORD;
BEGIN
-- Build dblink connection string
conn_str := format(
'dbname=%s host=%s port=%s user=%s password=%s',
bacula_db_name,
bacula_host,
bacula_port,
bacula_user,
bacula_password
);
-- Query jobs from Bacula database using dblink
FOR job_record IN
SELECT * FROM dblink(
conn_str,
$QUERY$
SELECT
j.JobId,
j.Name as job_name,
COALESCE(c.Name, 'unknown') as client_name,
CASE
WHEN j.Type = 'B' THEN 'Backup'
WHEN j.Type = 'R' THEN 'Restore'
WHEN j.Type = 'V' THEN 'Verify'
WHEN j.Type = 'C' THEN 'Copy'
WHEN j.Type = 'M' THEN 'Migrate'
ELSE 'Backup'
END as job_type,
CASE
WHEN j.Level = 'F' THEN 'Full'
WHEN j.Level = 'I' THEN 'Incremental'
WHEN j.Level = 'D' THEN 'Differential'
WHEN j.Level = 'S' THEN 'Since'
ELSE 'Full'
END as job_level,
CASE
WHEN j.JobStatus = 'T' THEN 'Running'
WHEN j.JobStatus = 'C' THEN 'Completed'
WHEN j.JobStatus = 'f' OR j.JobStatus = 'F' THEN 'Failed'
WHEN j.JobStatus = 'A' THEN 'Canceled'
WHEN j.JobStatus = 'W' THEN 'Waiting'
ELSE 'Waiting'
END as status,
COALESCE(j.JobBytes, 0) as bytes_written,
COALESCE(j.JobFiles, 0) as files_written,
j.StartTime as started_at,
j.EndTime as ended_at,
CASE
WHEN j.EndTime IS NOT NULL AND j.StartTime IS NOT NULL
THEN EXTRACT(EPOCH FROM (j.EndTime - j.StartTime))::INTEGER
ELSE NULL
END as duration_seconds
FROM Job j
LEFT JOIN Client c ON j.ClientId = c.ClientId
ORDER BY j.StartTime DESC
LIMIT 1000
$QUERY$
) AS t(
job_id INTEGER,
job_name TEXT,
client_name TEXT,
job_type TEXT,
job_level TEXT,
status TEXT,
bytes_written BIGINT,
files_written INTEGER,
started_at TIMESTAMP,
ended_at TIMESTAMP,
duration_seconds INTEGER
)
LOOP
BEGIN
-- Check if job already exists (before insert/update)
IF EXISTS (SELECT 1 FROM backup_jobs WHERE job_id = job_record.job_id) THEN
updated_count := updated_count + 1;
ELSE
inserted_count := inserted_count + 1;
END IF;
-- Upsert job to backup_jobs table
INSERT INTO backup_jobs (
job_id, job_name, client_name, job_type, job_level, status,
bytes_written, files_written, started_at, ended_at, duration_seconds,
updated_at
) VALUES (
job_record.job_id,
job_record.job_name,
job_record.client_name,
job_record.job_type,
job_record.job_level,
job_record.status,
job_record.bytes_written,
job_record.files_written,
job_record.started_at,
job_record.ended_at,
job_record.duration_seconds,
NOW()
)
ON CONFLICT (job_id) DO UPDATE SET
job_name = EXCLUDED.job_name,
client_name = EXCLUDED.client_name,
job_type = EXCLUDED.job_type,
job_level = EXCLUDED.job_level,
status = EXCLUDED.status,
bytes_written = EXCLUDED.bytes_written,
files_written = EXCLUDED.files_written,
started_at = EXCLUDED.started_at,
ended_at = EXCLUDED.ended_at,
duration_seconds = EXCLUDED.duration_seconds,
updated_at = NOW();
jobs_count := jobs_count + 1;
EXCEPTION
WHEN OTHERS THEN
error_count := error_count + 1;
-- Log error but continue with next job
RAISE WARNING 'Error syncing job %: %', job_record.job_id, SQLERRM;
END;
END LOOP;
-- Return summary
RETURN QUERY SELECT jobs_count, inserted_count, updated_count, error_count;
END;
$$ LANGUAGE plpgsql;
-- Create a simpler version that uses current database connection settings
-- This version assumes Bacula is on same host/port with same user
CREATE OR REPLACE FUNCTION sync_bacula_jobs_simple()
RETURNS TABLE(
jobs_synced INTEGER,
jobs_inserted INTEGER,
jobs_updated INTEGER,
errors INTEGER
) AS $$
DECLARE
current_user_name TEXT;
current_host TEXT;
current_port INTEGER;
current_db TEXT;
BEGIN
-- Get current connection info
SELECT
current_user,
COALESCE(inet_server_addr()::TEXT, 'localhost'),
COALESCE(inet_server_port(), 5432),
current_database()
INTO
current_user_name,
current_host,
current_port,
current_db;
-- Call main function with current connection settings
-- Note: password needs to be passed or configured in .pgpass
RETURN QUERY
SELECT * FROM sync_bacula_jobs(
'bacula', -- Try 'bacula' first
current_host,
current_port,
current_user_name,
'' -- Empty password - will use .pgpass or peer authentication
);
END;
$$ LANGUAGE plpgsql;
-- Grant execute permission to calypso user
GRANT EXECUTE ON FUNCTION sync_bacula_jobs(TEXT, TEXT, INTEGER, TEXT, TEXT) TO calypso;
GRANT EXECUTE ON FUNCTION sync_bacula_jobs_simple() TO calypso;
-- Create index if not exists (should already exist from migration 009)
CREATE INDEX IF NOT EXISTS idx_backup_jobs_job_id ON backup_jobs(job_id);
CREATE INDEX IF NOT EXISTS idx_backup_jobs_updated_at ON backup_jobs(updated_at);
COMMENT ON FUNCTION sync_bacula_jobs IS 'Syncs jobs from Bacula database to Calypso backup_jobs table using dblink';
COMMENT ON FUNCTION sync_bacula_jobs_simple IS 'Simplified version that uses current connection settings (requires .pgpass for password)';

View File

@@ -0,0 +1,209 @@
-- AtlasOS - Calypso
-- PostgreSQL Function to Sync Jobs from Bacula to Calypso
-- Version: 11.0
--
-- This function syncs jobs from Bacula database (Job table) to Calypso database (backup_jobs table)
-- Uses dblink extension to query Bacula database from Calypso database
--
-- Prerequisites:
-- 1. dblink extension must be installed: CREATE EXTENSION IF NOT EXISTS dblink;
-- 2. User must have access to both databases
-- 3. Connection parameters must be configured in the function
-- Create function to sync jobs from Bacula to Calypso
CREATE OR REPLACE FUNCTION sync_bacula_jobs(
bacula_db_name TEXT DEFAULT 'bacula',
bacula_host TEXT DEFAULT 'localhost',
bacula_port INTEGER DEFAULT 5432,
bacula_user TEXT DEFAULT 'calypso',
bacula_password TEXT DEFAULT ''
)
RETURNS TABLE(
jobs_synced INTEGER,
jobs_inserted INTEGER,
jobs_updated INTEGER,
errors INTEGER
) AS $$
DECLARE
conn_str TEXT;
jobs_count INTEGER := 0;
inserted_count INTEGER := 0;
updated_count INTEGER := 0;
error_count INTEGER := 0;
job_record RECORD;
BEGIN
-- Build dblink connection string
conn_str := format(
'dbname=%s host=%s port=%s user=%s password=%s',
bacula_db_name,
bacula_host,
bacula_port,
bacula_user,
bacula_password
);
-- Query jobs from Bacula database using dblink
FOR job_record IN
SELECT * FROM dblink(
conn_str,
$$
SELECT
j.JobId,
j.Name as job_name,
COALESCE(c.Name, 'unknown') as client_name,
CASE
WHEN j.Type = 'B' THEN 'Backup'
WHEN j.Type = 'R' THEN 'Restore'
WHEN j.Type = 'V' THEN 'Verify'
WHEN j.Type = 'C' THEN 'Copy'
WHEN j.Type = 'M' THEN 'Migrate'
ELSE 'Backup'
END as job_type,
CASE
WHEN j.Level = 'F' THEN 'Full'
WHEN j.Level = 'I' THEN 'Incremental'
WHEN j.Level = 'D' THEN 'Differential'
WHEN j.Level = 'S' THEN 'Since'
ELSE 'Full'
END as job_level,
CASE
WHEN j.JobStatus = 'T' THEN 'Running'
WHEN j.JobStatus = 'C' THEN 'Completed'
WHEN j.JobStatus = 'f' OR j.JobStatus = 'F' THEN 'Failed'
WHEN j.JobStatus = 'A' THEN 'Canceled'
WHEN j.JobStatus = 'W' THEN 'Waiting'
ELSE 'Waiting'
END as status,
COALESCE(j.JobBytes, 0) as bytes_written,
COALESCE(j.JobFiles, 0) as files_written,
j.StartTime as started_at,
j.EndTime as ended_at,
CASE
WHEN j.EndTime IS NOT NULL AND j.StartTime IS NOT NULL
THEN EXTRACT(EPOCH FROM (j.EndTime - j.StartTime))::INTEGER
ELSE NULL
END as duration_seconds
FROM Job j
LEFT JOIN Client c ON j.ClientId = c.ClientId
ORDER BY j.StartTime DESC
LIMIT 1000
$$
) AS t(
job_id INTEGER,
job_name TEXT,
client_name TEXT,
job_type TEXT,
job_level TEXT,
status TEXT,
bytes_written BIGINT,
files_written INTEGER,
started_at TIMESTAMP,
ended_at TIMESTAMP,
duration_seconds INTEGER
)
LOOP
BEGIN
-- Check if job already exists (before insert/update)
IF EXISTS (SELECT 1 FROM backup_jobs WHERE job_id = job_record.job_id) THEN
updated_count := updated_count + 1;
ELSE
inserted_count := inserted_count + 1;
END IF;
-- Upsert job to backup_jobs table
INSERT INTO backup_jobs (
job_id, job_name, client_name, job_type, job_level, status,
bytes_written, files_written, started_at, ended_at, duration_seconds,
updated_at
) VALUES (
job_record.job_id,
job_record.job_name,
job_record.client_name,
job_record.job_type,
job_record.job_level,
job_record.status,
job_record.bytes_written,
job_record.files_written,
job_record.started_at,
job_record.ended_at,
job_record.duration_seconds,
NOW()
)
ON CONFLICT (job_id) DO UPDATE SET
job_name = EXCLUDED.job_name,
client_name = EXCLUDED.client_name,
job_type = EXCLUDED.job_type,
job_level = EXCLUDED.job_level,
status = EXCLUDED.status,
bytes_written = EXCLUDED.bytes_written,
files_written = EXCLUDED.files_written,
started_at = EXCLUDED.started_at,
ended_at = EXCLUDED.ended_at,
duration_seconds = EXCLUDED.duration_seconds,
updated_at = NOW();
jobs_count := jobs_count + 1;
EXCEPTION
WHEN OTHERS THEN
error_count := error_count + 1;
-- Log error but continue with next job
RAISE WARNING 'Error syncing job %: %', job_record.job_id, SQLERRM;
END;
END LOOP;
-- Return summary
RETURN QUERY SELECT jobs_count, inserted_count, updated_count, error_count;
END;
$$ LANGUAGE plpgsql;
-- Create a simpler version that uses current database connection settings
-- This version assumes Bacula is on same host/port with same user
CREATE OR REPLACE FUNCTION sync_bacula_jobs_simple()
RETURNS TABLE(
jobs_synced INTEGER,
jobs_inserted INTEGER,
jobs_updated INTEGER,
errors INTEGER
) AS $$
DECLARE
current_user_name TEXT;
current_host TEXT;
current_port INTEGER;
current_db TEXT;
BEGIN
-- Get current connection info
SELECT
current_user,
COALESCE(inet_server_addr()::TEXT, 'localhost'),
COALESCE(inet_server_port(), 5432),
current_database()
INTO
current_user_name,
current_host,
current_port,
current_db;
-- Call main function with current connection settings
-- Note: password needs to be passed or configured in .pgpass
RETURN QUERY
SELECT * FROM sync_bacula_jobs(
'bacula', -- Try 'bacula' first
current_host,
current_port,
current_user_name,
'' -- Empty password - will use .pgpass or peer authentication
);
END;
$$ LANGUAGE plpgsql;
-- Grant execute permission to calypso user
GRANT EXECUTE ON FUNCTION sync_bacula_jobs(TEXT, TEXT, INTEGER, TEXT, TEXT) TO calypso;
GRANT EXECUTE ON FUNCTION sync_bacula_jobs_simple() TO calypso;
-- Create index if not exists (should already exist from migration 009)
CREATE INDEX IF NOT EXISTS idx_backup_jobs_job_id ON backup_jobs(job_id);
CREATE INDEX IF NOT EXISTS idx_backup_jobs_updated_at ON backup_jobs(updated_at);
COMMENT ON FUNCTION sync_bacula_jobs IS 'Syncs jobs from Bacula database to Calypso backup_jobs table using dblink';
COMMENT ON FUNCTION sync_bacula_jobs_simple IS 'Simplified version that uses current connection settings (requires .pgpass for password)';

View File

@@ -0,0 +1,23 @@
-- Snapshot schedules table for automated snapshot creation
CREATE TABLE IF NOT EXISTS snapshot_schedules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
dataset VARCHAR(255) NOT NULL,
snapshot_name_template VARCHAR(255) NOT NULL, -- e.g., "auto-%Y-%m-%d-%H%M" or "daily-backup"
schedule_type VARCHAR(50) NOT NULL, -- 'hourly', 'daily', 'weekly', 'monthly', 'cron'
schedule_config JSONB NOT NULL, -- For cron: {"cron": "0 0 * * *"}, for others: {"time": "00:00", "day": 1, etc.}
recursive BOOLEAN NOT NULL DEFAULT false,
enabled BOOLEAN NOT NULL DEFAULT true,
retention_count INTEGER, -- Keep last N snapshots (null = unlimited)
retention_days INTEGER, -- Keep snapshots for N days (null = unlimited)
last_run_at TIMESTAMP,
next_run_at TIMESTAMP,
created_by UUID REFERENCES users(id),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE(name)
);
CREATE INDEX IF NOT EXISTS idx_snapshot_schedules_enabled ON snapshot_schedules(enabled);
CREATE INDEX IF NOT EXISTS idx_snapshot_schedules_next_run ON snapshot_schedules(next_run_at);
CREATE INDEX IF NOT EXISTS idx_snapshot_schedules_dataset ON snapshot_schedules(dataset);

View File

@@ -0,0 +1,76 @@
-- ZFS Replication Tasks Table
-- Supports both outbound (sender) and inbound (receiver) replication
CREATE TABLE IF NOT EXISTS replication_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
direction VARCHAR(20) NOT NULL, -- 'outbound' (sender) or 'inbound' (receiver)
-- For outbound replication (sender)
source_dataset VARCHAR(512), -- Source dataset on this system (outbound) or remote system (inbound)
target_host VARCHAR(255), -- Target host IP or hostname (for outbound)
target_port INTEGER DEFAULT 22, -- SSH port (default 22, for outbound)
target_user VARCHAR(255) DEFAULT 'root', -- SSH user (for outbound)
target_dataset VARCHAR(512), -- Target dataset on remote system (for outbound)
target_ssh_key_path TEXT, -- Path to SSH private key (for outbound)
-- For inbound replication (receiver)
source_host VARCHAR(255), -- Source host IP or hostname (for inbound)
source_port INTEGER DEFAULT 22, -- SSH port (for inbound)
source_user VARCHAR(255) DEFAULT 'root', -- SSH user (for inbound)
local_dataset VARCHAR(512), -- Local dataset to receive snapshots (for inbound)
-- Common settings
schedule_type VARCHAR(50), -- 'manual', 'hourly', 'daily', 'weekly', 'monthly', 'cron'
schedule_config JSONB, -- Schedule configuration (similar to snapshot schedules)
compression VARCHAR(50) DEFAULT 'lz4', -- 'off', 'lz4', 'gzip', 'zstd'
encryption BOOLEAN DEFAULT false, -- Enable encryption during transfer
recursive BOOLEAN DEFAULT false, -- Replicate recursively
incremental BOOLEAN DEFAULT true, -- Use incremental replication
auto_snapshot BOOLEAN DEFAULT true, -- Auto-create snapshot before replication
-- Status and tracking
enabled BOOLEAN NOT NULL DEFAULT true,
status VARCHAR(50) DEFAULT 'idle', -- 'idle', 'running', 'failed', 'paused'
last_run_at TIMESTAMP,
last_run_status VARCHAR(50), -- 'success', 'failed', 'partial'
last_run_error TEXT,
next_run_at TIMESTAMP,
last_snapshot_sent VARCHAR(512), -- Last snapshot successfully sent (for outbound)
last_snapshot_received VARCHAR(512), -- Last snapshot successfully received (for inbound)
-- Statistics
total_runs INTEGER DEFAULT 0,
successful_runs INTEGER DEFAULT 0,
failed_runs INTEGER DEFAULT 0,
bytes_sent BIGINT DEFAULT 0, -- Total bytes sent (for outbound)
bytes_received BIGINT DEFAULT 0, -- Total bytes received (for inbound)
created_by UUID REFERENCES users(id),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
-- Validation: ensure required fields based on direction
CONSTRAINT chk_direction CHECK (direction IN ('outbound', 'inbound')),
CONSTRAINT chk_outbound_fields CHECK (
direction != 'outbound' OR (
source_dataset IS NOT NULL AND
target_host IS NOT NULL AND
target_dataset IS NOT NULL
)
),
CONSTRAINT chk_inbound_fields CHECK (
direction != 'inbound' OR (
source_host IS NOT NULL AND
source_dataset IS NOT NULL AND
local_dataset IS NOT NULL
)
)
);
-- Create indexes
CREATE INDEX IF NOT EXISTS idx_replication_tasks_direction ON replication_tasks(direction);
CREATE INDEX IF NOT EXISTS idx_replication_tasks_enabled ON replication_tasks(enabled);
CREATE INDEX IF NOT EXISTS idx_replication_tasks_status ON replication_tasks(status);
CREATE INDEX IF NOT EXISTS idx_replication_tasks_next_run ON replication_tasks(next_run_at);
CREATE INDEX IF NOT EXISTS idx_replication_tasks_source_dataset ON replication_tasks(source_dataset);
CREATE INDEX IF NOT EXISTS idx_replication_tasks_target_host ON replication_tasks(target_host);

View File

@@ -0,0 +1,30 @@
-- AtlasOS - Calypso
-- Client Categories Schema
-- Version: 14.0
-- Adds category support for backup clients (File, Database, Virtual)
-- Client metadata table to store additional information about clients
-- This extends the Bacula Client table with Calypso-specific metadata
CREATE TABLE IF NOT EXISTS client_metadata (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
client_id INTEGER NOT NULL, -- Bacula Client.ClientId
client_name VARCHAR(255) NOT NULL, -- Bacula Client.Name (for reference)
category VARCHAR(50) NOT NULL DEFAULT 'File', -- 'File', 'Database', 'Virtual'
description TEXT,
tags JSONB, -- Additional tags/metadata as JSON
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
-- Ensure one metadata entry per client
CONSTRAINT unique_client_id UNIQUE (client_id),
CONSTRAINT chk_category CHECK (category IN ('File', 'Database', 'Virtual'))
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_client_metadata_client_id ON client_metadata(client_id);
CREATE INDEX IF NOT EXISTS idx_client_metadata_client_name ON client_metadata(client_name);
CREATE INDEX IF NOT EXISTS idx_client_metadata_category ON client_metadata(category);
-- Add comment
COMMENT ON TABLE client_metadata IS 'Stores Calypso-specific metadata for backup clients, including category classification';
COMMENT ON COLUMN client_metadata.category IS 'Client category: File (file system backups), Database (database backups), Virtual (virtual machine backups)';

View File

@@ -0,0 +1,44 @@
-- AtlasOS - Calypso
-- Migration 015: Add Bacula clients and capability history tables
--
-- Adds tables for tracking registered Bacula agents, their backup capabilities,
-- and a history log for UI- or agent-triggered capability changes. Pending
-- updates are stored on the client row until the agent pulls them.
CREATE TABLE IF NOT EXISTS bacula_clients (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
hostname TEXT NOT NULL,
ip_address TEXT,
agent_version TEXT,
status TEXT NOT NULL DEFAULT 'online',
backup_types JSONB NOT NULL,
pending_backup_types JSONB,
pending_requested_by UUID,
pending_requested_at TIMESTAMPTZ,
pending_notes TEXT,
metadata JSONB,
registered_by_user_id UUID NOT NULL,
last_seen TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uniq_bacula_clients_hostname UNIQUE (hostname)
);
CREATE INDEX IF NOT EXISTS idx_bacula_clients_registered_by ON bacula_clients (registered_by_user_id);
CREATE INDEX IF NOT EXISTS idx_bacula_clients_status ON bacula_clients (status);
CREATE TABLE IF NOT EXISTS bacula_client_capability_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
client_id UUID NOT NULL REFERENCES bacula_clients (id) ON DELETE CASCADE,
backup_types JSONB NOT NULL,
source TEXT NOT NULL,
requested_by_user_id UUID,
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
notes TEXT
);
CREATE INDEX IF NOT EXISTS idx_bacula_client_capability_history_client ON bacula_client_capability_history (client_id);
CREATE INDEX IF NOT EXISTS idx_bacula_client_capability_history_requested_at ON bacula_client_capability_history (requested_at);
COMMENT ON TABLE bacula_clients IS 'Tracks Bacula clients registered with Calypso, including pending capability pushes.';
COMMENT ON TABLE bacula_client_capability_history IS 'Audit history of backup capability changes per client.';

Some files were not shown because too many files have changed in this diff Show More