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>
438 lines
10 KiB
Bash
438 lines
10 KiB
Bash
#!/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
|