316 lines
8.3 KiB
Bash
316 lines
8.3 KiB
Bash
#!/bin/bash
|
|
# ct/mermaidjs.sh - Create and configure a self-hosted Mermaid.js editor LXC
|
|
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[$(date +%H:%M:%S)]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[$(date +%H:%M:%S)]${NC} $1"; }
|
|
die() { echo -e "${RED}[$(date +%H:%M:%S)]${NC} $1"; exit 1; }
|
|
|
|
# Ensure root on Proxmox VE
|
|
if [[ $EUID -ne 0 ]] || ! command -v pct &>/dev/null; then
|
|
die "This script must be run as root on a Proxmox VE host"
|
|
fi
|
|
|
|
# === Configuration Defaults ===
|
|
LXC_ID=""
|
|
LXC_HOSTNAME="mermaid"
|
|
LXC_MEMORY=512
|
|
LXC_CORES=1
|
|
LXC_DISK="local-lvm"
|
|
LXC_TEMPLATE="local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst"
|
|
LXC_ROOT_PASSWORD="mermaid"
|
|
LXC_IP=""
|
|
LXC_NETMASK="24"
|
|
LXC_GATEWAY=""
|
|
LXC_BRIDGE="vmbr0"
|
|
LXC_PORT="80" # HTTP port — change if needed (e.g., 8080)
|
|
|
|
# Auto-increment VMID
|
|
get_next_id() {
|
|
local max=0
|
|
for id in $(pct list | awk 'NR>1 {print $1}'); do
|
|
((id > max)) && max=$id
|
|
done
|
|
echo $((max + 1))
|
|
}
|
|
|
|
log "=== Mermaid.js Interactive Editor LXC Setup (Debian 12) ==="
|
|
log "This creates a container with a live-editable Mermaid editor (offline-capable)."
|
|
|
|
# === Interactive Prompts ===
|
|
read -rp "Hostname (default: mermaid): " HOSTNAME_INPUT
|
|
[[ -n "$HOSTNAME_INPUT" ]] && LXC_HOSTNAME="$HOSTNAME_INPUT"
|
|
|
|
read -rp "Container ID (default: $(get_next_id)): " ID_INPUT
|
|
LXC_ID=${ID_INPUT:-$(get_next_id)}
|
|
|
|
read -rp "Memory (MB, default: 512): " MEM_INPUT
|
|
LXC_MEMORY=${MEM_INPUT:-512}
|
|
|
|
read -rp "CPU Cores (default: 1): " CORES_INPUT
|
|
LXC_CORES=${CORES_INPUT:-1}
|
|
|
|
read -rp "Root Password (default: mermaid): " PASS_INPUT
|
|
LXC_ROOT_PASSWORD=${PASS_INPUT:-mermaid}
|
|
|
|
read -rp "Disk Storage Pool (default: local-lvm): " DISK_INPUT
|
|
LXC_DISK=${DISK_INPUT:-local-lvm}
|
|
|
|
read -rp "Network Bridge (default: vmbr0): " BRIDGE_INPUT
|
|
LXC_BRIDGE=${BRIDGE_INPUT:-vmbr0}
|
|
|
|
read -rp "HTTP Port (default: 80): " PORT_INPUT
|
|
LXC_PORT=${PORT_INPUT:-80}
|
|
|
|
read -rp "IP Address (e.g., 192.168.1.60; leave blank for DHCP): " IP_INPUT
|
|
LXC_IP="$IP_INPUT"
|
|
|
|
if [[ -n "$LXC_IP" ]]; then
|
|
read -rp "Netmask (default: 24): " NETMASK_INPUT
|
|
LXC_NETMASK=${NETMASK_INPUT:-24}
|
|
read -rp "Gateway (blank for none): " GW_INPUT
|
|
LXC_GATEWAY="$GW_INPUT"
|
|
fi
|
|
|
|
# === 1. Create LXC Container ===
|
|
log "Creating Debian 12 LXC container $LXC_ID ($LXC_HOSTNAME)..."
|
|
|
|
# Build NET_CONFIG safely BEFORE pct create
|
|
if [[ -n "$LXC_IP" && -n "$LXC_GATEWAY" ]]; then
|
|
NET0_ARGS="name=eth0,bridge=$LXC_BRIDGE,ip=$LXC_IP/$LXC_NETMASK,gw=$LXC_GATEWAY"
|
|
elif [[ -z "$LXC_GATEWAY" && -n "$LXC_IP" ]]; then
|
|
NET0_ARGS="name=eth0,bridge=$LXC_BRIDGE,ip=dhcp"
|
|
else
|
|
NET0_ARGS="name=eth0,bridge=$LXC_BRIDGE,ip=dhcp"
|
|
fi
|
|
|
|
echo "DEBUG: NET0_ARGS = $NET0_ARGS"
|
|
|
|
pct create $LXC_ID $LXC_TEMPLATE \
|
|
-hostname "$LXC_HOSTNAME" \
|
|
-memory "$LXC_MEMORY" \
|
|
-cores "$LXC_CORES" \
|
|
-net0 "$NET0_ARGS" \
|
|
-rootfs "$LXC_DISK:2" \
|
|
-ostype debian \
|
|
-password "$LXC_ROOT_PASSWORD" \
|
|
-onboot 1 \
|
|
-features nesting=1
|
|
|
|
log "Starting container..."
|
|
pct start $LXC_ID
|
|
|
|
log "Waiting for container to be reachable (max 60s)..."
|
|
for i in {1..60}; do
|
|
if pct exec $LXC_ID -- sh -c "ping -c1 127.0.0.1 >/dev/null 2>&1"; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# === 2. Prepare and Deploy Install Script ===
|
|
log "Deploying Mermaid editor + Caddy..."
|
|
|
|
cat > /tmp/mermaid-install.sh <<'EOF'
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# === Define functions FIRST ===
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[$(date +%H:%M:%S)]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[$(date +%H:%M:%S)]${NC} $1"; }
|
|
die() { echo -e "${RED}[$(date +%H:%M:%S)]${NC} $1"; exit 1; }
|
|
|
|
PORT="${1:-80}"
|
|
|
|
# === Now run operations ===
|
|
log "Setting up DNS (critical step)..."
|
|
|
|
# Backup original resolv.conf
|
|
cp /etc/resolv.conf /etc/resolv.conf.bak 2>/dev/null || true
|
|
|
|
# Force public DNS (bypass AdGuard which may have DoH issues)
|
|
cat > /etc/resolv.conf <<'EOF'
|
|
nameserver 1.1.1.1
|
|
nameserver 8.8.8.8
|
|
EOF
|
|
|
|
# Verify DNS works
|
|
log "Verifying DNS resolution..."
|
|
if ! nslookup deb.debian.org 1.1.1.1 &>/dev/null; then
|
|
die "DNS resolution failed — check network connectivity"
|
|
fi
|
|
|
|
log "DNS verified ✓"
|
|
|
|
# === 2. Now configure apt ===
|
|
log "Configuring apt sources..."
|
|
mkdir -p /etc/apt/apt.conf.d
|
|
|
|
# Use explicit DNS for apt (redundant but safe)
|
|
cat > /etc/apt/apt.conf.d/99-dns <<'EOF'
|
|
Acquire::Dns "1.1.1.1";
|
|
Acquire::Dns "8.8.8.8";
|
|
EOF
|
|
|
|
# Enable non-free repository
|
|
if grep -q "deb debianmain" /etc/apt/sources.list; then
|
|
sed -i 's/deb \(.*\) main/deb \1 main non-free non-free-firmware/' /etc/apt/sources.list
|
|
fi
|
|
|
|
# === 3. Update apt (with retries) ===
|
|
log "Updating package lists (with retries)..."
|
|
apt-get update -o Acquire::Retries=5 -o APT::Acquire::Retries=5 || {
|
|
warn "First apt-get update failed — retrying..."
|
|
sleep 5
|
|
apt-get update -o Acquire::Retries=5 || die "apt-get update failed after retries"
|
|
}
|
|
|
|
log "✓ apt-get update succeeded"
|
|
|
|
log "Installing dependencies..."
|
|
apt-get install -y -qq \
|
|
wget \
|
|
ufw \
|
|
curl \
|
|
apt-transport-https \
|
|
ca-certificates
|
|
|
|
# 2. Download Mermaid (offline bundled)
|
|
log "Downloading Mermaid v10.6.1 (offline)..."
|
|
mkdir -p /var/www/mermaid
|
|
wget -q https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js -O /var/www/mermaid/mermaid.min.js
|
|
|
|
# 3. Create the editor HTML (dark theme, live preview)
|
|
log "Creating Mermaid editor interface..."
|
|
cat > /var/www/mermaid/index.html <<'HTML'
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Self-hosted Mermaid Editor</title>
|
|
<script src="/mermaid.min.js"></script>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
height: 100vh;
|
|
background: #f0f0f0;
|
|
}
|
|
textarea {
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 1rem;
|
|
font-family: 'JetBrains Mono', Consolas, monospace;
|
|
font-size: 14px;
|
|
border: none;
|
|
resize: none;
|
|
background: #2d2d2d;
|
|
color: #f8f8f2;
|
|
}
|
|
#preview {
|
|
padding: 1rem;
|
|
overflow: auto;
|
|
border-left: 1px solid #ccc;
|
|
background: #fff;
|
|
}
|
|
.mermaid { max-width: 90%; margin: 0 auto; }
|
|
</style>
|
|
<script>
|
|
mermaid.initialize({startOnLoad: false, securityLevel: 'loose'});
|
|
const editor = document.querySelector('textarea');
|
|
const preview = document.getElementById('preview');
|
|
|
|
editor.addEventListener('input', () => {
|
|
const code = editor.value.trim();
|
|
if (code) {
|
|
mermaid.render('diagram', code, svg => preview.innerHTML = svg);
|
|
} else {
|
|
preview.innerHTML = '<p style="color:#999;">Type Mermaid code here...</p>';
|
|
}
|
|
});
|
|
|
|
// Initial render
|
|
editor.value = `graph TD
|
|
A[Start] --> B{Decision?}
|
|
B -->|Yes| C[Do A]
|
|
B -->|No| D[Do B]
|
|
C --> E[End]
|
|
D --> E`;
|
|
input();
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<textarea spellcheck="false"></textarea>
|
|
<div id="preview"></div>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
|
|
# 4. Caddy config (HTTP on $PORT)
|
|
log "Configuring Caddy..."
|
|
cat > /etc/caddy/Caddyfile <<CADDY
|
|
:$PORT {
|
|
root * /var/www/mermaid
|
|
file_server
|
|
log {
|
|
output file /var/log/caddy/access.log
|
|
}
|
|
}
|
|
CADDY
|
|
|
|
# 5. Start & enable Caddy
|
|
rc-service caddy start
|
|
rc-update add caddy
|
|
|
|
# 6. Firewall
|
|
ufw --force enable 2>/dev/null || true
|
|
ufw allow $PORT/tcp 2>/dev/null || true
|
|
|
|
# 7. Wait + check
|
|
log "Waiting for Caddy to start..."
|
|
sleep 5
|
|
if curl -sf "http://localhost:$PORT" >/dev/null; then
|
|
log "Mermaid Editor is live!"
|
|
else
|
|
warn "Caddy failed — check 'rc-service caddy status' and 'journalctl -u caddy'"
|
|
exit 1
|
|
fi
|
|
|
|
# 8. Output summary
|
|
IP=$(hostname -I 2>/dev/null | cut -d' ' -f1)
|
|
if [[ -z "$IP" ]]; then
|
|
IP=$(ip -4 addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
|
|
fi
|
|
log "URL: ${BLUE}http://${IP}:${PORT}${NC}"
|
|
log "Root password: ${YELLOW}$LXC_ROOT_PASSWORD${NC}"
|
|
log "SSH: ssh root@${IP}"
|
|
EOF
|
|
|
|
# Push & run install script
|
|
pct push $LXC_ID /tmp/mermaid-install.sh /root/mermaid-install.sh || die "Failed to push script"
|
|
pct exec $LXC_ID -- bash /root/mermaid-install.sh "$LXC_PORT" || die "Install failed"
|
|
|
|
# Cleanup
|
|
rm -f /tmp/mermaid-install.sh
|
|
|
|
# === 3. Final Summary ===
|
|
IP_ADDR=$(pct exec $LXC_ID -- hostname -I 2>/dev/null | cut -d' ' -f1)
|
|
[[ -z "$IP_ADDR" ]] && IP_ADDR=$(pct exec $LXC_ID -- ip -4 addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
|
|
|
|
log "Mermaid Editor LXC ($LXC_ID) ready!"
|
|
log "URL: ${BLUE}http://${IP_ADDR}:${LXC_PORT}${NC}"
|
|
log "Root password: ${YELLOW}$LXC_ROOT_PASSWORD${NC}" |