Streamlit Control Dashboard Phase 3:
- Add auto-refresh toggle to all main pages (10s/30s/60s intervals)
- Add permission-aware UI with can_write() and is_admin() helpers
- Containers page: tabs (All/Running/Stopped), search filter, info panels
- Security page: better CrowdSec parsing, threat table, raw data viewer
- Streamlit apps page: restart button, delete confirmation dialog
- Network page: HAProxy filter, WireGuard/DNS placeholders
fix(crowdsec-dashboard): Handle RPC error codes in overview.js
Fix TypeError when CrowdSec RPC returns error code instead of object.
Added type check to treat non-objects as empty {} in render/pollData.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1247 lines
32 KiB
Bash
1247 lines
32 KiB
Bash
#!/bin/sh
|
|
# RezApp Forge - Docker to LXC Converter
|
|
# Copyright (C) 2026 SecuBox
|
|
|
|
. /lib/functions.sh
|
|
|
|
CONFIG="rezapp"
|
|
CACHE_DIR=""
|
|
OUTPUT_DIR=""
|
|
APPS_DIR=""
|
|
LXC_DIR=""
|
|
DEFAULT_MEMORY=""
|
|
DEFAULT_NETWORK=""
|
|
TEMPLATES_DIR="/usr/share/rezapp/templates"
|
|
|
|
# Logging
|
|
log_info() { echo "[INFO] $*"; }
|
|
log_warn() { echo "[WARN] $*" >&2; }
|
|
log_error() { echo "[ERROR] $*" >&2; }
|
|
|
|
# Container runtime (docker or podman)
|
|
CONTAINER_RUNTIME=""
|
|
|
|
# Detect and initialize container runtime (Docker or Podman fallback)
|
|
init_runtime() {
|
|
[ -n "$CONTAINER_RUNTIME" ] && return 0
|
|
|
|
# Try Docker first
|
|
if command -v docker >/dev/null 2>&1; then
|
|
if docker info >/dev/null 2>&1; then
|
|
CONTAINER_RUNTIME="docker"
|
|
log_info "Using Docker runtime"
|
|
return 0
|
|
fi
|
|
# Try starting Docker daemon
|
|
if [ -x /etc/init.d/dockerd ]; then
|
|
log_info "Starting Docker daemon..."
|
|
/etc/init.d/dockerd start 2>/dev/null
|
|
sleep 5
|
|
if docker info >/dev/null 2>&1; then
|
|
CONTAINER_RUNTIME="docker"
|
|
log_info "Using Docker runtime"
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Fallback to Podman
|
|
if command -v podman >/dev/null 2>&1; then
|
|
if podman info >/dev/null 2>&1; then
|
|
CONTAINER_RUNTIME="podman"
|
|
log_info "Using Podman runtime (fallback)"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
log_error "No container runtime available (docker or podman)"
|
|
return 1
|
|
}
|
|
|
|
# Runtime wrapper functions
|
|
runtime_pull() {
|
|
$CONTAINER_RUNTIME pull "$@"
|
|
}
|
|
|
|
runtime_create() {
|
|
$CONTAINER_RUNTIME create "$@"
|
|
}
|
|
|
|
runtime_export() {
|
|
$CONTAINER_RUNTIME export "$@"
|
|
}
|
|
|
|
runtime_rm() {
|
|
$CONTAINER_RUNTIME rm "$@"
|
|
}
|
|
|
|
runtime_inspect() {
|
|
$CONTAINER_RUNTIME inspect "$@"
|
|
}
|
|
|
|
# Load configuration
|
|
load_config() {
|
|
config_load "$CONFIG"
|
|
config_get CACHE_DIR main cache_dir "/srv/rezapp/cache"
|
|
config_get OUTPUT_DIR main output_dir "/srv/rezapp/generated"
|
|
config_get APPS_DIR main apps_dir "/srv/rezapp/apps"
|
|
config_get LXC_DIR main lxc_dir "/srv/lxc"
|
|
config_get DEFAULT_MEMORY main default_memory "512M"
|
|
config_get DEFAULT_NETWORK main default_network "host"
|
|
}
|
|
|
|
# Ensure directories exist
|
|
ensure_dirs() {
|
|
mkdir -p "$CACHE_DIR" "$OUTPUT_DIR" "$APPS_DIR"
|
|
}
|
|
|
|
# ==========================================
|
|
# Catalog Commands
|
|
# ==========================================
|
|
|
|
cmd_catalog_list() {
|
|
echo "Enabled Catalogs:"
|
|
echo "================="
|
|
|
|
_print_catalog() {
|
|
local section="$1"
|
|
local name enabled type namespace
|
|
|
|
config_get name "$section" name "$section"
|
|
config_get enabled "$section" enabled "0"
|
|
config_get type "$section" type "dockerhub"
|
|
config_get namespace "$section" namespace ""
|
|
|
|
[ "$enabled" = "1" ] || return
|
|
|
|
if [ -n "$namespace" ]; then
|
|
printf " %-15s %-12s %s\n" "$section" "$type" "$namespace/*"
|
|
else
|
|
printf " %-15s %-12s (all)\n" "$section" "$type"
|
|
fi
|
|
}
|
|
|
|
config_load "$CONFIG"
|
|
config_foreach _print_catalog catalog
|
|
}
|
|
|
|
cmd_catalog_add() {
|
|
local name="$1"
|
|
local namespace="$2"
|
|
|
|
[ -z "$name" ] && { log_error "Catalog name required"; return 1; }
|
|
|
|
uci set rezapp.$name=catalog
|
|
uci set rezapp.$name.name="$name"
|
|
uci set rezapp.$name.type="dockerhub"
|
|
[ -n "$namespace" ] && uci set rezapp.$name.namespace="$namespace"
|
|
uci set rezapp.$name.enabled="1"
|
|
uci commit rezapp
|
|
|
|
log_info "Catalog added: $name"
|
|
}
|
|
|
|
cmd_catalog_remove() {
|
|
local name="$1"
|
|
|
|
[ -z "$name" ] && { log_error "Catalog name required"; return 1; }
|
|
|
|
uci delete rezapp.$name 2>/dev/null
|
|
uci commit rezapp
|
|
|
|
log_info "Catalog removed: $name"
|
|
}
|
|
|
|
# ==========================================
|
|
# Search Commands
|
|
# ==========================================
|
|
|
|
cmd_search() {
|
|
local query="$1"
|
|
local catalog="$2"
|
|
|
|
[ -z "$query" ] && { log_error "Search query required"; return 1; }
|
|
|
|
log_info "Searching Docker Hub for: $query"
|
|
|
|
local url="https://hub.docker.com/v2/search/repositories/?query=${query}&page_size=25"
|
|
local result=$(curl -s "$url")
|
|
|
|
if [ -z "$result" ]; then
|
|
log_error "Search failed"
|
|
return 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "Search Results:"
|
|
echo "==============="
|
|
|
|
echo "$result" | jsonfilter -e '@.results[*]' | while read -r item; do
|
|
local name=$(echo "$item" | jsonfilter -e '@.repo_name')
|
|
local desc=$(echo "$item" | jsonfilter -e '@.short_description' | head -c 60)
|
|
local stars=$(echo "$item" | jsonfilter -e '@.star_count')
|
|
local official=$(echo "$item" | jsonfilter -e '@.is_official')
|
|
|
|
local badge=""
|
|
[ "$official" = "true" ] && badge="[official]"
|
|
|
|
printf " %-35s %5s* %s %s\n" "$name" "$stars" "$badge" "$desc"
|
|
done
|
|
}
|
|
|
|
cmd_info() {
|
|
local image="$1"
|
|
|
|
[ -z "$image" ] && { log_error "Image name required"; return 1; }
|
|
|
|
# Parse image name
|
|
local namespace="${image%%/*}"
|
|
local repo="${image#*/}"
|
|
|
|
if [ "$namespace" = "$image" ]; then
|
|
namespace="library"
|
|
repo="$image"
|
|
fi
|
|
|
|
log_info "Fetching info for: $image"
|
|
|
|
# Get tags
|
|
local tags_url="https://hub.docker.com/v2/repositories/${namespace}/${repo}/tags?page_size=10"
|
|
local tags=$(curl -s "$tags_url")
|
|
|
|
echo ""
|
|
echo "Image: $image"
|
|
echo "========================================"
|
|
echo ""
|
|
echo "Available Tags:"
|
|
echo "$tags" | jsonfilter -e '@.results[*].name' | head -10 | while read tag; do
|
|
echo " - $tag"
|
|
done
|
|
|
|
# Try to get config for latest
|
|
echo ""
|
|
echo "To convert: rezappctl convert $image --name <app-name>"
|
|
}
|
|
|
|
# ==========================================
|
|
# Convert Command (with offline mode support)
|
|
# ==========================================
|
|
|
|
cmd_convert() {
|
|
local image=""
|
|
local name=""
|
|
local tag="latest"
|
|
local memory=""
|
|
local network=""
|
|
local ports=""
|
|
local mounts=""
|
|
local from_tar=""
|
|
local from_oci=""
|
|
local offline="0"
|
|
|
|
# Parse arguments
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--tag) tag="$2"; shift 2 ;;
|
|
--memory) memory="$2"; shift 2 ;;
|
|
--network) network="$2"; shift 2 ;;
|
|
--port) ports="$ports $2"; shift 2 ;;
|
|
--mount) mounts="$mounts $2"; shift 2 ;;
|
|
--from-tar) from_tar="$2"; offline="1"; shift 2 ;;
|
|
--from-oci) from_oci="$2"; offline="1"; shift 2 ;;
|
|
--offline) offline="1"; shift ;;
|
|
-*) log_error "Unknown option: $1"; return 1 ;;
|
|
*) image="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
# Offline mode: convert from local tarball
|
|
if [ -n "$from_tar" ]; then
|
|
[ ! -f "$from_tar" ] && { log_error "Tarball not found: $from_tar"; return 1; }
|
|
[ -z "$name" ] && { log_error "Name required with --from-tar"; return 1; }
|
|
_convert_from_tarball "$from_tar" "$name" "$memory" "$network"
|
|
return $?
|
|
fi
|
|
|
|
# Offline mode: convert from OCI directory
|
|
if [ -n "$from_oci" ]; then
|
|
[ ! -d "$from_oci" ] && { log_error "OCI directory not found: $from_oci"; return 1; }
|
|
[ -z "$name" ] && { log_error "Name required with --from-oci"; return 1; }
|
|
_convert_from_oci "$from_oci" "$name" "$memory" "$network"
|
|
return $?
|
|
fi
|
|
|
|
# Check cache for existing tarball (offline conversion)
|
|
[ -z "$image" ] && { log_error "Image name required"; return 1; }
|
|
|
|
# Default name from image
|
|
if [ -z "$name" ]; then
|
|
name="${image##*/}"
|
|
name="${name%%:*}"
|
|
fi
|
|
|
|
[ -z "$memory" ] && memory="$DEFAULT_MEMORY"
|
|
[ -z "$network" ] && network="$DEFAULT_NETWORK"
|
|
|
|
local full_image="${image}:${tag}"
|
|
local app_dir="$APPS_DIR/$name"
|
|
local lxc_path="$LXC_DIR/$name"
|
|
local tarball="$CACHE_DIR/${name}.tar"
|
|
|
|
# Check if cached tarball exists
|
|
if [ -f "$tarball" ] && [ "$offline" = "1" ]; then
|
|
log_info "Using cached tarball: $tarball"
|
|
_convert_from_tarball "$tarball" "$name" "$memory" "$network"
|
|
return $?
|
|
fi
|
|
|
|
log_info "Converting $full_image -> $name"
|
|
|
|
# Step 1: Initialize container runtime (Docker or Podman)
|
|
init_runtime || return 1
|
|
|
|
# Step 2: Pull image
|
|
log_info "Pulling image via $CONTAINER_RUNTIME..."
|
|
runtime_pull "$full_image" || { log_error "Failed to pull image"; return 1; }
|
|
|
|
# Step 3: Inspect image
|
|
log_info "Inspecting image metadata..."
|
|
mkdir -p "$app_dir"
|
|
runtime_inspect "$full_image" > "$app_dir/docker-inspect.json"
|
|
|
|
# Extract metadata
|
|
local entrypoint=$(jsonfilter -i "$app_dir/docker-inspect.json" -e '@[0].Config.Entrypoint[*]' 2>/dev/null | tr '\n' ' ')
|
|
local cmd=$(jsonfilter -i "$app_dir/docker-inspect.json" -e '@[0].Config.Cmd[*]' 2>/dev/null | tr '\n' ' ')
|
|
local workdir=$(jsonfilter -i "$app_dir/docker-inspect.json" -e '@[0].Config.WorkingDir' 2>/dev/null)
|
|
local user=$(jsonfilter -i "$app_dir/docker-inspect.json" -e '@[0].Config.User' 2>/dev/null)
|
|
local exposed=$(jsonfilter -i "$app_dir/docker-inspect.json" -e '@[0].Config.ExposedPorts' 2>/dev/null)
|
|
|
|
# Extract environment variables
|
|
local env_vars=""
|
|
local env_list=$(jsonfilter -i "$app_dir/docker-inspect.json" -e '@[0].Config.Env[*]' 2>/dev/null)
|
|
if [ -n "$env_list" ]; then
|
|
# Filter out PATH and other system vars, keep useful ones
|
|
env_vars=$(echo "$env_list" | grep -vE '^(PATH=|HOME=|HOSTNAME=|TERM=)' | tr '\n' '|')
|
|
fi
|
|
|
|
log_info "Extracted: entrypoint='$entrypoint' cmd='$cmd' workdir='$workdir' user='$user'"
|
|
[ -n "$exposed" ] && log_info "Exposed ports: $exposed"
|
|
|
|
# Step 4: Export filesystem
|
|
log_info "Exporting container filesystem..."
|
|
runtime_create --name rezapp-export-$$ "$full_image" >/dev/null 2>&1
|
|
runtime_export rezapp-export-$$ > "$tarball"
|
|
runtime_rm rezapp-export-$$ >/dev/null 2>&1
|
|
|
|
# Create LXC from tarball
|
|
_create_lxc_from_tar "$tarball" "$name" "$memory" "$entrypoint" "$cmd" "$workdir" "$user" "$full_image" "$network" "$ports" "$env_vars" "$exposed"
|
|
}
|
|
|
|
# Convert from local tarball (offline mode)
|
|
_convert_from_tarball() {
|
|
local tarball="$1"
|
|
local name="$2"
|
|
local memory="${3:-$DEFAULT_MEMORY}"
|
|
local network="${4:-$DEFAULT_NETWORK}"
|
|
|
|
log_info "Converting from tarball: $tarball -> $name"
|
|
|
|
# Try to extract metadata from tarball
|
|
local entrypoint=""
|
|
local cmd="/bin/sh"
|
|
local workdir="/"
|
|
local user=""
|
|
|
|
# Check for OCI manifest in tarball
|
|
if tar tf "$tarball" 2>/dev/null | grep -q "manifest.json"; then
|
|
log_info "Found OCI manifest, extracting metadata..."
|
|
local manifest_tmp="/tmp/rezapp-manifest-$$.json"
|
|
tar xf "$tarball" -O manifest.json > "$manifest_tmp" 2>/dev/null
|
|
# Extract config digest and parse
|
|
local config_file=$(jsonfilter -i "$manifest_tmp" -e '@[0].Config' 2>/dev/null)
|
|
if [ -n "$config_file" ] && tar tf "$tarball" 2>/dev/null | grep -q "$config_file"; then
|
|
tar xf "$tarball" -O "$config_file" > "/tmp/rezapp-config-$$.json" 2>/dev/null
|
|
entrypoint=$(jsonfilter -i "/tmp/rezapp-config-$$.json" -e '@.config.Entrypoint[*]' 2>/dev/null | tr '\n' ' ')
|
|
cmd=$(jsonfilter -i "/tmp/rezapp-config-$$.json" -e '@.config.Cmd[*]' 2>/dev/null | tr '\n' ' ')
|
|
workdir=$(jsonfilter -i "/tmp/rezapp-config-$$.json" -e '@.config.WorkingDir' 2>/dev/null)
|
|
user=$(jsonfilter -i "/tmp/rezapp-config-$$.json" -e '@.config.User' 2>/dev/null)
|
|
rm -f "/tmp/rezapp-config-$$.json"
|
|
fi
|
|
rm -f "$manifest_tmp"
|
|
fi
|
|
|
|
_create_lxc_from_tar "$tarball" "$name" "$memory" "$entrypoint" "$cmd" "$workdir" "$user" "local:$tarball" "$network" "" "" ""
|
|
}
|
|
|
|
# Convert from OCI directory (offline mode)
|
|
_convert_from_oci() {
|
|
local oci_dir="$1"
|
|
local name="$2"
|
|
local memory="${3:-$DEFAULT_MEMORY}"
|
|
local network="${4:-$DEFAULT_NETWORK}"
|
|
|
|
log_info "Converting from OCI directory: $oci_dir -> $name"
|
|
|
|
local app_dir="$APPS_DIR/$name"
|
|
local lxc_path="$LXC_DIR/$name"
|
|
|
|
# Parse OCI index and config
|
|
local index_file="$oci_dir/index.json"
|
|
[ ! -f "$index_file" ] && { log_error "OCI index.json not found"; return 1; }
|
|
|
|
local manifest_digest=$(jsonfilter -i "$index_file" -e '@.manifests[0].digest' 2>/dev/null)
|
|
manifest_digest="${manifest_digest#sha256:}"
|
|
local manifest_file="$oci_dir/blobs/sha256/$manifest_digest"
|
|
|
|
[ ! -f "$manifest_file" ] && { log_error "OCI manifest not found"; return 1; }
|
|
|
|
# Get config
|
|
local config_digest=$(jsonfilter -i "$manifest_file" -e '@.config.digest' 2>/dev/null)
|
|
config_digest="${config_digest#sha256:}"
|
|
local config_file="$oci_dir/blobs/sha256/$config_digest"
|
|
|
|
local entrypoint=""
|
|
local cmd="/bin/sh"
|
|
local workdir="/"
|
|
local user=""
|
|
|
|
if [ -f "$config_file" ]; then
|
|
entrypoint=$(jsonfilter -i "$config_file" -e '@.config.Entrypoint[*]' 2>/dev/null | tr '\n' ' ')
|
|
cmd=$(jsonfilter -i "$config_file" -e '@.config.Cmd[*]' 2>/dev/null | tr '\n' ' ')
|
|
workdir=$(jsonfilter -i "$config_file" -e '@.config.WorkingDir' 2>/dev/null)
|
|
user=$(jsonfilter -i "$config_file" -e '@.config.User' 2>/dev/null)
|
|
fi
|
|
|
|
# Extract layers
|
|
mkdir -p "$app_dir" "$lxc_path/rootfs"
|
|
log_info "Extracting OCI layers..."
|
|
|
|
# Get layer digests and extract in order
|
|
local layers=$(jsonfilter -i "$manifest_file" -e '@.layers[*].digest' 2>/dev/null)
|
|
for layer_digest in $layers; do
|
|
layer_digest="${layer_digest#sha256:}"
|
|
local layer_file="$oci_dir/blobs/sha256/$layer_digest"
|
|
if [ -f "$layer_file" ]; then
|
|
log_info " Extracting layer: ${layer_digest:0:12}..."
|
|
tar xf "$layer_file" -C "$lxc_path/rootfs" 2>/dev/null
|
|
fi
|
|
done
|
|
|
|
# Generate LXC config
|
|
_generate_lxc_config "$name" "$memory" "$entrypoint" "$cmd" "$workdir" "$user" "oci:$oci_dir" "$network" "" "" ""
|
|
}
|
|
|
|
# Create LXC container from tarball
|
|
_create_lxc_from_tar() {
|
|
local tarball="$1"
|
|
local name="$2"
|
|
local memory="$3"
|
|
local entrypoint="$4"
|
|
local cmd="$5"
|
|
local workdir="$6"
|
|
local user="$7"
|
|
local source="$8"
|
|
local network="$9"
|
|
local ports="${10}"
|
|
local env_vars="${11}"
|
|
local exposed="${12}"
|
|
|
|
local app_dir="$APPS_DIR/$name"
|
|
local lxc_path="$LXC_DIR/$name"
|
|
|
|
# Create LXC rootfs
|
|
log_info "Creating LXC container rootfs..."
|
|
rm -rf "$lxc_path"
|
|
mkdir -p "$lxc_path/rootfs" "$app_dir"
|
|
|
|
log_info "Extracting filesystem (this may take a while)..."
|
|
tar xf "$tarball" -C "$lxc_path/rootfs" 2>/dev/null
|
|
|
|
# Ensure /bin/sh exists (some images use busybox or ash)
|
|
if [ ! -e "$lxc_path/rootfs/bin/sh" ]; then
|
|
if [ -e "$lxc_path/rootfs/bin/bash" ]; then
|
|
ln -sf bash "$lxc_path/rootfs/bin/sh"
|
|
elif [ -e "$lxc_path/rootfs/bin/busybox" ]; then
|
|
ln -sf busybox "$lxc_path/rootfs/bin/sh"
|
|
fi
|
|
fi
|
|
|
|
_generate_lxc_config "$name" "$memory" "$entrypoint" "$cmd" "$workdir" "$user" "$source" "$network" "$ports" "$env_vars" "$exposed"
|
|
}
|
|
|
|
# Generate LXC config and metadata
|
|
_generate_lxc_config() {
|
|
local name="$1"
|
|
local memory="$2"
|
|
local entrypoint="$3"
|
|
local cmd="$4"
|
|
local workdir="$5"
|
|
local user="$6"
|
|
local source="$7"
|
|
local network="$8"
|
|
local ports="$9"
|
|
local env_vars="${10}"
|
|
local exposed_ports="${11}"
|
|
|
|
local app_dir="$APPS_DIR/$name"
|
|
local lxc_path="$LXC_DIR/$name"
|
|
|
|
# Generate start script
|
|
log_info "Generating start script..."
|
|
local start_script="$lxc_path/rootfs/start-lxc.sh"
|
|
|
|
# Build the exec command
|
|
local exec_cmd=""
|
|
if [ -n "$entrypoint" ]; then
|
|
exec_cmd="$entrypoint"
|
|
[ -n "$cmd" ] && exec_cmd="$exec_cmd $cmd"
|
|
elif [ -n "$cmd" ]; then
|
|
exec_cmd="$cmd"
|
|
else
|
|
exec_cmd="/bin/sh"
|
|
fi
|
|
|
|
cat > "$start_script" << STARTEOF
|
|
#!/bin/sh
|
|
# Auto-generated by RezApp Forge from $source
|
|
|
|
# Set working directory
|
|
cd ${workdir:-/}
|
|
|
|
# Create common directories
|
|
mkdir -p /config /data /tmp 2>/dev/null
|
|
|
|
# Export environment
|
|
export HOME=\${HOME:-/root}
|
|
export PATH=\${PATH:-/usr/local/bin:/usr/bin:/bin}
|
|
|
|
# Run entrypoint/cmd
|
|
exec $exec_cmd
|
|
STARTEOF
|
|
chmod +x "$start_script"
|
|
|
|
# Parse user for UID/GID
|
|
local uid="0"
|
|
local gid="0"
|
|
if [ -n "$user" ] && [ "$user" != "root" ]; then
|
|
# Handle numeric or name:name format
|
|
case "$user" in
|
|
*:*)
|
|
uid="${user%%:*}"
|
|
gid="${user#*:}"
|
|
;;
|
|
[0-9]*)
|
|
uid="$user"
|
|
gid="$user"
|
|
;;
|
|
*)
|
|
# Named user - keep as 0 for now
|
|
uid="0"
|
|
gid="0"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Convert memory to bytes
|
|
local mem_bytes
|
|
case "$memory" in
|
|
*G) mem_bytes=$(( ${memory%G} * 1073741824 )) ;;
|
|
*M) mem_bytes=$(( ${memory%M} * 1048576 )) ;;
|
|
*K) mem_bytes=$(( ${memory%K} * 1024 )) ;;
|
|
*) mem_bytes="$memory" ;;
|
|
esac
|
|
|
|
log_info "Generating LXC config (network: $network)..."
|
|
|
|
# Start LXC config
|
|
cat > "$lxc_path/config" << LXCEOF
|
|
# LXC config for $name
|
|
# Auto-generated by RezApp Forge from $source
|
|
|
|
lxc.uts.name = $name
|
|
lxc.rootfs.path = dir:$lxc_path/rootfs
|
|
lxc.init.cmd = /start-lxc.sh
|
|
|
|
# Filesystem mounts
|
|
lxc.mount.auto = proc:mixed sys:ro
|
|
lxc.mount.entry = /srv/$name data none bind,create=dir 0 0
|
|
|
|
# Resource limits
|
|
lxc.cgroup2.memory.max = $mem_bytes
|
|
|
|
# User/Group
|
|
lxc.init.uid = $uid
|
|
lxc.init.gid = $gid
|
|
|
|
# Drop dangerous capabilities
|
|
lxc.cap.drop = sys_admin sys_module sys_boot sys_rawio mac_admin mac_override
|
|
|
|
# TTY configuration
|
|
lxc.console.size = 1024
|
|
lxc.pty.max = 1024
|
|
lxc.tty.max = 4
|
|
|
|
# Device access
|
|
lxc.cgroup2.devices.allow = c 1:* rwm
|
|
lxc.cgroup2.devices.allow = c 5:* rwm
|
|
lxc.cgroup2.devices.allow = c 136:* rwm
|
|
|
|
# Disable seccomp for compatibility
|
|
lxc.seccomp.profile =
|
|
|
|
# Autostart disabled by default
|
|
lxc.start.auto = 0
|
|
|
|
LXCEOF
|
|
|
|
# Add network configuration based on mode
|
|
case "$network" in
|
|
host)
|
|
cat >> "$lxc_path/config" << NETEOF
|
|
# Network: Share host namespace
|
|
lxc.namespace.share.net = 1
|
|
NETEOF
|
|
;;
|
|
bridge)
|
|
cat >> "$lxc_path/config" << NETEOF
|
|
# Network: Bridge mode
|
|
lxc.net.0.type = veth
|
|
lxc.net.0.link = br-lan
|
|
lxc.net.0.flags = up
|
|
lxc.net.0.name = eth0
|
|
NETEOF
|
|
;;
|
|
none)
|
|
cat >> "$lxc_path/config" << NETEOF
|
|
# Network: None (isolated)
|
|
lxc.net.0.type = none
|
|
NETEOF
|
|
;;
|
|
*)
|
|
# Default to host network for simplicity
|
|
cat >> "$lxc_path/config" << NETEOF
|
|
# Network: Share host namespace (default)
|
|
lxc.namespace.share.net = 1
|
|
NETEOF
|
|
;;
|
|
esac
|
|
|
|
# Add environment variables
|
|
cat >> "$lxc_path/config" << ENVEOF
|
|
|
|
# Environment variables
|
|
lxc.environment = PUID=$uid
|
|
lxc.environment = PGID=$gid
|
|
lxc.environment = TZ=Europe/Paris
|
|
ENVEOF
|
|
|
|
# Add extracted Docker ENV vars
|
|
if [ -n "$env_vars" ]; then
|
|
echo "" >> "$lxc_path/config"
|
|
echo "# Docker ENV defaults" >> "$lxc_path/config"
|
|
echo "$env_vars" | tr '|' '\n' | while read -r env; do
|
|
[ -n "$env" ] && echo "lxc.environment = $env" >> "$lxc_path/config"
|
|
done
|
|
fi
|
|
|
|
# Create data directory
|
|
mkdir -p "/srv/$name"
|
|
[ "$uid" != "0" ] && chown "$uid:$gid" "/srv/$name" 2>/dev/null
|
|
|
|
# Auto-detect ports from Docker EXPOSE
|
|
local detected_ports=""
|
|
if [ -n "$exposed_ports" ]; then
|
|
detected_ports=$(echo "$exposed_ports" | sed 's|[{}"]||g' | tr ',' '\n' | sed 's|/tcp||g; s|/udp||g' | tr '\n' ' ')
|
|
fi
|
|
|
|
# Save metadata
|
|
cat > "$app_dir/metadata.json" << METAEOF
|
|
{
|
|
"name": "$name",
|
|
"source": "$source",
|
|
"converted_at": "$(date -Iseconds)",
|
|
"entrypoint": "$entrypoint",
|
|
"cmd": "$cmd",
|
|
"workdir": "$workdir",
|
|
"user": "$user",
|
|
"uid": "$uid",
|
|
"gid": "$gid",
|
|
"memory": "$memory",
|
|
"network": "$network",
|
|
"ports": "$ports",
|
|
"exposed_ports": "$detected_ports",
|
|
"lxc_path": "$lxc_path",
|
|
"data_path": "/srv/$name"
|
|
}
|
|
METAEOF
|
|
|
|
log_info "Conversion complete!"
|
|
echo ""
|
|
echo "Container: $name"
|
|
echo " LXC Path: $lxc_path"
|
|
echo " Data Path: /srv/$name"
|
|
echo " Network: $network"
|
|
[ -n "$detected_ports" ] && echo " Ports: $detected_ports"
|
|
echo ""
|
|
echo "To test: lxc-start -n $name -F"
|
|
echo "To package: rezappctl package $name"
|
|
}
|
|
|
|
# ==========================================
|
|
# Import Command (download image for offline use)
|
|
# ==========================================
|
|
|
|
cmd_import() {
|
|
local image="$1"
|
|
local tag="${2:-latest}"
|
|
|
|
[ -z "$image" ] && { log_error "Image name required"; return 1; }
|
|
|
|
local name="${image##*/}"
|
|
name="${name%%:*}"
|
|
local full_image="${image}:${tag}"
|
|
local tarball="$CACHE_DIR/${name}.tar"
|
|
|
|
log_info "Importing $full_image for offline use..."
|
|
|
|
# Initialize container runtime (Docker or Podman)
|
|
init_runtime || return 1
|
|
|
|
# Pull and export
|
|
log_info "Pulling image via $CONTAINER_RUNTIME..."
|
|
runtime_pull "$full_image" || { log_error "Failed to pull image"; return 1; }
|
|
|
|
log_info "Exporting to cache..."
|
|
mkdir -p "$CACHE_DIR"
|
|
runtime_create --name rezapp-import-$$ "$full_image" >/dev/null 2>&1
|
|
runtime_export rezapp-import-$$ > "$tarball"
|
|
runtime_rm rezapp-import-$$ >/dev/null 2>&1
|
|
|
|
# Save inspect data
|
|
runtime_inspect "$full_image" > "$CACHE_DIR/${name}.inspect.json"
|
|
|
|
log_info "Image cached: $tarball"
|
|
echo ""
|
|
echo "To convert offline: rezappctl convert --from-tar $tarball --name $name"
|
|
}
|
|
|
|
# ==========================================
|
|
# Package Command
|
|
# ==========================================
|
|
|
|
cmd_package() {
|
|
local name="$1"
|
|
local install_after=""
|
|
|
|
[ "$name" = "--install" ] && { install_after="1"; name="$2"; }
|
|
[ -z "$name" ] && { log_error "App name required"; return 1; }
|
|
|
|
local app_dir="$APPS_DIR/$name"
|
|
local meta_file="$app_dir/metadata.json"
|
|
local pkg_dir="$OUTPUT_DIR/secubox-app-$name"
|
|
|
|
[ ! -f "$meta_file" ] && { log_error "App not found: $name (run convert first)"; return 1; }
|
|
|
|
# Load metadata
|
|
local source_image=$(jsonfilter -i "$meta_file" -e '@.source_image')
|
|
local memory=$(jsonfilter -i "$meta_file" -e '@.memory')
|
|
local uid=$(jsonfilter -i "$meta_file" -e '@.uid')
|
|
local gid=$(jsonfilter -i "$meta_file" -e '@.gid')
|
|
|
|
log_info "Generating package for: $name"
|
|
|
|
# Create package structure
|
|
rm -rf "$pkg_dir"
|
|
mkdir -p "$pkg_dir/files/etc/config"
|
|
mkdir -p "$pkg_dir/files/etc/init.d"
|
|
mkdir -p "$pkg_dir/files/usr/sbin"
|
|
|
|
# Generate Makefile
|
|
cat > "$pkg_dir/Makefile" << MAKEEOF
|
|
include \$(TOPDIR)/rules.mk
|
|
|
|
PKG_NAME:=secubox-app-$name
|
|
PKG_VERSION:=1.0.0
|
|
PKG_RELEASE:=1
|
|
|
|
include \$(INCLUDE_DIR)/package.mk
|
|
|
|
define Package/secubox-app-$name
|
|
SECTION:=secubox
|
|
CATEGORY:=SecuBox
|
|
SUBMENU:=Apps
|
|
TITLE:=$name (via RezApp Forge)
|
|
DEPENDS:=+lxc +lxc-common
|
|
PKGARCH:=all
|
|
endef
|
|
|
|
define Package/secubox-app-$name/description
|
|
$name - converted from Docker image $source_image
|
|
Generated by RezApp Forge.
|
|
endef
|
|
|
|
define Package/secubox-app-$name/conffiles
|
|
/etc/config/$name
|
|
endef
|
|
|
|
define Build/Compile
|
|
endef
|
|
|
|
define Package/secubox-app-$name/install
|
|
\$(INSTALL_DIR) \$(1)/etc/config
|
|
\$(INSTALL_CONF) ./files/etc/config/$name \$(1)/etc/config/
|
|
|
|
\$(INSTALL_DIR) \$(1)/etc/init.d
|
|
\$(INSTALL_BIN) ./files/etc/init.d/$name \$(1)/etc/init.d/
|
|
|
|
\$(INSTALL_DIR) \$(1)/usr/sbin
|
|
\$(INSTALL_BIN) ./files/usr/sbin/${name}ctl \$(1)/usr/sbin/
|
|
endef
|
|
|
|
\$(eval \$(call BuildPackage,secubox-app-$name))
|
|
MAKEEOF
|
|
|
|
# Generate UCI config
|
|
cat > "$pkg_dir/files/etc/config/$name" << UCIEOF
|
|
config main 'main'
|
|
option enabled '0'
|
|
option container '$name'
|
|
option memory '$memory'
|
|
option data_path '/srv/$name'
|
|
UCIEOF
|
|
|
|
# Generate init script
|
|
cat > "$pkg_dir/files/etc/init.d/$name" << 'INITEOF'
|
|
#!/bin/sh /etc/rc.common
|
|
|
|
START=95
|
|
STOP=10
|
|
EXTRA_COMMANDS="status"
|
|
EXTRA_HELP=" status Show container status"
|
|
|
|
CONF="APPNAME"
|
|
|
|
load_config() {
|
|
. /lib/functions.sh
|
|
config_load "$CONF"
|
|
config_get ENABLED main enabled "0"
|
|
config_get CONTAINER main container "APPNAME"
|
|
}
|
|
|
|
start() {
|
|
load_config
|
|
[ "$ENABLED" != "1" ] && { echo "APPNAME disabled"; return 0; }
|
|
|
|
if lxc-info -n "$CONTAINER" 2>/dev/null | grep -q "RUNNING"; then
|
|
echo "APPNAME already running"
|
|
else
|
|
echo "Starting APPNAME..."
|
|
lxc-start -n "$CONTAINER" -d
|
|
sleep 3
|
|
lxc-info -n "$CONTAINER" | grep State
|
|
fi
|
|
}
|
|
|
|
stop() {
|
|
load_config
|
|
if lxc-info -n "$CONTAINER" 2>/dev/null | grep -q "RUNNING"; then
|
|
echo "Stopping APPNAME..."
|
|
lxc-stop -n "$CONTAINER"
|
|
else
|
|
echo "APPNAME not running"
|
|
fi
|
|
}
|
|
|
|
restart() {
|
|
stop
|
|
sleep 2
|
|
start
|
|
}
|
|
|
|
status() {
|
|
load_config
|
|
lxc-info -n "$CONTAINER" 2>/dev/null || echo "Container not found"
|
|
}
|
|
INITEOF
|
|
sed -i "s/APPNAME/$name/g" "$pkg_dir/files/etc/init.d/$name"
|
|
chmod +x "$pkg_dir/files/etc/init.d/$name"
|
|
|
|
# Generate CLI tool
|
|
cat > "$pkg_dir/files/usr/sbin/${name}ctl" << 'CTLEOF'
|
|
#!/bin/sh
|
|
# Generated by RezApp Forge
|
|
|
|
CONF="APPNAME"
|
|
CONTAINER="APPNAME"
|
|
|
|
. /lib/functions.sh
|
|
|
|
load_config() {
|
|
config_load "$CONF"
|
|
config_get CONTAINER main container "APPNAME"
|
|
}
|
|
|
|
usage() {
|
|
cat << EOF
|
|
Usage: APPNAMEctl <command>
|
|
|
|
Commands:
|
|
start Start container
|
|
stop Stop container
|
|
restart Restart container
|
|
status Show status
|
|
logs Show logs
|
|
shell Open shell
|
|
enable Enable autostart
|
|
disable Disable autostart
|
|
EOF
|
|
}
|
|
|
|
case "$1" in
|
|
start) /etc/init.d/APPNAME start ;;
|
|
stop) /etc/init.d/APPNAME stop ;;
|
|
restart) /etc/init.d/APPNAME restart ;;
|
|
status) /etc/init.d/APPNAME status ;;
|
|
logs) load_config; lxc-attach -n "$CONTAINER" -- tail -100 /var/log/*.log 2>/dev/null ;;
|
|
shell) load_config; lxc-attach -n "$CONTAINER" -- /bin/sh ;;
|
|
enable) uci set APPNAME.main.enabled=1; uci commit APPNAME; echo "Enabled" ;;
|
|
disable) uci set APPNAME.main.enabled=0; uci commit APPNAME; echo "Disabled" ;;
|
|
*) usage ;;
|
|
esac
|
|
CTLEOF
|
|
sed -i "s/APPNAME/$name/g" "$pkg_dir/files/usr/sbin/${name}ctl"
|
|
chmod +x "$pkg_dir/files/usr/sbin/${name}ctl"
|
|
|
|
log_info "Package generated: $pkg_dir"
|
|
echo ""
|
|
echo "To build:"
|
|
echo " 1. rsync -av $pkg_dir/ secubox-tools/local-feed/secubox-app-$name/"
|
|
echo " 2. ./secubox-tools/local-build.sh build secubox-app-$name"
|
|
}
|
|
|
|
# ==========================================
|
|
# Publish Command
|
|
# ==========================================
|
|
|
|
cmd_publish() {
|
|
local name="$1"
|
|
|
|
[ -z "$name" ] && { log_error "App name required"; return 1; }
|
|
|
|
local app_dir="$APPS_DIR/$name"
|
|
local meta_file="$app_dir/metadata.json"
|
|
local catalog_dir="/usr/share/secubox/plugins/catalog"
|
|
|
|
[ ! -f "$meta_file" ] && { log_error "App not found: $name"; return 1; }
|
|
|
|
# Load metadata
|
|
local source_image=$(jsonfilter -i "$meta_file" -e '@.source_image')
|
|
|
|
log_info "Publishing $name to catalog..."
|
|
|
|
mkdir -p "$catalog_dir"
|
|
|
|
cat > "$catalog_dir/$name.json" << CATEOF
|
|
{
|
|
"id": "$name",
|
|
"name": "$name",
|
|
"category": "utilities",
|
|
"runtime": "lxc",
|
|
"maturity": "community",
|
|
"description": "Converted from $source_image via RezApp Forge",
|
|
"source": {
|
|
"docker_image": "$source_image"
|
|
},
|
|
"packages": ["secubox-app-$name"],
|
|
"capabilities": ["lxc-container"],
|
|
"requirements": {
|
|
"arch": ["aarch64"],
|
|
"min_ram_mb": 256,
|
|
"min_storage_mb": 500
|
|
},
|
|
"actions": {
|
|
"install": "${name}ctl enable",
|
|
"status": "${name}ctl status"
|
|
}
|
|
}
|
|
CATEOF
|
|
|
|
log_info "Published to: $catalog_dir/$name.json"
|
|
}
|
|
|
|
# ==========================================
|
|
# List Command
|
|
# ==========================================
|
|
|
|
cmd_list() {
|
|
echo "Converted Apps:"
|
|
echo "==============="
|
|
|
|
if [ -d "$APPS_DIR" ]; then
|
|
for app in "$APPS_DIR"/*/metadata.json; do
|
|
[ -f "$app" ] || continue
|
|
local name=$(dirname "$app")
|
|
name="${name##*/}"
|
|
local image=$(jsonfilter -i "$app" -e '@.source_image' 2>/dev/null)
|
|
[ -z "$image" ] && image=$(jsonfilter -i "$app" -e '@.source' 2>/dev/null)
|
|
printf " %-20s %s\n" "$name" "$image"
|
|
done
|
|
else
|
|
echo " (none)"
|
|
fi
|
|
}
|
|
|
|
# ==========================================
|
|
# Cache Command (show cached images)
|
|
# ==========================================
|
|
|
|
cmd_cache() {
|
|
echo "Cached Images (offline ready):"
|
|
echo "==============================="
|
|
|
|
if [ -d "$CACHE_DIR" ]; then
|
|
local found=0
|
|
for tarball in "$CACHE_DIR"/*.tar; do
|
|
[ -f "$tarball" ] || continue
|
|
found=1
|
|
local name=$(basename "$tarball" .tar)
|
|
local size=$(du -h "$tarball" | cut -f1)
|
|
local inspect="$CACHE_DIR/${name}.inspect.json"
|
|
local image=""
|
|
if [ -f "$inspect" ]; then
|
|
image=$(jsonfilter -i "$inspect" -e '@[0].RepoTags[0]' 2>/dev/null)
|
|
fi
|
|
printf " %-20s %8s %s\n" "$name" "$size" "${image:-local}"
|
|
done
|
|
[ "$found" = "0" ] && echo " (none)"
|
|
else
|
|
echo " (none)"
|
|
fi
|
|
echo ""
|
|
echo "Convert offline: rezappctl convert --from-tar <file> --name <name>"
|
|
}
|
|
|
|
# ==========================================
|
|
# Run Command (start/test container)
|
|
# ==========================================
|
|
|
|
cmd_run() {
|
|
local name="$1"
|
|
local foreground=""
|
|
local shell=""
|
|
|
|
shift
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-f|--foreground) foreground="1"; shift ;;
|
|
-s|--shell) shell="1"; shift ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
|
|
[ -z "$name" ] && { log_error "App name required"; return 1; }
|
|
|
|
local lxc_path="$LXC_DIR/$name"
|
|
[ ! -d "$lxc_path" ] && { log_error "Container not found: $name"; return 1; }
|
|
|
|
# Check if already running
|
|
if lxc-info -n "$name" 2>/dev/null | grep -q "RUNNING"; then
|
|
if [ -n "$shell" ]; then
|
|
log_info "Opening shell in running container..."
|
|
lxc-attach -n "$name" -- /bin/sh
|
|
return $?
|
|
fi
|
|
log_warn "Container already running"
|
|
lxc-info -n "$name"
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "$foreground" ]; then
|
|
log_info "Starting $name in foreground (Ctrl+C to stop)..."
|
|
lxc-start -n "$name" -F
|
|
else
|
|
log_info "Starting $name..."
|
|
lxc-start -n "$name" -d
|
|
sleep 3
|
|
lxc-info -n "$name"
|
|
fi
|
|
}
|
|
|
|
cmd_stop() {
|
|
local name="$1"
|
|
[ -z "$name" ] && { log_error "App name required"; return 1; }
|
|
|
|
if lxc-info -n "$name" 2>/dev/null | grep -q "RUNNING"; then
|
|
log_info "Stopping $name..."
|
|
lxc-stop -n "$name"
|
|
else
|
|
log_warn "Container not running"
|
|
fi
|
|
}
|
|
|
|
# ==========================================
|
|
# Expose Command (HAProxy integration)
|
|
# ==========================================
|
|
|
|
cmd_expose() {
|
|
local name="$1"
|
|
local domain="$2"
|
|
local port="$3"
|
|
|
|
[ -z "$name" ] && { log_error "App name required"; return 1; }
|
|
|
|
local meta_file="$APPS_DIR/$name/metadata.json"
|
|
[ ! -f "$meta_file" ] && { log_error "App not found: $name"; return 1; }
|
|
|
|
# Auto-detect port from metadata if not specified
|
|
if [ -z "$port" ]; then
|
|
port=$(jsonfilter -i "$meta_file" -e '@.exposed_ports' 2>/dev/null | awk '{print $1}')
|
|
fi
|
|
[ -z "$port" ] && { log_error "Port required (or specify in metadata)"; return 1; }
|
|
|
|
# Default domain
|
|
[ -z "$domain" ] && domain="${name}.gk2.secubox.in"
|
|
|
|
log_info "Exposing $name on $domain (port $port)..."
|
|
|
|
# Check for haproxyctl
|
|
if ! command -v haproxyctl >/dev/null 2>&1; then
|
|
log_warn "haproxyctl not found - manual HAProxy config required"
|
|
echo ""
|
|
echo "Add to /srv/haproxy/config/haproxy.cfg:"
|
|
echo " acl host_${name} hdr(host) -i $domain"
|
|
echo " use_backend ${name}_backend if host_${name}"
|
|
echo ""
|
|
echo " backend ${name}_backend"
|
|
echo " mode http"
|
|
echo " server ${name} 192.168.255.1:$port check"
|
|
return 0
|
|
fi
|
|
|
|
# Add vhost via haproxyctl
|
|
haproxyctl vhost add "$domain"
|
|
|
|
# Add route to mitmproxy
|
|
local routes_file="/srv/mitmproxy-in/haproxy-routes.json"
|
|
if [ -f "$routes_file" ]; then
|
|
log_info "Adding mitmproxy route..."
|
|
# Use Python to safely add to JSON
|
|
python3 << PYEOF
|
|
import json
|
|
with open('$routes_file', 'r') as f:
|
|
routes = json.load(f)
|
|
routes['$domain'] = ['192.168.255.1', $port]
|
|
with open('$routes_file', 'w') as f:
|
|
json.dump(routes, f, indent=2)
|
|
print('Route added: $domain -> 192.168.255.1:$port')
|
|
PYEOF
|
|
fi
|
|
|
|
log_info "Exposed: https://$domain -> 192.168.255.1:$port"
|
|
echo ""
|
|
echo "Restart mitmproxy to apply: /etc/init.d/mitmproxy restart"
|
|
}
|
|
|
|
# ==========================================
|
|
# Help
|
|
# ==========================================
|
|
|
|
usage() {
|
|
cat << EOF
|
|
RezApp Forge - Docker/OCI to SecuBox LXC Converter
|
|
|
|
Usage: rezappctl <command> [options]
|
|
|
|
Catalog Commands:
|
|
catalog list List enabled catalogs
|
|
catalog add <name> [ns] Add catalog (optional namespace)
|
|
catalog remove <name> Remove catalog
|
|
|
|
Search:
|
|
search <query> Search Docker Hub (no runtime needed)
|
|
info <image> Show image details (no runtime needed)
|
|
|
|
Import (download for offline use):
|
|
import <image> [tag] Download and cache image for offline conversion
|
|
|
|
Convert:
|
|
convert <image> [opts] Convert Docker/OCI image to LXC
|
|
--name <name> App name (default: from image)
|
|
--tag <tag> Image tag (default: latest)
|
|
--memory <limit> Memory limit (default: 512M)
|
|
--network <type> Network: host, bridge, none (default: host)
|
|
|
|
Offline mode (no Docker/Podman needed):
|
|
--from-tar <file> Convert from local tarball
|
|
--from-oci <dir> Convert from OCI image directory
|
|
--offline Use cached tarball if available
|
|
|
|
Run & Manage:
|
|
run <name> [-f] [-s] Start container (-f=foreground, -s=shell)
|
|
stop <name> Stop container
|
|
list Show converted apps
|
|
cache Show cached images
|
|
|
|
Package & Publish:
|
|
package <name> Generate SecuBox package
|
|
publish <name> Add to app catalog
|
|
|
|
Expose (HAProxy integration):
|
|
expose <name> [domain] [port] Expose via HAProxy/mitmproxy
|
|
|
|
Runtime: Uses Docker, falls back to Podman if unavailable.
|
|
For offline conversion, use --from-tar or --from-oci flags.
|
|
|
|
Examples:
|
|
# Online workflow
|
|
rezappctl search heimdall
|
|
rezappctl convert linuxserver/heimdall --name heimdall
|
|
rezappctl run heimdall -f # Test in foreground
|
|
rezappctl expose heimdall # Expose via HAProxy
|
|
|
|
# Import for offline use
|
|
rezappctl import linuxserver/heimdall latest
|
|
rezappctl cache # Show cached images
|
|
|
|
# Offline workflow (no Docker needed)
|
|
rezappctl convert --from-tar /srv/rezapp/cache/heimdall.tar --name heimdall
|
|
|
|
# Package for distribution
|
|
rezappctl package heimdall
|
|
rezappctl publish heimdall
|
|
|
|
EOF
|
|
}
|
|
|
|
# ==========================================
|
|
# Main
|
|
# ==========================================
|
|
|
|
load_config
|
|
ensure_dirs
|
|
|
|
case "$1" in
|
|
catalog)
|
|
case "$2" in
|
|
list) cmd_catalog_list ;;
|
|
add) shift 2; cmd_catalog_add "$@" ;;
|
|
remove) shift 2; cmd_catalog_remove "$@" ;;
|
|
*) usage ;;
|
|
esac
|
|
;;
|
|
search) shift; cmd_search "$@" ;;
|
|
info) shift; cmd_info "$@" ;;
|
|
import) shift; cmd_import "$@" ;;
|
|
convert) shift; cmd_convert "$@" ;;
|
|
run) shift; cmd_run "$@" ;;
|
|
stop) shift; cmd_stop "$@" ;;
|
|
package) shift; cmd_package "$@" ;;
|
|
publish) shift; cmd_publish "$@" ;;
|
|
expose) shift; cmd_expose "$@" ;;
|
|
list) cmd_list ;;
|
|
cache) cmd_cache ;;
|
|
help|--help|-h) usage ;;
|
|
*) usage ;;
|
|
esac
|