feat(apps): Convert Docker-based apps to LXC
Converted secubox-app-jellyfin, secubox-app-mailserver, and added secubox-app-roundcube to use LXC containers instead of Docker. Changes: - jellyfinctl: Now uses LXC at 192.168.255.31 - mailserverctl: New controller for Alpine LXC with Postfix/Dovecot - roundcubectl: New package for Roundcube webmail LXC All controllers support: - Bootstrap Alpine rootfs using static apk - LXC configuration generation - HAProxy integration with waf_bypass - Start/stop/status commands Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3d78b22d85
commit
2b8fb1cd62
@ -1,27 +1,26 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=secubox-app-jellyfin
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_VERSION:=3.0.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_ARCH:=all
|
||||
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
PKG_MAINTAINER:=SecuBox <info@secubox.in>
|
||||
PKG_LICENSE:=AGPL-3.0
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/secubox-app-jellyfin
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
SECTION:=secubox
|
||||
CATEGORY:=SecuBox
|
||||
SUBMENU:=Apps
|
||||
PKGARCH:=all
|
||||
SUBMENU:=SecuBox Apps
|
||||
TITLE:=SecuBox Jellyfin media server
|
||||
DEPENDS:=+dockerd +docker +containerd
|
||||
TITLE:=SecuBox Jellyfin Media Server (LXC)
|
||||
DEPENDS:=+lxc +curl
|
||||
endef
|
||||
|
||||
define Package/secubox-app-jellyfin/description
|
||||
Installer, configuration, and service manager for running Jellyfin
|
||||
inside Docker on SecuBox-powered OpenWrt systems. Free media server
|
||||
for streaming movies, TV shows, music, and photos.
|
||||
Jellyfin media server running in LXC container.
|
||||
Free media server for streaming movies, TV shows, music, and photos.
|
||||
endef
|
||||
|
||||
define Package/secubox-app-jellyfin/conffiles
|
||||
@ -47,16 +46,15 @@ define Package/secubox-app-jellyfin/postinst
|
||||
[ -n "$${IPKG_INSTROOT}" ] || {
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " Jellyfin Media Server Installed"
|
||||
echo " Jellyfin Media Server (LXC) Installed"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Quick Start:"
|
||||
echo " 1. Install: jellyfinctl install"
|
||||
echo " 2. Configure media: uci add_list jellyfin.media.media_path='/path/to/media'"
|
||||
echo " 3. Commit: uci commit jellyfin"
|
||||
echo " 4. Start: /etc/init.d/jellyfin start"
|
||||
echo " 1. Extract rootfs from Docker image (see README)"
|
||||
echo " 2. Install: jellyfinctl install"
|
||||
echo " 3. Start: jellyfinctl start"
|
||||
echo ""
|
||||
echo "Web UI: http://<device-ip>:8096"
|
||||
echo "Web UI: http://192.168.255.31:8096"
|
||||
echo ""
|
||||
}
|
||||
exit 0
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
#!/bin/sh
|
||||
# SecuBox Jellyfin Media Server manager
|
||||
# Full integration: Docker, HAProxy, Firewall, Mesh P2P, Backup/Restore
|
||||
# LXC-based deployment with HAProxy, Firewall, Mesh P2P integration
|
||||
|
||||
VERSION="2.0.0"
|
||||
VERSION="3.0.0"
|
||||
CONFIG="jellyfin"
|
||||
CONTAINER="secbx-jellyfin"
|
||||
OPKG_UPDATED=0
|
||||
CONTAINER="jellyfin"
|
||||
LXC_PATH="/srv/lxc/jellyfin"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
@ -29,83 +29,68 @@ require_root() {
|
||||
}
|
||||
|
||||
defaults() {
|
||||
image="$(uci_get main.image)"
|
||||
[ -z "$image" ] && image="jellyfin/jellyfin:latest"
|
||||
data_path="$(uci_get main.data_path)"
|
||||
[ -z "$data_path" ] && data_path="/srv/jellyfin"
|
||||
media_path="$(uci_get main.media_path)"
|
||||
[ -z "$media_path" ] && media_path="/srv/SHARE"
|
||||
port="$(uci_get main.port)"
|
||||
[ -z "$port" ] && port="8096"
|
||||
timezone="$(uci_get main.timezone)"
|
||||
[ -z "$timezone" ] && timezone="Europe/Paris"
|
||||
hw_accel="$(uci_get transcoding.hw_accel)"
|
||||
[ -z "$hw_accel" ] && hw_accel="0"
|
||||
gpu_device="$(uci_get transcoding.gpu_device)"
|
||||
ip_address="$(uci_get main.ip_address)"
|
||||
[ -z "$ip_address" ] && ip_address="192.168.255.31"
|
||||
domain="$(uci_get network.domain)"
|
||||
[ -z "$domain" ] && domain="jellyfin.secubox.local"
|
||||
}
|
||||
|
||||
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
|
||||
|
||||
ensure_packages() {
|
||||
for pkg in "$@"; do
|
||||
if ! opkg status "$pkg" 2>/dev/null | grep -q "Status:.*installed"; then
|
||||
if [ "$OPKG_UPDATED" -eq 0 ]; then
|
||||
opkg update || return 1
|
||||
OPKG_UPDATED=1
|
||||
fi
|
||||
opkg install "$pkg" || return 1
|
||||
fi
|
||||
done
|
||||
# ============================================================================
|
||||
# LXC Helpers
|
||||
# ============================================================================
|
||||
|
||||
lxc_running() {
|
||||
lxc-info -n "$CONTAINER" 2>/dev/null | grep -q "State:.*RUNNING"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Prerequisite Checks
|
||||
# ============================================================================
|
||||
lxc_exists() {
|
||||
[ -d "$LXC_PATH/rootfs" ]
|
||||
}
|
||||
|
||||
check_prereqs() {
|
||||
create_lxc_config() {
|
||||
defaults
|
||||
ensure_dir "$data_path"
|
||||
[ -d /sys/fs/cgroup ] || { error "/sys/fs/cgroup missing"; return 1; }
|
||||
ensure_packages dockerd docker containerd
|
||||
/etc/init.d/dockerd enable >/dev/null 2>&1
|
||||
/etc/init.d/dockerd start >/dev/null 2>&1
|
||||
cat > "$LXC_PATH/config" << EOF
|
||||
lxc.uts.name = jellyfin
|
||||
lxc.rootfs.path = dir:${LXC_PATH}/rootfs
|
||||
lxc.net.0.type = veth
|
||||
lxc.net.0.link = br-lan
|
||||
lxc.net.0.flags = up
|
||||
lxc.net.0.ipv4.address = ${ip_address}/24
|
||||
lxc.net.0.ipv4.gateway = 192.168.255.1
|
||||
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
|
||||
lxc.mount.entry = ${media_path} srv/SHARE none bind,ro 0 0
|
||||
lxc.mount.entry = ${data_path}/config config none bind 0 0
|
||||
lxc.mount.entry = ${data_path}/cache cache none bind 0 0
|
||||
lxc.cap.drop = sys_module mac_admin mac_override sys_time
|
||||
lxc.seccomp.profile =
|
||||
lxc.tty.max = 0
|
||||
lxc.pty.max = 256
|
||||
lxc.cgroup2.memory.max = 2048000000
|
||||
lxc.init.cmd = /opt/start-jellyfin.sh
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Docker Helpers
|
||||
# ============================================================================
|
||||
create_startup_script() {
|
||||
cat > "$LXC_PATH/rootfs/opt/start-jellyfin.sh" << 'EOF'
|
||||
#!/bin/bash
|
||||
export PATH=/usr/lib/jellyfin-ffmpeg:$PATH
|
||||
export LD_LIBRARY_PATH=/usr/lib/jellyfin-ffmpeg/lib:$LD_LIBRARY_PATH
|
||||
|
||||
pull_image() {
|
||||
defaults
|
||||
docker pull "$image"
|
||||
}
|
||||
|
||||
stop_container() {
|
||||
docker stop "$CONTAINER" >/dev/null 2>&1 || true
|
||||
docker rm "$CONTAINER" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
build_media_mounts() {
|
||||
local mounts=""
|
||||
local paths
|
||||
paths=$(uci -q get ${CONFIG}.media.media_path)
|
||||
if [ -n "$paths" ]; then
|
||||
for p in $paths; do
|
||||
[ -d "$p" ] && mounts="$mounts -v ${p}:${p}:ro"
|
||||
done
|
||||
fi
|
||||
echo "$mounts"
|
||||
}
|
||||
|
||||
build_gpu_args() {
|
||||
if [ "$hw_accel" = "1" ]; then
|
||||
local dev="${gpu_device:-/dev/dri}"
|
||||
if [ -e "$dev" ]; then
|
||||
echo "--device=${dev}:${dev}"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
echo "Starting Jellyfin..."
|
||||
exec /jellyfin/jellyfin \
|
||||
--datadir=/config \
|
||||
--cachedir=/cache \
|
||||
--webdir=/jellyfin/jellyfin-web
|
||||
EOF
|
||||
chmod +x "$LXC_PATH/rootfs/opt/start-jellyfin.sh"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
@ -123,155 +108,39 @@ configure_haproxy() {
|
||||
|
||||
defaults
|
||||
|
||||
local ssl=$(uci_get network.haproxy_ssl)
|
||||
local ssl_redirect=$(uci_get network.haproxy_ssl_redirect)
|
||||
|
||||
# Check if vhost already exists (idempotent)
|
||||
local existing=$(uci show haproxy 2>/dev/null | grep "\.domain='$domain'" | head -1)
|
||||
if [ -n "$existing" ]; then
|
||||
log "HAProxy vhost for $domain already configured"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Configuring HAProxy for $domain..."
|
||||
|
||||
# Add backend
|
||||
uci -q add haproxy backend
|
||||
uci -q set haproxy.@backend[-1].name='jellyfin_web'
|
||||
uci -q set haproxy.@backend[-1].mode='http'
|
||||
uci -q add_list haproxy.@backend[-1].server="jellyfin 192.168.255.1:$port check"
|
||||
|
||||
# Add vhost
|
||||
uci -q add haproxy vhost
|
||||
uci -q set haproxy.@vhost[-1].enabled='1'
|
||||
uci -q set haproxy.@vhost[-1].domain="$domain"
|
||||
uci -q set haproxy.@vhost[-1].backend='jellyfin_web'
|
||||
uci -q set haproxy.@vhost[-1].ssl="${ssl:-1}"
|
||||
uci -q set haproxy.@vhost[-1].ssl_redirect="${ssl_redirect:-1}"
|
||||
uci -q set haproxy.@vhost[-1].websocket='1'
|
||||
|
||||
uci commit haproxy
|
||||
/etc/init.d/haproxy reload 2>/dev/null
|
||||
|
||||
log "HAProxy configured for $domain"
|
||||
}
|
||||
|
||||
remove_haproxy() {
|
||||
if ! command -v haproxyctl >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
defaults
|
||||
|
||||
log "Removing HAProxy configuration for $domain..."
|
||||
|
||||
# Find and remove backend
|
||||
local idx=0
|
||||
while uci -q get haproxy.@backend[$idx] >/dev/null 2>&1; do
|
||||
local name=$(uci -q get haproxy.@backend[$idx].name)
|
||||
if [ "$name" = "jellyfin_web" ]; then
|
||||
uci delete haproxy.@backend[$idx]
|
||||
break
|
||||
fi
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
|
||||
# Find and remove vhost
|
||||
idx=0
|
||||
while uci -q get haproxy.@vhost[$idx] >/dev/null 2>&1; do
|
||||
local vdomain=$(uci -q get haproxy.@vhost[$idx].domain)
|
||||
if [ "$vdomain" = "$domain" ]; then
|
||||
uci delete haproxy.@vhost[$idx]
|
||||
break
|
||||
fi
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
|
||||
uci commit haproxy
|
||||
/etc/init.d/haproxy reload 2>/dev/null
|
||||
|
||||
log "HAProxy configuration removed"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Firewall
|
||||
# ============================================================================
|
||||
|
||||
configure_firewall() {
|
||||
local fw_wan=$(uci_get network.firewall_wan)
|
||||
[ "$fw_wan" != "1" ] && { log "WAN firewall rule disabled in UCI"; return 0; }
|
||||
|
||||
defaults
|
||||
|
||||
# Idempotent: check if rule already exists
|
||||
if uci show firewall 2>/dev/null | grep -q "Jellyfin-HTTP"; then
|
||||
log "Firewall rule for Jellyfin already exists"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Configuring firewall for port $port..."
|
||||
|
||||
uci add firewall rule
|
||||
uci set firewall.@rule[-1].name='Jellyfin-HTTP'
|
||||
uci set firewall.@rule[-1].src='wan'
|
||||
uci set firewall.@rule[-1].dest_port="$port"
|
||||
uci set firewall.@rule[-1].proto='tcp'
|
||||
uci set firewall.@rule[-1].target='ACCEPT'
|
||||
uci set firewall.@rule[-1].enabled='1'
|
||||
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall reload 2>/dev/null
|
||||
|
||||
log "Firewall configured"
|
||||
}
|
||||
|
||||
remove_firewall() {
|
||||
log "Removing firewall rules..."
|
||||
|
||||
local idx=0
|
||||
while uci -q get firewall.@rule[$idx] >/dev/null 2>&1; do
|
||||
local name=$(uci -q get firewall.@rule[$idx].name)
|
||||
if [ "$name" = "Jellyfin-HTTP" ]; then
|
||||
uci delete firewall.@rule[$idx]
|
||||
uci commit firewall
|
||||
/etc/init.d/firewall reload 2>/dev/null
|
||||
log "Firewall rule removed"
|
||||
return 0
|
||||
fi
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Mesh Integration
|
||||
# ============================================================================
|
||||
|
||||
register_mesh_service() {
|
||||
local mesh_enabled=$(uci_get mesh.enabled)
|
||||
[ "$mesh_enabled" != "1" ] && return 0
|
||||
|
||||
defaults
|
||||
|
||||
if [ -x /usr/sbin/secubox-p2p ]; then
|
||||
/usr/sbin/secubox-p2p register-service jellyfin "$port" 2>/dev/null
|
||||
log "Registered Jellyfin with mesh network"
|
||||
# Check if backend already exists
|
||||
if uci -q get haproxy.jellyfin_web >/dev/null 2>&1; then
|
||||
log "HAProxy backend jellyfin_web already exists, updating..."
|
||||
uci set haproxy.jellyfin_web.server="media ${ip_address}:${port} weight 100 check"
|
||||
else
|
||||
warn "secubox-p2p not found, skipping mesh registration"
|
||||
log "Creating HAProxy backend..."
|
||||
uci set haproxy.jellyfin_web=backend
|
||||
uci set haproxy.jellyfin_web.name='jellyfin_web'
|
||||
uci set haproxy.jellyfin_web.mode='http'
|
||||
uci set haproxy.jellyfin_web.balance='roundrobin'
|
||||
uci set haproxy.jellyfin_web.enabled='1'
|
||||
uci set haproxy.jellyfin_web.server="media ${ip_address}:${port} weight 100 check"
|
||||
fi
|
||||
|
||||
local dns_enabled=$(uci -q get secubox-p2p.dns.enabled || echo "0")
|
||||
if [ "$dns_enabled" = "1" ]; then
|
||||
local dns_domain=$(uci -q get secubox-p2p.dns.base_domain || echo "mesh.local")
|
||||
local hostname=$(echo "$domain" | cut -d'.' -f1)
|
||||
log "Mesh DNS: $hostname.$dns_domain"
|
||||
# Check if vhost already exists
|
||||
local vhost_name=$(echo "$domain" | tr '.' '_')
|
||||
if ! uci -q get haproxy.${vhost_name} >/dev/null 2>&1; then
|
||||
log "Creating HAProxy vhost for $domain..."
|
||||
uci set haproxy.${vhost_name}=vhost
|
||||
uci set haproxy.${vhost_name}.domain="$domain"
|
||||
uci set haproxy.${vhost_name}.backend='jellyfin_web'
|
||||
uci set haproxy.${vhost_name}.ssl='1'
|
||||
uci set haproxy.${vhost_name}.ssl_redirect='1'
|
||||
uci set haproxy.${vhost_name}.acme='1'
|
||||
uci set haproxy.${vhost_name}.waf_bypass='1'
|
||||
uci set haproxy.${vhost_name}.enabled='1'
|
||||
fi
|
||||
}
|
||||
|
||||
unregister_mesh_service() {
|
||||
if [ -x /usr/sbin/secubox-p2p ]; then
|
||||
/usr/sbin/secubox-p2p unregister-service jellyfin 2>/dev/null
|
||||
log "Unregistered Jellyfin from mesh network"
|
||||
fi
|
||||
uci commit haproxy
|
||||
haproxyctl generate 2>/dev/null
|
||||
haproxyctl reload 2>/dev/null
|
||||
|
||||
log "HAProxy configured for $domain -> ${ip_address}:${port}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
@ -280,83 +149,94 @@ unregister_mesh_service() {
|
||||
|
||||
cmd_install() {
|
||||
require_root
|
||||
log "Installing Jellyfin Media Server..."
|
||||
log "Installing Jellyfin LXC container..."
|
||||
|
||||
check_prereqs || exit 1
|
||||
defaults
|
||||
|
||||
ensure_dir "$LXC_PATH"
|
||||
ensure_dir "$LXC_PATH/rootfs/srv/SHARE"
|
||||
ensure_dir "$LXC_PATH/rootfs/config"
|
||||
ensure_dir "$LXC_PATH/rootfs/cache"
|
||||
ensure_dir "$LXC_PATH/rootfs/opt"
|
||||
ensure_dir "$data_path/config"
|
||||
ensure_dir "$data_path/cache"
|
||||
|
||||
log "Pulling Docker image..."
|
||||
pull_image || exit 1
|
||||
if [ ! -f "$LXC_PATH/rootfs/jellyfin/jellyfin" ]; then
|
||||
log "Jellyfin rootfs not found. Please extract from Docker image:"
|
||||
echo " docker pull jellyfin/jellyfin:latest"
|
||||
echo " docker create --name temp-jellyfin jellyfin/jellyfin:latest"
|
||||
echo " docker export temp-jellyfin > /tmp/jellyfin.tar"
|
||||
echo " tar -xf /tmp/jellyfin.tar -C $LXC_PATH/rootfs/"
|
||||
echo " docker rm temp-jellyfin"
|
||||
return 1
|
||||
fi
|
||||
|
||||
create_lxc_config
|
||||
create_startup_script
|
||||
|
||||
# Symlink ffmpeg if needed
|
||||
if [ -d "$LXC_PATH/rootfs/usr/lib/jellyfin-ffmpeg" ]; then
|
||||
ln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg "$LXC_PATH/rootfs/usr/bin/ffmpeg" 2>/dev/null
|
||||
ln -sf /usr/lib/jellyfin-ffmpeg/ffprobe "$LXC_PATH/rootfs/usr/bin/ffprobe" 2>/dev/null
|
||||
fi
|
||||
|
||||
uci_set main.enabled '1'
|
||||
uci commit ${CONFIG}
|
||||
/etc/init.d/jellyfin enable
|
||||
|
||||
# Integrate with HAProxy if configured
|
||||
configure_haproxy
|
||||
|
||||
# Configure firewall if WAN access requested
|
||||
configure_firewall
|
||||
|
||||
# Register with mesh if enabled
|
||||
register_mesh_service
|
||||
|
||||
log "Jellyfin installed successfully!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Add media: uci add_list jellyfin.media.media_path='/path/to/media'"
|
||||
echo " 2. Set domain: uci set jellyfin.network.domain='media.example.com'"
|
||||
echo " 3. Commit: uci commit jellyfin"
|
||||
echo " 4. Start: /etc/init.d/jellyfin start"
|
||||
echo " Web UI: http://<device-ip>:${port}"
|
||||
echo ""
|
||||
log "Jellyfin LXC installed successfully!"
|
||||
log "Start with: jellyfinctl start"
|
||||
}
|
||||
|
||||
cmd_uninstall() {
|
||||
# ============================================================================
|
||||
# Service Control
|
||||
# ============================================================================
|
||||
|
||||
cmd_start() {
|
||||
require_root
|
||||
log "Uninstalling Jellyfin..."
|
||||
|
||||
/etc/init.d/jellyfin stop 2>/dev/null
|
||||
/etc/init.d/jellyfin disable 2>/dev/null
|
||||
stop_container
|
||||
if lxc_running; then
|
||||
log "Jellyfin already running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Remove integrations
|
||||
remove_haproxy
|
||||
remove_firewall
|
||||
unregister_mesh_service
|
||||
if ! lxc_exists; then
|
||||
error "Jellyfin not installed. Run 'jellyfinctl install' first"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Remove image
|
||||
defaults
|
||||
docker rmi "$image" 2>/dev/null
|
||||
create_lxc_config
|
||||
|
||||
uci_set main.enabled '0'
|
||||
uci commit ${CONFIG}
|
||||
log "Starting Jellyfin LXC..."
|
||||
lxc-start -n "$CONTAINER" -d
|
||||
sleep 5
|
||||
|
||||
log "Jellyfin uninstalled. Data preserved at $data_path"
|
||||
log "To remove data: rm -rf $data_path"
|
||||
if lxc_running; then
|
||||
log "Jellyfin started at http://${ip_address}:${port}"
|
||||
else
|
||||
error "Failed to start Jellyfin"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Check & Update
|
||||
# ============================================================================
|
||||
|
||||
cmd_check() {
|
||||
check_prereqs
|
||||
echo "Prerequisite check completed."
|
||||
}
|
||||
|
||||
cmd_update() {
|
||||
cmd_stop() {
|
||||
require_root
|
||||
log "Pulling latest image..."
|
||||
pull_image || exit 1
|
||||
log "Restarting service..."
|
||||
/etc/init.d/jellyfin restart
|
||||
# Prune old images
|
||||
docker image prune -f 2>/dev/null
|
||||
log "Update complete"
|
||||
|
||||
if ! lxc_running; then
|
||||
log "Jellyfin is not running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Stopping Jellyfin..."
|
||||
lxc-stop -n "$CONTAINER"
|
||||
log "Jellyfin stopped"
|
||||
}
|
||||
|
||||
cmd_restart() {
|
||||
cmd_stop
|
||||
sleep 2
|
||||
cmd_start
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
@ -368,86 +248,43 @@ cmd_status() {
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Jellyfin Media Server v$VERSION"
|
||||
echo " Jellyfin Media Server v$VERSION (LXC)"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
local enabled=$(uci_get main.enabled)
|
||||
echo "Configuration:"
|
||||
echo " Enabled: $([ "$enabled" = "1" ] && echo -e "${GREEN}Yes${NC}" || echo -e "${RED}No${NC}")"
|
||||
echo " Image: $image"
|
||||
echo " IP Address: $ip_address"
|
||||
echo " Port: $port"
|
||||
echo " Data: $data_path"
|
||||
echo " Media: $media_path"
|
||||
echo " Domain: $domain"
|
||||
echo ""
|
||||
|
||||
# Docker check
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo -e "Docker: ${RED}Not installed${NC}"
|
||||
return
|
||||
fi
|
||||
|
||||
# Container status
|
||||
echo "Container:"
|
||||
local state=$(docker inspect -f '{{.State.Status}}' "$CONTAINER" 2>/dev/null)
|
||||
if [ "$state" = "running" ]; then
|
||||
if lxc_running; then
|
||||
echo -e " Status: ${GREEN}Running${NC}"
|
||||
local uptime=$(docker ps --filter "name=$CONTAINER" --format '{{.Status}}' 2>/dev/null)
|
||||
echo " Uptime: $uptime"
|
||||
elif [ -n "$state" ]; then
|
||||
echo -e " Status: ${YELLOW}$state${NC}"
|
||||
local pid=$(lxc-info -n "$CONTAINER" 2>/dev/null | grep PID | awk '{print $2}')
|
||||
echo " PID: $pid"
|
||||
|
||||
# Health check
|
||||
local health=$(curl -s "http://${ip_address}:${port}/health" 2>/dev/null)
|
||||
if [ "$health" = "Healthy" ]; then
|
||||
echo -e " Health: ${GREEN}Healthy${NC}"
|
||||
else
|
||||
echo -e " Health: ${YELLOW}Starting...${NC}"
|
||||
fi
|
||||
elif lxc_exists; then
|
||||
echo -e " Status: ${YELLOW}Stopped${NC}"
|
||||
else
|
||||
echo -e " Status: ${RED}Not installed${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Media paths
|
||||
local paths=$(uci -q get ${CONFIG}.media.media_path)
|
||||
if [ -n "$paths" ]; then
|
||||
echo "Media Libraries:"
|
||||
for p in $paths; do
|
||||
if [ -d "$p" ]; then
|
||||
local count=$(ls -1 "$p" 2>/dev/null | wc -l)
|
||||
echo " $p ($count items)"
|
||||
else
|
||||
echo -e " $p ${RED}(not found)${NC}"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Integration status
|
||||
echo "Integrations:"
|
||||
local haproxy_enabled=$(uci_get network.haproxy)
|
||||
if [ "$haproxy_enabled" = "1" ]; then
|
||||
local vhost_exists=$(uci show haproxy 2>/dev/null | grep "\.domain='$domain'" | head -1)
|
||||
if [ -n "$vhost_exists" ]; then
|
||||
echo -e " HAProxy: ${GREEN}Configured${NC} ($domain)"
|
||||
else
|
||||
echo -e " HAProxy: ${YELLOW}Enabled but not configured${NC}"
|
||||
fi
|
||||
else
|
||||
echo " HAProxy: Disabled"
|
||||
fi
|
||||
|
||||
local mesh_enabled=$(uci_get mesh.enabled)
|
||||
if [ "$mesh_enabled" = "1" ]; then
|
||||
echo -e " Mesh P2P: ${GREEN}Enabled${NC}"
|
||||
else
|
||||
echo " Mesh P2P: Disabled"
|
||||
fi
|
||||
|
||||
local fw_wan=$(uci_get network.firewall_wan)
|
||||
if [ "$fw_wan" = "1" ]; then
|
||||
echo -e " Firewall: ${GREEN}WAN access on port $port${NC}"
|
||||
else
|
||||
echo " Firewall: LAN only"
|
||||
fi
|
||||
|
||||
# Disk usage
|
||||
# Storage
|
||||
if [ -d "$data_path" ]; then
|
||||
local disk=$(du -sh "$data_path" 2>/dev/null | cut -f1)
|
||||
echo ""
|
||||
echo "Storage:"
|
||||
echo " Data size: ${disk:-unknown}"
|
||||
fi
|
||||
@ -459,84 +296,21 @@ cmd_status() {
|
||||
# Logs & Shell
|
||||
# ============================================================================
|
||||
|
||||
cmd_logs() { docker logs "$@" "$CONTAINER" 2>&1; }
|
||||
cmd_logs() {
|
||||
defaults
|
||||
if [ -f "$data_path/config/log/log_$(date +%Y%m%d).log" ]; then
|
||||
tail "${@:--100}" "$data_path/config/log/log_$(date +%Y%m%d).log"
|
||||
else
|
||||
ls -la "$data_path/config/log/" 2>/dev/null || echo "No logs found"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_shell() {
|
||||
docker exec -it "$CONTAINER" /bin/bash 2>/dev/null || docker exec -it "$CONTAINER" /bin/sh
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Backup / Restore
|
||||
# ============================================================================
|
||||
|
||||
cmd_backup() {
|
||||
local backup_file="${1:-/tmp/jellyfin-backup-$(date +%Y%m%d-%H%M%S).tar.gz}"
|
||||
|
||||
defaults
|
||||
log "Creating backup..."
|
||||
|
||||
tar -czf "$backup_file" \
|
||||
-C / \
|
||||
etc/config/jellyfin \
|
||||
"${data_path#/}/config" \
|
||||
2>/dev/null
|
||||
|
||||
if [ -f "$backup_file" ]; then
|
||||
local size=$(ls -lh "$backup_file" | awk '{print $5}')
|
||||
log "Backup created: $backup_file ($size)"
|
||||
else
|
||||
error "Backup failed"
|
||||
if ! lxc_running; then
|
||||
error "Container not running"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_restore() {
|
||||
local backup_file="$1"
|
||||
|
||||
if [ -z "$backup_file" ] || [ ! -f "$backup_file" ]; then
|
||||
echo "Usage: jellyfinctl restore <backup_file>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
require_root
|
||||
log "Restoring from $backup_file..."
|
||||
|
||||
/etc/init.d/jellyfin stop 2>/dev/null
|
||||
tar -xzf "$backup_file" -C /
|
||||
/etc/init.d/jellyfin start
|
||||
|
||||
log "Restore complete"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Service Run (procd integration)
|
||||
# ============================================================================
|
||||
|
||||
cmd_service_run() {
|
||||
require_root
|
||||
check_prereqs || exit 1
|
||||
defaults
|
||||
stop_container
|
||||
|
||||
local media_mounts
|
||||
media_mounts=$(build_media_mounts)
|
||||
|
||||
local gpu_args
|
||||
gpu_args=$(build_gpu_args)
|
||||
|
||||
local docker_args="--name $CONTAINER"
|
||||
docker_args="$docker_args -p ${port}:8096"
|
||||
docker_args="$docker_args -v ${data_path}/config:/config"
|
||||
docker_args="$docker_args -v ${data_path}/cache:/cache"
|
||||
docker_args="$docker_args -e TZ=${timezone}"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
exec docker run --rm $docker_args $media_mounts $gpu_args "$image"
|
||||
}
|
||||
|
||||
cmd_service_stop() {
|
||||
require_root
|
||||
stop_container
|
||||
lxc-attach -n "$CONTAINER" -- /bin/bash 2>/dev/null || lxc-attach -n "$CONTAINER" -- /bin/sh
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
@ -545,61 +319,42 @@ cmd_service_stop() {
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Jellyfin Media Server Control v$VERSION
|
||||
Jellyfin Media Server Control v$VERSION (LXC)
|
||||
|
||||
Usage: jellyfinctl <command> [options]
|
||||
|
||||
Commands:
|
||||
install Install prerequisites, pull image, configure integrations
|
||||
uninstall Stop service, remove container and integrations
|
||||
check Run prerequisite checks
|
||||
update Pull latest image and restart
|
||||
status Show service and integration status
|
||||
install Install LXC container
|
||||
start Start Jellyfin
|
||||
stop Stop Jellyfin
|
||||
restart Restart Jellyfin
|
||||
status Show status
|
||||
|
||||
logs [-f] [--tail N] Show container logs
|
||||
logs [-f] [--tail N] Show logs
|
||||
shell Open shell inside container
|
||||
|
||||
configure-haproxy Configure/update HAProxy vhost
|
||||
remove-haproxy Remove HAProxy configuration
|
||||
configure-fw Configure firewall rules
|
||||
remove-fw Remove firewall rules
|
||||
register-mesh Register with mesh P2P network
|
||||
unregister-mesh Unregister from mesh network
|
||||
|
||||
backup [file] Create configuration backup
|
||||
restore <file> Restore from backup
|
||||
|
||||
service-run Internal: run container via procd
|
||||
service-stop Internal: stop container
|
||||
configure-haproxy Configure HAProxy vhost
|
||||
|
||||
Examples:
|
||||
jellyfinctl install
|
||||
jellyfinctl start
|
||||
jellyfinctl status
|
||||
jellyfinctl logs --tail 100
|
||||
jellyfinctl backup /tmp/jellyfin.tar.gz
|
||||
jellyfinctl configure-haproxy
|
||||
jellyfinctl logs -100
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
install) shift; cmd_install "$@" ;;
|
||||
uninstall) shift; cmd_uninstall "$@" ;;
|
||||
check) shift; cmd_check "$@" ;;
|
||||
update) shift; cmd_update "$@" ;;
|
||||
start) shift; cmd_start "$@" ;;
|
||||
stop) shift; cmd_stop "$@" ;;
|
||||
restart) shift; cmd_restart "$@" ;;
|
||||
status) shift; cmd_status "$@" ;;
|
||||
logs) shift; cmd_logs "$@" ;;
|
||||
shell) shift; cmd_shell "$@" ;;
|
||||
configure-haproxy) configure_haproxy ;;
|
||||
remove-haproxy) remove_haproxy ;;
|
||||
configure-fw) configure_firewall ;;
|
||||
remove-fw) remove_firewall ;;
|
||||
register-mesh) register_mesh_service ;;
|
||||
unregister-mesh) unregister_mesh_service ;;
|
||||
backup) shift; cmd_backup "$@" ;;
|
||||
restore) shift; cmd_restore "$@" ;;
|
||||
service-run) shift; cmd_service_run "$@" ;;
|
||||
service-stop) shift; cmd_service_stop "$@" ;;
|
||||
service-run) cmd_start ;;
|
||||
service-stop) cmd_stop ;;
|
||||
help|--help|-h|'') show_help ;;
|
||||
*) error "Unknown command: $1"; show_help >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
@ -1,39 +1,34 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=secubox-app-mailserver
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=2
|
||||
PKG_VERSION:=2.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_MAINTAINER:=SecuBox Team
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_MAINTAINER:=SecuBox <info@secubox.in>
|
||||
PKG_LICENSE:=AGPL-3.0
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
SECTION:=secubox
|
||||
CATEGORY:=SecuBox
|
||||
TITLE:=SecuBox Mail Server Manager
|
||||
DEPENDS:=+lxc +secubox-app-dns-provider
|
||||
SUBMENU:=Apps
|
||||
TITLE:=SecuBox Mail Server (LXC)
|
||||
DEPENDS:=+lxc +curl +openssl-util
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
Custom mail server (Postfix + Dovecot) in LXC with mesh backup support.
|
||||
Integrates with dnsctl for MX/SPF/DKIM/DMARC management.
|
||||
Postfix + Dovecot mail server running in LXC container.
|
||||
Supports IMAP/SMTP with SSL/TLS.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/etc/config/mailserver $(1)/etc/config/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./files/etc/init.d/mailserver $(1)/etc/init.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./files/usr/sbin/mailctl $(1)/usr/sbin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/lib/mailserver
|
||||
$(INSTALL_DATA) ./files/usr/lib/mailserver/*.sh $(1)/usr/lib/mailserver/
|
||||
$(INSTALL_BIN) ./files/usr/sbin/mailserverctl $(1)/usr/sbin/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
|
||||
@ -0,0 +1,570 @@
|
||||
#!/bin/sh
|
||||
# SecuBox Mailserver Controller
|
||||
# LXC-based Postfix + Dovecot mail server
|
||||
|
||||
VERSION="2.0.0"
|
||||
CONFIG="mailserver"
|
||||
CONTAINER="mailserver"
|
||||
LXC_PATH="/srv/lxc/mailserver"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[MAILSERVER]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Helpers
|
||||
# ============================================================================
|
||||
|
||||
uci_get() { uci -q get ${CONFIG}.$1; }
|
||||
uci_set() { uci -q set ${CONFIG}.$1="$2"; }
|
||||
|
||||
require_root() {
|
||||
[ "$(id -u)" -eq 0 ] || { error "Root required"; exit 1; }
|
||||
}
|
||||
|
||||
defaults() {
|
||||
ip_address="$(uci_get main.ip_address)"
|
||||
[ -z "$ip_address" ] && ip_address="192.168.255.30"
|
||||
domain="$(uci_get main.domain)"
|
||||
[ -z "$domain" ] && domain="secubox.in"
|
||||
hostname="$(uci_get main.hostname)"
|
||||
[ -z "$hostname" ] && hostname="mail.$domain"
|
||||
}
|
||||
|
||||
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
|
||||
|
||||
# ============================================================================
|
||||
# LXC Helpers
|
||||
# ============================================================================
|
||||
|
||||
lxc_running() {
|
||||
lxc-info -n "$CONTAINER" 2>/dev/null | grep -q "State:.*RUNNING"
|
||||
}
|
||||
|
||||
lxc_exists() {
|
||||
[ -d "$LXC_PATH/rootfs" ]
|
||||
}
|
||||
|
||||
create_lxc_config() {
|
||||
defaults
|
||||
cat > "$LXC_PATH/config" << EOF
|
||||
lxc.uts.name = mailserver
|
||||
lxc.rootfs.path = dir:${LXC_PATH}/rootfs
|
||||
lxc.net.0.type = veth
|
||||
lxc.net.0.link = br-lan
|
||||
lxc.net.0.flags = up
|
||||
lxc.net.0.ipv4.address = ${ip_address}/24
|
||||
lxc.net.0.ipv4.gateway = 192.168.255.1
|
||||
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
|
||||
lxc.cap.drop = sys_module mac_admin mac_override sys_time
|
||||
lxc.seccomp.profile =
|
||||
lxc.tty.max = 0
|
||||
lxc.pty.max = 256
|
||||
lxc.cgroup2.memory.max = 512000000
|
||||
lxc.init.cmd = /opt/start-mail.sh
|
||||
EOF
|
||||
}
|
||||
|
||||
create_startup_script() {
|
||||
cat > "$LXC_PATH/rootfs/opt/start-mail.sh" << 'EOF'
|
||||
#!/bin/sh
|
||||
# Mailserver startup script
|
||||
|
||||
# Start services
|
||||
/usr/sbin/rsyslogd
|
||||
sleep 1
|
||||
/usr/sbin/postfix start
|
||||
/usr/sbin/dovecot
|
||||
echo "Mail services started"
|
||||
|
||||
# Keep container running
|
||||
exec tail -f /var/log/dovecot.log /var/log/messages 2>/dev/null || exec sleep infinity
|
||||
EOF
|
||||
chmod +x "$LXC_PATH/rootfs/opt/start-mail.sh"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Alpine Bootstrap
|
||||
# ============================================================================
|
||||
|
||||
bootstrap_alpine() {
|
||||
require_root
|
||||
defaults
|
||||
|
||||
log "Bootstrapping Alpine Linux rootfs..."
|
||||
|
||||
ensure_dir "$LXC_PATH"
|
||||
|
||||
# Download apk-tools-static
|
||||
cd "$LXC_PATH"
|
||||
if [ ! -f sbin/apk.static ]; then
|
||||
log "Downloading apk-tools-static..."
|
||||
curl -L -o apk-tools-static.apk \
|
||||
"https://dl-cdn.alpinelinux.org/alpine/v3.21/main/aarch64/apk-tools-static-2.14.6-r3.apk"
|
||||
tar -xzf apk-tools-static.apk sbin/apk.static
|
||||
rm -f apk-tools-static.apk
|
||||
fi
|
||||
|
||||
# Bootstrap rootfs
|
||||
log "Installing base system..."
|
||||
./sbin/apk.static -X https://dl-cdn.alpinelinux.org/alpine/v3.21/main \
|
||||
-U --allow-untrusted --root rootfs --initdb add \
|
||||
alpine-base alpine-baselayout busybox musl openrc
|
||||
|
||||
# Set up repositories
|
||||
mkdir -p rootfs/etc/apk
|
||||
cat > rootfs/etc/apk/repositories << 'REPOEOF'
|
||||
https://dl-cdn.alpinelinux.org/alpine/v3.21/main
|
||||
https://dl-cdn.alpinelinux.org/alpine/v3.21/community
|
||||
REPOEOF
|
||||
|
||||
# Set up DNS
|
||||
cat > rootfs/etc/resolv.conf << 'DNSEOF'
|
||||
nameserver 8.8.8.8
|
||||
nameserver 1.1.1.1
|
||||
DNSEOF
|
||||
|
||||
# Set hostname
|
||||
echo "mailserver" > rootfs/etc/hostname
|
||||
|
||||
log "Base system installed"
|
||||
}
|
||||
|
||||
install_mail_packages() {
|
||||
require_root
|
||||
|
||||
if ! lxc_running; then
|
||||
log "Starting container for package installation..."
|
||||
lxc-start -n "$CONTAINER" -d
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
log "Installing mail packages..."
|
||||
lxc-attach -n "$CONTAINER" -- apk update
|
||||
lxc-attach -n "$CONTAINER" -- apk add --no-cache \
|
||||
postfix \
|
||||
dovecot \
|
||||
dovecot-lmtpd \
|
||||
ca-certificates \
|
||||
openssl \
|
||||
rsyslog
|
||||
|
||||
log "Packages installed"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Mail Configuration
|
||||
# ============================================================================
|
||||
|
||||
configure_postfix() {
|
||||
defaults
|
||||
local rootfs="$LXC_PATH/rootfs"
|
||||
|
||||
log "Configuring Postfix..."
|
||||
|
||||
cat > "$rootfs/etc/postfix/main.cf" << EOF
|
||||
# Basic config
|
||||
myhostname = $hostname
|
||||
mydomain = $domain
|
||||
myorigin = \$mydomain
|
||||
mydestination = \$myhostname, localhost.\$mydomain, localhost
|
||||
mynetworks = 127.0.0.0/8 [::1]/128 192.168.255.0/24
|
||||
|
||||
# Virtual mailbox
|
||||
virtual_mailbox_domains = $domain
|
||||
virtual_mailbox_base = /var/mail
|
||||
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
|
||||
virtual_uid_maps = static:102
|
||||
virtual_gid_maps = static:105
|
||||
virtual_transport = lmtp:unix:private/dovecot-lmtp
|
||||
|
||||
# SASL auth via Dovecot
|
||||
smtpd_sasl_auth_enable = yes
|
||||
smtpd_sasl_type = dovecot
|
||||
smtpd_sasl_path = private/auth
|
||||
smtpd_sasl_security_options = noanonymous
|
||||
smtpd_sasl_local_domain = \$mydomain
|
||||
broken_sasl_auth_clients = yes
|
||||
|
||||
# TLS
|
||||
smtpd_tls_cert_file = /etc/ssl/certs/mail.crt
|
||||
smtpd_tls_key_file = /etc/ssl/private/mail.key
|
||||
smtpd_tls_security_level = may
|
||||
smtp_tls_security_level = may
|
||||
|
||||
# Restrictions
|
||||
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
|
||||
smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks
|
||||
|
||||
# Limits
|
||||
mailbox_size_limit = 0
|
||||
message_size_limit = 52428800
|
||||
inet_interfaces = all
|
||||
inet_protocols = ipv4
|
||||
EOF
|
||||
|
||||
cat > "$rootfs/etc/postfix/master.cf" << 'EOF'
|
||||
smtp inet n - n - - smtpd
|
||||
submission inet n - n - - smtpd
|
||||
-o syslog_name=postfix/submission
|
||||
-o smtpd_tls_security_level=encrypt
|
||||
-o smtpd_sasl_auth_enable=yes
|
||||
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
||||
smtps inet n - n - - smtpd
|
||||
-o syslog_name=postfix/smtps
|
||||
-o smtpd_tls_wrappermode=yes
|
||||
-o smtpd_sasl_auth_enable=yes
|
||||
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
|
||||
pickup unix n - n 60 1 pickup
|
||||
cleanup unix n - n - 0 cleanup
|
||||
qmgr unix n - n 300 1 qmgr
|
||||
tlsmgr unix - - n 1000? 1 tlsmgr
|
||||
rewrite unix - - n - - trivial-rewrite
|
||||
bounce unix - - n - 0 bounce
|
||||
defer unix - - n - 0 bounce
|
||||
trace unix - - n - 0 bounce
|
||||
verify unix - - n - 1 verify
|
||||
flush unix n - n 1000? 0 flush
|
||||
proxymap unix - - n - - proxymap
|
||||
proxywrite unix - - n - 1 proxymap
|
||||
smtp unix - - n - - smtp
|
||||
relay unix - - n - - smtp
|
||||
showq unix n - n - - showq
|
||||
error unix - - n - - error
|
||||
retry unix - - n - - error
|
||||
discard unix - - n - - discard
|
||||
local unix - n n - - local
|
||||
virtual unix - n n - - virtual
|
||||
lmtp unix - - n - - lmtp
|
||||
anvil unix - - n - 1 anvil
|
||||
scache unix - - n - 1 scache
|
||||
EOF
|
||||
|
||||
log "Postfix configured"
|
||||
}
|
||||
|
||||
configure_dovecot() {
|
||||
defaults
|
||||
local rootfs="$LXC_PATH/rootfs"
|
||||
|
||||
log "Configuring Dovecot..."
|
||||
|
||||
cat > "$rootfs/etc/dovecot/dovecot.conf" << 'EOF'
|
||||
protocols = imap lmtp
|
||||
listen = *
|
||||
mail_location = maildir:/var/mail/%d/%n
|
||||
mail_uid = 102
|
||||
mail_gid = 105
|
||||
first_valid_uid = 102
|
||||
last_valid_uid = 102
|
||||
|
||||
# Auth
|
||||
auth_mechanisms = plain login
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = /etc/dovecot/users
|
||||
}
|
||||
userdb {
|
||||
driver = static
|
||||
args = uid=102 gid=105 home=/var/mail/%d/%n
|
||||
}
|
||||
|
||||
# SSL
|
||||
ssl = yes
|
||||
ssl_cert = </etc/ssl/certs/mail.crt
|
||||
ssl_key = </etc/ssl/private/mail.key
|
||||
|
||||
# Services
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
port = 143
|
||||
}
|
||||
inet_listener imaps {
|
||||
port = 993
|
||||
ssl = yes
|
||||
}
|
||||
}
|
||||
|
||||
service lmtp {
|
||||
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
||||
mode = 0600
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
}
|
||||
|
||||
service auth {
|
||||
unix_listener /var/spool/postfix/private/auth {
|
||||
mode = 0660
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
}
|
||||
|
||||
namespace inbox {
|
||||
inbox = yes
|
||||
separator = /
|
||||
}
|
||||
|
||||
log_path = /var/log/dovecot.log
|
||||
info_log_path = /var/log/dovecot-info.log
|
||||
EOF
|
||||
|
||||
log "Dovecot configured"
|
||||
}
|
||||
|
||||
generate_ssl_cert() {
|
||||
local rootfs="$LXC_PATH/rootfs"
|
||||
defaults
|
||||
|
||||
log "Generating SSL certificate..."
|
||||
|
||||
mkdir -p "$rootfs/etc/ssl/private" "$rootfs/etc/ssl/certs"
|
||||
|
||||
openssl req -x509 -nodes -days 3650 \
|
||||
-newkey rsa:2048 \
|
||||
-keyout "$rootfs/etc/ssl/private/mail.key" \
|
||||
-out "$rootfs/etc/ssl/certs/mail.crt" \
|
||||
-subj "/CN=$hostname/O=SecuBox/C=FR" 2>/dev/null
|
||||
|
||||
chmod 600 "$rootfs/etc/ssl/private/mail.key"
|
||||
log "SSL certificate generated"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# User Management
|
||||
# ============================================================================
|
||||
|
||||
cmd_add_user() {
|
||||
local email="$1"
|
||||
local password="$2"
|
||||
|
||||
if [ -z "$email" ] || [ -z "$password" ]; then
|
||||
echo "Usage: mailserverctl add-user <email> <password>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local user=$(echo "$email" | cut -d@ -f1)
|
||||
local domain=$(echo "$email" | cut -d@ -f2)
|
||||
local rootfs="$LXC_PATH/rootfs"
|
||||
|
||||
# Create mailbox entry
|
||||
echo "$email ${domain}/${user}/" >> "$rootfs/etc/postfix/vmailbox"
|
||||
|
||||
# Generate password hash and add to users file
|
||||
if lxc_running; then
|
||||
local pass_hash=$(lxc-attach -n "$CONTAINER" -- doveadm pw -s SHA512-CRYPT -p "$password")
|
||||
echo "${email}:${pass_hash}:102:105::/var/mail/${domain}/${user}::" >> "$rootfs/etc/dovecot/users"
|
||||
else
|
||||
error "Container not running. Start it first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Create mail directory
|
||||
mkdir -p "$rootfs/var/mail/${domain}/${user}"
|
||||
lxc-attach -n "$CONTAINER" -- chown -R vmail:vmail "/var/mail/${domain}"
|
||||
|
||||
# Rebuild postfix maps
|
||||
lxc-attach -n "$CONTAINER" -- postmap /etc/postfix/vmailbox
|
||||
|
||||
log "User $email added"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Service Control
|
||||
# ============================================================================
|
||||
|
||||
cmd_install() {
|
||||
require_root
|
||||
log "Installing Mailserver LXC..."
|
||||
|
||||
defaults
|
||||
|
||||
if lxc_exists; then
|
||||
log "Container already exists"
|
||||
else
|
||||
bootstrap_alpine
|
||||
fi
|
||||
|
||||
create_lxc_config
|
||||
create_startup_script
|
||||
|
||||
# Start for package installation
|
||||
lxc-start -n "$CONTAINER" -d
|
||||
sleep 3
|
||||
|
||||
if lxc_running; then
|
||||
install_mail_packages
|
||||
lxc-stop -n "$CONTAINER"
|
||||
fi
|
||||
|
||||
configure_postfix
|
||||
configure_dovecot
|
||||
generate_ssl_cert
|
||||
|
||||
# Create vmail user directories
|
||||
local rootfs="$LXC_PATH/rootfs"
|
||||
mkdir -p "$rootfs/var/mail"
|
||||
mkdir -p "$rootfs/var/spool/postfix/private"
|
||||
|
||||
# Create minimal interfaces file
|
||||
cat > "$rootfs/etc/network/interfaces" << 'EOF'
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
EOF
|
||||
|
||||
uci_set main.enabled '1'
|
||||
uci commit ${CONFIG}
|
||||
|
||||
log "Mailserver installed!"
|
||||
log "Add users with: mailserverctl add-user user@domain.com password"
|
||||
log "Start with: mailserverctl start"
|
||||
}
|
||||
|
||||
cmd_start() {
|
||||
require_root
|
||||
|
||||
if lxc_running; then
|
||||
log "Mailserver already running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! lxc_exists; then
|
||||
error "Mailserver not installed. Run 'mailserverctl install' first"
|
||||
return 1
|
||||
fi
|
||||
|
||||
defaults
|
||||
create_lxc_config
|
||||
|
||||
log "Starting Mailserver LXC..."
|
||||
lxc-start -n "$CONTAINER" -d
|
||||
sleep 5
|
||||
|
||||
if lxc_running; then
|
||||
log "Mailserver started at $ip_address"
|
||||
log "IMAP: ${ip_address}:993 (SSL)"
|
||||
log "SMTP: ${ip_address}:465 (SSL)"
|
||||
else
|
||||
error "Failed to start Mailserver"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_stop() {
|
||||
require_root
|
||||
|
||||
if ! lxc_running; then
|
||||
log "Mailserver is not running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Stopping Mailserver..."
|
||||
lxc-stop -n "$CONTAINER"
|
||||
log "Mailserver stopped"
|
||||
}
|
||||
|
||||
cmd_restart() {
|
||||
cmd_stop
|
||||
sleep 2
|
||||
cmd_start
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
defaults
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " SecuBox Mailserver v$VERSION (LXC)"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
echo "Configuration:"
|
||||
echo " Domain: $domain"
|
||||
echo " Hostname: $hostname"
|
||||
echo " IP Address: $ip_address"
|
||||
echo ""
|
||||
|
||||
echo "Container:"
|
||||
if lxc_running; then
|
||||
echo -e " Status: ${GREEN}Running${NC}"
|
||||
local pid=$(lxc-info -n "$CONTAINER" 2>/dev/null | grep PID | awk '{print $2}')
|
||||
echo " PID: $pid"
|
||||
|
||||
# Test IMAP
|
||||
local imap_test=$(echo "a LOGOUT" | openssl s_client -connect ${ip_address}:993 -quiet 2>/dev/null | head -1)
|
||||
if echo "$imap_test" | grep -q "OK"; then
|
||||
echo -e " IMAP: ${GREEN}OK${NC}"
|
||||
else
|
||||
echo -e " IMAP: ${YELLOW}Not responding${NC}"
|
||||
fi
|
||||
elif lxc_exists; then
|
||||
echo -e " Status: ${YELLOW}Stopped${NC}"
|
||||
else
|
||||
echo -e " Status: ${RED}Not installed${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Ports:"
|
||||
echo " SMTP: 25, 465 (SSL), 587 (submission)"
|
||||
echo " IMAP: 143, 993 (SSL)"
|
||||
echo ""
|
||||
}
|
||||
|
||||
cmd_shell() {
|
||||
if ! lxc_running; then
|
||||
error "Container not running"
|
||||
return 1
|
||||
fi
|
||||
lxc-attach -n "$CONTAINER" -- /bin/sh
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
SecuBox Mailserver Control v$VERSION (LXC)
|
||||
|
||||
Usage: mailserverctl <command> [options]
|
||||
|
||||
Commands:
|
||||
install Install LXC mail server
|
||||
start Start mail server
|
||||
stop Stop mail server
|
||||
restart Restart mail server
|
||||
status Show status
|
||||
|
||||
add-user <email> <pass> Add mail user
|
||||
shell Open shell in container
|
||||
|
||||
Examples:
|
||||
mailserverctl install
|
||||
mailserverctl add-user admin@example.com MyPassword123
|
||||
mailserverctl start
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
install) shift; cmd_install "$@" ;;
|
||||
start) shift; cmd_start "$@" ;;
|
||||
stop) shift; cmd_stop "$@" ;;
|
||||
restart) shift; cmd_restart "$@" ;;
|
||||
status) shift; cmd_status "$@" ;;
|
||||
add-user) shift; cmd_add_user "$@" ;;
|
||||
shell) shift; cmd_shell "$@" ;;
|
||||
service-run) cmd_start ;;
|
||||
service-stop) cmd_stop ;;
|
||||
help|--help|-h|'') show_help ;;
|
||||
*) error "Unknown command: $1"; show_help >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
34
package/secubox/secubox-app-roundcube/Makefile
Normal file
34
package/secubox/secubox-app-roundcube/Makefile
Normal file
@ -0,0 +1,34 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=secubox-app-roundcube
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_MAINTAINER:=SecuBox <info@secubox.in>
|
||||
PKG_LICENSE:=AGPL-3.0
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/secubox-app-roundcube
|
||||
SECTION:=secubox
|
||||
CATEGORY:=SecuBox
|
||||
SUBMENU:=Apps
|
||||
TITLE:=Roundcube Webmail (LXC)
|
||||
DEPENDS:=+lxc +curl
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/secubox-app-roundcube/description
|
||||
Roundcube Webmail running in LXC container with nginx and PHP-FPM.
|
||||
Provides web-based email client for IMAP servers.
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/secubox-app-roundcube/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./files/usr/sbin/roundcubectl $(1)/usr/sbin/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,secubox-app-roundcube))
|
||||
@ -0,0 +1,437 @@
|
||||
#!/bin/sh
|
||||
# SecuBox Roundcube Webmail Controller
|
||||
# LXC-based nginx + PHP-FPM + Roundcube
|
||||
|
||||
VERSION="1.0.0"
|
||||
CONFIG="roundcube"
|
||||
CONTAINER="roundcube"
|
||||
LXC_PATH="/srv/lxc/roundcube"
|
||||
ROUNDCUBE_VERSION="1.6.12"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[ROUNDCUBE]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
uci_get() { uci -q get ${CONFIG}.$1; }
|
||||
uci_set() { uci -q set ${CONFIG}.$1="$2"; }
|
||||
|
||||
require_root() {
|
||||
[ "$(id -u)" -eq 0 ] || { error "Root required"; exit 1; }
|
||||
}
|
||||
|
||||
defaults() {
|
||||
port="$(uci_get main.port)"
|
||||
[ -z "$port" ] && port="8027"
|
||||
mail_host="$(uci_get main.mail_host)"
|
||||
[ -z "$mail_host" ] && mail_host="192.168.255.30"
|
||||
domain="$(uci_get main.domain)"
|
||||
[ -z "$domain" ] && domain="webmail.gk2.secubox.in"
|
||||
}
|
||||
|
||||
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
|
||||
|
||||
# ============================================================================
|
||||
# LXC Helpers
|
||||
# ============================================================================
|
||||
|
||||
lxc_running() {
|
||||
lxc-info -n "$CONTAINER" 2>/dev/null | grep -q "State:.*RUNNING"
|
||||
}
|
||||
|
||||
lxc_exists() {
|
||||
[ -d "$LXC_PATH/rootfs" ]
|
||||
}
|
||||
|
||||
create_lxc_config() {
|
||||
defaults
|
||||
cat > "$LXC_PATH/config" << EOF
|
||||
lxc.uts.name = roundcube
|
||||
lxc.rootfs.path = dir:${LXC_PATH}/rootfs
|
||||
lxc.net.0.type = none
|
||||
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
|
||||
lxc.cap.drop = sys_module mac_admin mac_override sys_time
|
||||
lxc.seccomp.profile =
|
||||
lxc.tty.max = 0
|
||||
lxc.pty.max = 256
|
||||
lxc.cgroup2.memory.max = 128000000
|
||||
lxc.init.cmd = /opt/start-roundcube.sh
|
||||
EOF
|
||||
}
|
||||
|
||||
create_startup_script() {
|
||||
cat > "$LXC_PATH/rootfs/opt/start-roundcube.sh" << 'EOF'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Initialize SQLite database if not exists
|
||||
if [ ! -f /var/www/roundcube/db/roundcube.db ]; then
|
||||
echo "Initializing Roundcube database..."
|
||||
mkdir -p /var/www/roundcube/db
|
||||
cd /var/www/roundcube
|
||||
sqlite3 db/roundcube.db < SQL/sqlite.initial.sql
|
||||
chown nginx:nginx db/roundcube.db
|
||||
chmod 640 db/roundcube.db
|
||||
echo "Database initialized"
|
||||
fi
|
||||
|
||||
# Start PHP-FPM
|
||||
echo "Starting PHP-FPM..."
|
||||
php-fpm84 -D
|
||||
|
||||
# Start nginx in foreground
|
||||
echo "Starting nginx..."
|
||||
exec nginx -g "daemon off;"
|
||||
EOF
|
||||
chmod +x "$LXC_PATH/rootfs/opt/start-roundcube.sh"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Installation
|
||||
# ============================================================================
|
||||
|
||||
bootstrap_alpine() {
|
||||
require_root
|
||||
log "Bootstrapping Alpine Linux rootfs..."
|
||||
|
||||
ensure_dir "$LXC_PATH"
|
||||
cd "$LXC_PATH"
|
||||
|
||||
if [ ! -f sbin/apk.static ]; then
|
||||
log "Downloading apk-tools-static..."
|
||||
curl -L -o apk-tools-static.apk \
|
||||
"https://dl-cdn.alpinelinux.org/alpine/v3.21/main/aarch64/apk-tools-static-2.14.6-r3.apk"
|
||||
tar -xzf apk-tools-static.apk sbin/apk.static
|
||||
rm -f apk-tools-static.apk
|
||||
fi
|
||||
|
||||
log "Installing base system..."
|
||||
./sbin/apk.static -X https://dl-cdn.alpinelinux.org/alpine/v3.21/main \
|
||||
-U --allow-untrusted --root rootfs --initdb add \
|
||||
alpine-base alpine-baselayout busybox musl
|
||||
|
||||
mkdir -p rootfs/etc/apk
|
||||
cat > rootfs/etc/apk/repositories << 'EOF'
|
||||
https://dl-cdn.alpinelinux.org/alpine/v3.21/main
|
||||
https://dl-cdn.alpinelinux.org/alpine/v3.21/community
|
||||
EOF
|
||||
|
||||
cat > rootfs/etc/resolv.conf << 'EOF'
|
||||
nameserver 8.8.8.8
|
||||
nameserver 1.1.1.1
|
||||
EOF
|
||||
|
||||
log "Base system installed"
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
require_root
|
||||
|
||||
if ! lxc_running; then
|
||||
log "Starting container for package installation..."
|
||||
lxc-start -n "$CONTAINER" -d
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
log "Installing packages..."
|
||||
lxc-attach -n "$CONTAINER" -- apk update
|
||||
lxc-attach -n "$CONTAINER" -- apk add --no-cache \
|
||||
nginx \
|
||||
php84 php84-fpm php84-imap php84-mbstring php84-openssl \
|
||||
php84-session php84-pdo php84-pdo_sqlite php84-sqlite3 \
|
||||
php84-xml php84-dom php84-intl php84-zip php84-gd \
|
||||
php84-ctype php84-json php84-fileinfo php84-ldap \
|
||||
sqlite curl
|
||||
|
||||
log "Packages installed"
|
||||
}
|
||||
|
||||
download_roundcube() {
|
||||
local rootfs="$LXC_PATH/rootfs"
|
||||
|
||||
log "Downloading Roundcube $ROUNDCUBE_VERSION..."
|
||||
|
||||
mkdir -p "$rootfs/var/www"
|
||||
curl -L -o "$rootfs/tmp/roundcube.tar.gz" \
|
||||
"https://github.com/roundcube/roundcubemail/releases/download/${ROUNDCUBE_VERSION}/roundcubemail-${ROUNDCUBE_VERSION}-complete.tar.gz"
|
||||
|
||||
tar -xzf "$rootfs/tmp/roundcube.tar.gz" -C "$rootfs/var/www/"
|
||||
mv "$rootfs/var/www/roundcubemail-${ROUNDCUBE_VERSION}" "$rootfs/var/www/roundcube"
|
||||
rm -f "$rootfs/tmp/roundcube.tar.gz"
|
||||
|
||||
log "Roundcube downloaded"
|
||||
}
|
||||
|
||||
configure_roundcube() {
|
||||
defaults
|
||||
local rootfs="$LXC_PATH/rootfs"
|
||||
|
||||
log "Configuring Roundcube..."
|
||||
|
||||
cat > "$rootfs/var/www/roundcube/config/config.inc.php" << EOF
|
||||
<?php
|
||||
\$config["db_dsnw"] = "sqlite:////var/www/roundcube/db/roundcube.db?mode=0640";
|
||||
\$config["imap_host"] = "ssl://${mail_host}:993";
|
||||
\$config["smtp_host"] = "ssl://${mail_host}:465";
|
||||
\$config["imap_conn_options"] = [
|
||||
"ssl" => ["verify_peer" => false, "verify_peer_name" => false]
|
||||
];
|
||||
\$config["smtp_conn_options"] = [
|
||||
"ssl" => ["verify_peer" => false, "verify_peer_name" => false]
|
||||
];
|
||||
\$config["support_url"] = "";
|
||||
\$config["product_name"] = "SecuBox Webmail";
|
||||
\$config["des_key"] = "rcmail-!24ByteDESKey*Sym";
|
||||
\$config["plugins"] = ["archive", "zipdownload"];
|
||||
\$config["skin"] = "elastic";
|
||||
\$config["language"] = "fr_FR";
|
||||
EOF
|
||||
|
||||
# Configure nginx
|
||||
cat > "$rootfs/etc/nginx/http.d/roundcube.conf" << EOF
|
||||
server {
|
||||
listen ${port};
|
||||
server_name _;
|
||||
root /var/www/roundcube;
|
||||
index index.php;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.php?\$args;
|
||||
}
|
||||
|
||||
location ~ \.php\$ {
|
||||
fastcgi_pass unix:/run/php-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi.conf;
|
||||
}
|
||||
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Configure PHP-FPM
|
||||
mkdir -p "$rootfs/etc/php84/php-fpm.d"
|
||||
cat > "$rootfs/etc/php84/php-fpm.d/www.conf" << 'EOF'
|
||||
[www]
|
||||
user = nginx
|
||||
group = nginx
|
||||
listen = /run/php-fpm.sock
|
||||
listen.owner = nginx
|
||||
listen.group = nginx
|
||||
pm = dynamic
|
||||
pm.max_children = 5
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
EOF
|
||||
|
||||
# Set permissions
|
||||
lxc-attach -n "$CONTAINER" -- chown -R nginx:nginx /var/www/roundcube 2>/dev/null || true
|
||||
|
||||
log "Roundcube configured"
|
||||
}
|
||||
|
||||
configure_haproxy() {
|
||||
defaults
|
||||
|
||||
if ! command -v haproxyctl >/dev/null 2>&1; then
|
||||
warn "haproxyctl not found"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local vhost_name=$(echo "$domain" | tr '.' '_')
|
||||
|
||||
if ! uci -q get haproxy.roundcube >/dev/null 2>&1; then
|
||||
log "Creating HAProxy backend..."
|
||||
uci set haproxy.roundcube=backend
|
||||
uci set haproxy.roundcube.name='roundcube'
|
||||
uci set haproxy.roundcube.mode='http'
|
||||
uci set haproxy.roundcube.balance='roundrobin'
|
||||
uci set haproxy.roundcube.enabled='1'
|
||||
uci set haproxy.roundcube.option='forwardfor'
|
||||
uci add_list haproxy.roundcube.http_request='set-header X-Forwarded-Proto https'
|
||||
uci add_list haproxy.roundcube.http_request='set-header X-Real-IP %[src]'
|
||||
|
||||
uci set haproxy.roundcube_srv=server
|
||||
uci set haproxy.roundcube_srv.backend='roundcube'
|
||||
uci set haproxy.roundcube_srv.name='roundcube'
|
||||
uci set haproxy.roundcube_srv.address='192.168.255.1'
|
||||
uci set haproxy.roundcube_srv.port="$port"
|
||||
uci set haproxy.roundcube_srv.weight='100'
|
||||
uci set haproxy.roundcube_srv.check='1'
|
||||
uci set haproxy.roundcube_srv.enabled='1'
|
||||
fi
|
||||
|
||||
if ! uci -q get haproxy.${vhost_name} >/dev/null 2>&1; then
|
||||
log "Creating HAProxy vhost for $domain..."
|
||||
uci set haproxy.${vhost_name}=vhost
|
||||
uci set haproxy.${vhost_name}.domain="$domain"
|
||||
uci set haproxy.${vhost_name}.backend='roundcube'
|
||||
uci set haproxy.${vhost_name}.ssl='1'
|
||||
uci set haproxy.${vhost_name}.ssl_redirect='1'
|
||||
uci set haproxy.${vhost_name}.acme='1'
|
||||
uci set haproxy.${vhost_name}.waf_bypass='1'
|
||||
uci set haproxy.${vhost_name}.enabled='1'
|
||||
fi
|
||||
|
||||
uci commit haproxy
|
||||
haproxyctl generate 2>/dev/null
|
||||
haproxyctl reload 2>/dev/null
|
||||
|
||||
log "HAProxy configured for $domain"
|
||||
}
|
||||
|
||||
cmd_install() {
|
||||
require_root
|
||||
log "Installing Roundcube LXC..."
|
||||
|
||||
defaults
|
||||
|
||||
if ! lxc_exists; then
|
||||
bootstrap_alpine
|
||||
fi
|
||||
|
||||
create_lxc_config
|
||||
create_startup_script
|
||||
|
||||
lxc-start -n "$CONTAINER" -d
|
||||
sleep 3
|
||||
|
||||
if lxc_running; then
|
||||
install_packages
|
||||
download_roundcube
|
||||
configure_roundcube
|
||||
lxc-stop -n "$CONTAINER"
|
||||
else
|
||||
error "Failed to start container"
|
||||
return 1
|
||||
fi
|
||||
|
||||
configure_haproxy
|
||||
|
||||
uci_set main.enabled '1'
|
||||
uci commit ${CONFIG}
|
||||
|
||||
log "Roundcube installed!"
|
||||
log "Start with: roundcubectl start"
|
||||
}
|
||||
|
||||
cmd_start() {
|
||||
require_root
|
||||
|
||||
if lxc_running; then
|
||||
log "Roundcube already running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! lxc_exists; then
|
||||
error "Roundcube not installed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
defaults
|
||||
create_lxc_config
|
||||
|
||||
log "Starting Roundcube LXC..."
|
||||
lxc-start -n "$CONTAINER" -d
|
||||
sleep 3
|
||||
|
||||
if lxc_running; then
|
||||
log "Roundcube started on port $port"
|
||||
else
|
||||
error "Failed to start Roundcube"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_stop() {
|
||||
require_root
|
||||
|
||||
if ! lxc_running; then
|
||||
log "Roundcube is not running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Stopping Roundcube..."
|
||||
lxc-stop -n "$CONTAINER"
|
||||
log "Roundcube stopped"
|
||||
}
|
||||
|
||||
cmd_restart() {
|
||||
cmd_stop
|
||||
sleep 2
|
||||
cmd_start
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
defaults
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " SecuBox Roundcube v$VERSION (LXC)"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
echo "Configuration:"
|
||||
echo " Port: $port"
|
||||
echo " Mail Host: $mail_host"
|
||||
echo " Domain: $domain"
|
||||
echo ""
|
||||
|
||||
echo "Container:"
|
||||
if lxc_running; then
|
||||
echo -e " Status: ${GREEN}Running${NC}"
|
||||
local test=$(curl -sI "http://127.0.0.1:$port/" 2>/dev/null | head -1)
|
||||
if echo "$test" | grep -q "200"; then
|
||||
echo -e " Web: ${GREEN}OK${NC}"
|
||||
else
|
||||
echo -e " Web: ${YELLOW}Starting...${NC}"
|
||||
fi
|
||||
elif lxc_exists; then
|
||||
echo -e " Status: ${YELLOW}Stopped${NC}"
|
||||
else
|
||||
echo -e " Status: ${RED}Not installed${NC}"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
SecuBox Roundcube Webmail v$VERSION (LXC)
|
||||
|
||||
Usage: roundcubectl <command>
|
||||
|
||||
Commands:
|
||||
install Install LXC container
|
||||
start Start Roundcube
|
||||
stop Stop Roundcube
|
||||
restart Restart Roundcube
|
||||
status Show status
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
install) shift; cmd_install "$@" ;;
|
||||
start) shift; cmd_start "$@" ;;
|
||||
stop) shift; cmd_stop "$@" ;;
|
||||
restart) shift; cmd_restart "$@" ;;
|
||||
status) shift; cmd_status "$@" ;;
|
||||
service-run) cmd_start ;;
|
||||
service-stop) cmd_stop ;;
|
||||
help|--help|-h|'') show_help ;;
|
||||
*) error "Unknown command: $1"; show_help >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Loading…
Reference in New Issue
Block a user