#!/bin/bash # ProxMenux - OCI Application Manager # ============================================ # Author : MacRimi # License : MIT # Version : 1.0 # ============================================ # Manages OCI container applications through dialog menus or direct CLI. # This script wraps the Python oci_manager.py for terminal usage. SCRIPT_TITLE="OCI Application Manager" LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts" BASE_DIR="/usr/local/share/proxmenux" UTILS_FILE="$BASE_DIR/utils.sh" OCI_MANAGER="$LOCAL_SCRIPTS/oci_manager.py" # OCI paths - persistent data in proxmenux directory OCI_DIR="$BASE_DIR/oci" OCI_CATALOG="$OCI_DIR/catalog.json" OCI_INSTALLED="$OCI_DIR/installed.json" OCI_INSTANCES="$OCI_DIR/instances" # Source catalog bundled with Scripts SCRIPTS_CATALOG="$LOCAL_SCRIPTS/oci/catalog.json" export BASE_DIR if [[ -f "$UTILS_FILE" ]]; then source "$UTILS_FILE" fi load_language 2>/dev/null || true initialize_cache 2>/dev/null || true # ========================================================== # OCI Directory Initialization # ========================================================== ensure_oci_directories() { # Create OCI directories if they don't exist mkdir -p "$OCI_DIR" mkdir -p "$OCI_INSTANCES" # Copy catalog from Scripts if not present if [[ ! -f "$OCI_CATALOG" && -f "$SCRIPTS_CATALOG" ]]; then cp "$SCRIPTS_CATALOG" "$OCI_CATALOG" msg_ok "Initialized OCI catalog" fi # Create empty installed.json if not present if [[ ! -f "$OCI_INSTALLED" ]]; then echo '{"version": "1.0.0", "instances": {}}' > "$OCI_INSTALLED" fi } # ========================================================== # Proxmox OCI Support Detection # ========================================================== check_proxmox_oci_support() { PVE_VERSION="" OCI_SUPPORTED=false # Get Proxmox VE version if command -v pveversion >/dev/null 2>&1; then PVE_VERSION=$(pveversion | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1) # Check if version >= 9.1 local major minor major=$(echo "$PVE_VERSION" | cut -d. -f1) minor=$(echo "$PVE_VERSION" | cut -d. -f2) if [[ $major -gt 9 ]] || [[ $major -eq 9 && $minor -ge 1 ]]; then OCI_SUPPORTED=true fi fi } check_oci_support() { check_proxmox_oci_support if [[ "$OCI_SUPPORTED" != "true" ]]; then msg_error "$(translate "OCI containers require Proxmox VE 9.1 or later.")" msg_info2 "$(translate "Current version: $PVE_VERSION")" return 1 fi return 0 } # ========================================================== # Helper Functions # ========================================================== run_oci_manager() { python3 "$OCI_MANAGER" "$@" } get_app_status() { local app_id="$1" run_oci_manager status --app-id "$app_id" 2>/dev/null | jq -r '.state // "not_installed"' } is_installed() { local app_id="$1" local status=$(get_app_status "$app_id") [[ "$status" != "not_installed" ]] } # ========================================================== # Secure Gateway Functions # ========================================================== deploy_secure_gateway() { show_proxmenux_logo msg_title "$(translate "Secure Gateway (Tailscale VPN)")" if ! check_oci_support; then return 1 fi # Check if already installed if is_installed "secure-gateway"; then local status=$(get_app_status "secure-gateway") msg_warn "$(translate "Secure Gateway is already installed.")" msg_info2 "Status: $status" echo "" read -p "$(translate "Press Enter to continue...")" _ return 0 fi msg_info2 "$(translate "This will deploy a Tailscale VPN gateway for secure remote access.")" msg_info2 "$(translate "You will need a Tailscale auth key from: https://login.tailscale.com/admin/settings/keys")" echo "" # Get auth key local auth_key while true; do read -p "$(translate "Enter Tailscale Auth Key"): " auth_key if [[ -z "$auth_key" ]]; then msg_error "$(translate "Auth key is required.")" continue fi if [[ ! "$auth_key" =~ ^tskey- ]]; then msg_warn "$(translate "Warning: Auth key should start with 'tskey-'")" fi break done # Get hostname local default_hostname="${HOSTNAME:-proxmox}-gateway" read -p "$(translate "Device hostname") [$default_hostname]: " hostname hostname="${hostname:-$default_hostname}" # Access mode echo "" msg_info2 "$(translate "Access Scope:")" echo " 1) Host Only - ProxMenux Monitor & Proxmox UI only" echo " 2) Proxmox Network - Include VMs, LXCs, and host services" echo " 3) Custom - Select specific networks" echo "" local access_mode="host_only" local routes="" read -p "$(translate "Select access mode") [1]: " mode_choice case "$mode_choice" in 2) access_mode="proxmox_network" # Auto-detect networks routes=$(detect_networks_for_routing) ;; 3) access_mode="custom" msg_info2 "$(translate "Enter networks to advertise (comma-separated CIDR):")" msg_info2 " Example: 10.0.1.0/24,192.168.1.0/24" read -p "Networks: " routes ;; *) access_mode="host_only" ;; esac # Exit node option local exit_node="false" read -p "$(translate "Offer as exit node?") [y/N]: " exit_choice [[ "$exit_choice" =~ ^[Yy] ]] && exit_node="true" # Accept routes option local accept_routes="false" read -p "$(translate "Accept routes from other nodes?") [y/N]: " accept_choice [[ "$accept_choice" =~ ^[Yy] ]] && accept_routes="true" echo "" msg_info2 "$(translate "Configuration Summary:")" echo " Hostname: $hostname" echo " Access Mode: $access_mode" [[ -n "$routes" ]] && echo " Networks: $routes" echo " Exit Node: $exit_node" echo " Accept Routes: $accept_routes" echo "" read -p "$(translate "Deploy with this configuration?") [Y/n]: " confirm if [[ "$confirm" =~ ^[Nn] ]]; then msg_warn "$(translate "Deployment cancelled.")" return 0 fi # Build config JSON local routes_array="[]" if [[ -n "$routes" ]]; then # Convert comma-separated to JSON array routes_array=$(echo "$routes" | tr ',' '\n' | jq -R . | jq -s .) fi local config_json=$(cat <&1) if echo "$result" | jq -e '.success == true' >/dev/null 2>&1; then msg_ok "$(translate "Secure Gateway deployed successfully!")" msg_info2 "$(translate "The gateway should appear in your Tailscale admin console shortly.")" else local error_msg=$(echo "$result" | jq -r '.message // "Unknown error"') msg_error "$(translate "Deployment failed"): $error_msg" return 1 fi echo "" read -p "$(translate "Press Enter to continue...")" _ } detect_networks_for_routing() { # Detect bridge interfaces and their subnets local networks="" for iface in $(ip -o link show | awk -F': ' '{print $2}' | grep -E '^vmbr|^bond' | head -5); do local subnet=$(ip -4 addr show "$iface" 2>/dev/null | grep -oP 'inet \K[\d.]+/\d+' | head -1) if [[ -n "$subnet" ]]; then # Convert IP/prefix to network/prefix local network=$(python3 -c "import ipaddress; print(ipaddress.IPv4Network('$subnet', strict=False))" 2>/dev/null) if [[ -n "$network" ]]; then [[ -n "$networks" ]] && networks="$networks," networks="$networks$network" fi fi done echo "$networks" } manage_secure_gateway() { show_proxmenux_logo msg_title "$(translate "Manage Secure Gateway")" local status=$(get_app_status "secure-gateway") msg_info2 "Current status: $status" echo "" case "$status" in "running") echo "1) Stop gateway" echo "2) Restart gateway" echo "3) View logs" echo "4) Remove gateway" echo "5) Back" ;; "stopped"|"exited") echo "1) Start gateway" echo "2) View logs" echo "3) Remove gateway" echo "4) Back" ;; *) msg_error "$(translate "Gateway is not installed.")" read -p "$(translate "Press Enter to continue...")" _ return ;; esac echo "" read -p "$(translate "Select option"): " choice case "$status" in "running") case "$choice" in 1) action_stop_gateway ;; 2) action_restart_gateway ;; 3) action_view_logs ;; 4) action_remove_gateway ;; esac ;; "stopped"|"exited") case "$choice" in 1) action_start_gateway ;; 2) action_view_logs ;; 3) action_remove_gateway ;; esac ;; esac } action_start_gateway() { msg_info "$(translate "Starting gateway...")" local result=$(run_oci_manager start --app-id "secure-gateway" 2>&1) if echo "$result" | jq -e '.success == true' >/dev/null 2>&1; then msg_ok "$(translate "Gateway started.")" else msg_error "$(translate "Failed to start gateway.")" fi read -p "$(translate "Press Enter to continue...")" _ } action_stop_gateway() { msg_info "$(translate "Stopping gateway...")" local result=$(run_oci_manager stop --app-id "secure-gateway" 2>&1) if echo "$result" | jq -e '.success == true' >/dev/null 2>&1; then msg_ok "$(translate "Gateway stopped.")" else msg_error "$(translate "Failed to stop gateway.")" fi read -p "$(translate "Press Enter to continue...")" _ } action_restart_gateway() { msg_info "$(translate "Restarting gateway...")" local result=$(run_oci_manager restart --app-id "secure-gateway" 2>&1) if echo "$result" | jq -e '.success == true' >/dev/null 2>&1; then msg_ok "$(translate "Gateway restarted.")" else msg_error "$(translate "Failed to restart gateway.")" fi read -p "$(translate "Press Enter to continue...")" _ } action_view_logs() { echo "" msg_info2 "$(translate "Recent logs:")" echo "----------------------------------------" $RUNTIME logs --tail 50 proxmenux-secure-gateway 2>/dev/null || echo "No logs available" echo "----------------------------------------" echo "" read -p "$(translate "Press Enter to continue...")" _ } action_remove_gateway() { echo "" read -p "$(translate "Remove Secure Gateway? State will be preserved.") [y/N]: " confirm if [[ ! "$confirm" =~ ^[Yy] ]]; then return fi msg_info "$(translate "Removing gateway...")" local result=$(run_oci_manager remove --app-id "secure-gateway" 2>&1) if echo "$result" | jq -e '.success == true' >/dev/null 2>&1; then msg_ok "$(translate "Gateway removed.")" else msg_error "$(translate "Failed to remove gateway.")" fi read -p "$(translate "Press Enter to continue...")" _ } # ========================================================== # Main Menu # ========================================================== show_oci_menu() { while true; do show_proxmenux_logo msg_title "$(translate "$SCRIPT_TITLE")" detect_runtime if [[ -z "$RUNTIME" ]]; then msg_warn "$(translate "No container runtime available.")" msg_info2 "$(translate "Install podman or docker to continue.")" echo "" read -p "$(translate "Press Enter to exit...")" _ return fi msg_info2 "Runtime: $RUNTIME $RUNTIME_VERSION" echo "" # Check gateway status local gw_status=$(get_app_status "secure-gateway") local gw_label="Secure Gateway (Tailscale VPN)" if [[ "$gw_status" != "not_installed" ]]; then gw_label="$gw_label [$gw_status]" fi echo "1) $gw_label" echo "" echo "0) $(translate "Exit")" echo "" read -p "$(translate "Select option"): " choice case "$choice" in 1) if [[ "$gw_status" == "not_installed" ]]; then deploy_secure_gateway else manage_secure_gateway fi ;; 0|q|Q) break ;; *) msg_error "$(translate "Invalid option")" sleep 1 ;; esac done } # ========================================================== # CLI Mode # ========================================================== cli_mode() { local command="$1" shift case "$command" in deploy) local app_id="" local config="" while [[ $# -gt 0 ]]; do case "$1" in --app-id) app_id="$2"; shift 2 ;; --config) config="$2"; shift 2 ;; *) shift ;; esac done if [[ -z "$app_id" ]]; then echo "Error: --app-id required" exit 1 fi run_oci_manager deploy --app-id "$app_id" --config "${config:-{}}" --source "cli" ;; start|stop|restart|remove|status) local app_id="" while [[ $# -gt 0 ]]; do case "$1" in --app-id) app_id="$2"; shift 2 ;; *) shift ;; esac done if [[ -z "$app_id" ]]; then echo "Error: --app-id required" exit 1 fi run_oci_manager "$command" --app-id "$app_id" ;; list) run_oci_manager list ;; catalog) run_oci_manager catalog ;; networks) run_oci_manager networks ;; runtime) run_oci_manager runtime ;; *) echo "Usage: $0 [command] [options]" echo "" echo "Commands:" echo " (no args) Interactive menu" echo " deploy Deploy an app (--app-id, --config)" echo " start Start an app (--app-id)" echo " stop Stop an app (--app-id)" echo " restart Restart an app (--app-id)" echo " remove Remove an app (--app-id)" echo " status Get app status (--app-id)" echo " list List installed apps" echo " catalog List available apps" echo " networks Detect available networks" echo " runtime Show container runtime info" exit 1 ;; esac } # ========================================================== # Entry Point # ========================================================== main() { # Initialize OCI directories and catalog ensure_oci_directories if [[ $# -gt 0 ]]; then cli_mode "$@" else show_oci_menu fi } main "$@"