2026-03-07 13:49:44 +00:00
|
|
|
#!/bin/bash
|
2026-03-07 14:19:13 +00:00
|
|
|
set -e
|
|
|
|
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
# D3V-NPMWG Installer for Ubuntu/Debian
|
2026-03-08 02:33:24 +00:00
|
|
|
# xGat3 + WireGuard VPN
|
2026-03-17 16:06:47 +00:00
|
|
|
# https://src.d3v.ac/xtcnet/D3V-Server
|
2026-03-07 14:19:13 +00:00
|
|
|
# ============================================================
|
2026-03-07 13:49:44 +00:00
|
|
|
|
|
|
|
|
INSTALL_DIR="/opt/d3v-npmwg"
|
2026-03-07 14:19:13 +00:00
|
|
|
COMPOSE_FILE="${INSTALL_DIR}/docker-compose.yml"
|
|
|
|
|
CONTAINER_NAME="d3v-npmwg"
|
2026-03-17 16:07:41 +00:00
|
|
|
IMAGE_NAME="src.d3v.ac/xtcnet/d3v-server:latest"
|
2026-03-07 13:49:44 +00:00
|
|
|
|
2026-03-17 15:25:48 +00:00
|
|
|
FORGEJO_INSTALL_DIR="/opt/forgejo"
|
|
|
|
|
FORGEJO_COMPOSE_FILE="${FORGEJO_INSTALL_DIR}/docker-compose.yml"
|
|
|
|
|
FORGEJO_CONTAINER_NAME="forgejo"
|
2026-03-17 15:35:47 +00:00
|
|
|
FORGEJO_IMAGE="codeberg.org/forgejo/forgejo:9"
|
2026-03-17 15:25:48 +00:00
|
|
|
DOCKER_NETWORK="d3v-net"
|
|
|
|
|
|
2026-03-17 16:05:15 +00:00
|
|
|
FORGEJO_RUNNER_DIR="/opt/forgejo-runner"
|
|
|
|
|
FORGEJO_RUNNER_CONTAINER="forgejo-runner"
|
2026-03-17 16:18:22 +00:00
|
|
|
FORGEJO_RUNNER_IMAGE="gitea/act_runner:latest"
|
2026-03-17 16:05:15 +00:00
|
|
|
|
2026-03-07 13:49:44 +00:00
|
|
|
RED='\033[0;31m'
|
|
|
|
|
GREEN='\033[0;32m'
|
|
|
|
|
YELLOW='\033[1;33m'
|
2026-03-07 14:19:13 +00:00
|
|
|
CYAN='\033[0;36m'
|
|
|
|
|
BOLD='\033[1m'
|
|
|
|
|
NC='\033[0m'
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# Helpers
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
log_step() { echo -e "${CYAN}[*]${NC} $1"; }
|
|
|
|
|
log_ok() { echo -e "${GREEN}[✓]${NC} $1"; }
|
|
|
|
|
log_warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
|
|
|
|
log_err() { echo -e "${RED}[✗]${NC} $1"; }
|
|
|
|
|
separator() { echo -e "${GREEN}=================================================================${NC}"; }
|
2026-03-07 13:49:44 +00:00
|
|
|
|
2026-03-17 16:16:50 +00:00
|
|
|
self_update() {
|
|
|
|
|
local remote_url="https://src.d3v.ac/xtcnet/D3V-Server/raw/branch/master/install.sh"
|
|
|
|
|
local tmp
|
|
|
|
|
tmp=$(mktemp) || return
|
|
|
|
|
if curl -fsSL -m 10 "$remote_url" -o "$tmp" 2>/dev/null; then
|
|
|
|
|
if ! cmp -s "$tmp" "$0"; then
|
|
|
|
|
log_warn "A newer version of install.sh found. Updating..."
|
|
|
|
|
cp "$tmp" "$0"
|
|
|
|
|
chmod +x "$0"
|
|
|
|
|
rm -f "$tmp"
|
|
|
|
|
log_ok "install.sh updated. Restarting..."
|
|
|
|
|
exec "$0" "$@"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
rm -f "$tmp"
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
require_root() {
|
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
|
|
|
log_err "This script must be run as root. Use: sudo $0"
|
2026-03-07 13:49:44 +00:00
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
get_compose_cmd() {
|
|
|
|
|
if docker compose version > /dev/null 2>&1; then
|
|
|
|
|
echo "docker compose"
|
2026-03-07 14:14:08 +00:00
|
|
|
else
|
2026-03-07 14:21:47 +00:00
|
|
|
log_err "Docker Compose plugin not found. Please install it first."
|
|
|
|
|
exit 1
|
2026-03-07 14:11:09 +00:00
|
|
|
fi
|
2026-03-07 14:19:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
detect_public_ip() {
|
|
|
|
|
local ip=""
|
|
|
|
|
ip=$(curl -s -m 5 https://ifconfig.me 2>/dev/null) \
|
|
|
|
|
|| ip=$(curl -s -m 5 https://icanhazip.com 2>/dev/null) \
|
|
|
|
|
|| ip=$(curl -s -m 5 https://api.ipify.org 2>/dev/null) \
|
|
|
|
|
|| ip=""
|
|
|
|
|
echo "$ip"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# 1. Install system dependencies
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
install_deps() {
|
|
|
|
|
separator
|
|
|
|
|
echo -e "${BOLD} Installing System Dependencies${NC}"
|
|
|
|
|
separator
|
2026-03-07 14:11:09 +00:00
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# --- curl ---
|
|
|
|
|
log_step "Checking curl..."
|
|
|
|
|
if command -v curl > /dev/null 2>&1; then
|
|
|
|
|
log_ok "curl is already installed."
|
2026-03-07 14:11:09 +00:00
|
|
|
else
|
2026-03-07 14:19:13 +00:00
|
|
|
log_step "Installing curl..."
|
|
|
|
|
apt-get update -qq
|
|
|
|
|
apt-get install -y curl
|
|
|
|
|
log_ok "curl installed."
|
2026-03-07 13:49:44 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# --- Docker ---
|
|
|
|
|
log_step "Checking Docker..."
|
|
|
|
|
if command -v docker > /dev/null 2>&1; then
|
|
|
|
|
log_ok "Docker is already installed ($(docker --version))."
|
2026-03-07 14:11:09 +00:00
|
|
|
else
|
2026-03-07 14:19:13 +00:00
|
|
|
log_step "Installing Docker... (this may take 1-2 minutes)"
|
|
|
|
|
curl -fsSL https://get.docker.com | sh
|
2026-03-07 14:21:47 +00:00
|
|
|
log_ok "Docker installed."
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-07 14:24:58 +00:00
|
|
|
# --- Ensure Docker daemon is running ---
|
|
|
|
|
log_step "Starting Docker service..."
|
|
|
|
|
systemctl enable docker > /dev/null 2>&1 || true
|
|
|
|
|
systemctl restart docker > /dev/null 2>&1 || true
|
|
|
|
|
|
2026-03-07 14:21:47 +00:00
|
|
|
# --- Wait for Docker daemon ---
|
|
|
|
|
log_step "Waiting for Docker daemon to be ready..."
|
|
|
|
|
local retries=0
|
|
|
|
|
while ! docker info > /dev/null 2>&1; do
|
|
|
|
|
retries=$((retries + 1))
|
|
|
|
|
if [ "$retries" -ge 30 ]; then
|
|
|
|
|
log_err "Docker daemon did not start after 30 seconds."
|
|
|
|
|
log_err "Try: systemctl restart docker"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
sleep 1
|
|
|
|
|
done
|
|
|
|
|
log_ok "Docker daemon is running."
|
|
|
|
|
|
|
|
|
|
# --- Remove old standalone docker-compose (Python) if present ---
|
|
|
|
|
if command -v docker-compose > /dev/null 2>&1; then
|
|
|
|
|
local dc_path
|
|
|
|
|
dc_path=$(command -v docker-compose)
|
|
|
|
|
# Check if it's the old Python version
|
|
|
|
|
if docker-compose version 2>&1 | grep -qi "docker-compose version 1"; then
|
|
|
|
|
log_warn "Detected legacy docker-compose (Python). Removing it..."
|
|
|
|
|
apt-get purge -y docker-compose 2>/dev/null || rm -f "$dc_path" 2>/dev/null || true
|
|
|
|
|
log_ok "Legacy docker-compose removed."
|
|
|
|
|
fi
|
2026-03-07 13:49:44 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-03-07 14:21:47 +00:00
|
|
|
# --- Docker Compose plugin ---
|
|
|
|
|
log_step "Checking Docker Compose plugin..."
|
2026-03-07 14:19:13 +00:00
|
|
|
if docker compose version > /dev/null 2>&1; then
|
2026-03-07 14:21:47 +00:00
|
|
|
log_ok "Docker Compose plugin is ready ($(docker compose version))."
|
2026-03-07 13:49:44 +00:00
|
|
|
else
|
2026-03-07 14:19:13 +00:00
|
|
|
log_step "Installing Docker Compose plugin..."
|
|
|
|
|
apt-get update -qq
|
|
|
|
|
apt-get install -y docker-compose-plugin
|
|
|
|
|
log_ok "Docker Compose plugin installed."
|
2026-03-07 13:49:44 +00:00
|
|
|
fi
|
2026-03-07 14:19:13 +00:00
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
log_ok "All system dependencies are ready."
|
2026-03-07 13:49:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-08 07:58:47 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# x. Generate docker-compose.yml
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
generate_docker_compose() {
|
|
|
|
|
local host="$1"
|
|
|
|
|
if [ -z "$host" ]; then
|
|
|
|
|
log_err "generate_docker_compose: host is empty."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log_step "Generating docker-compose.yml..."
|
2026-03-17 15:31:13 +00:00
|
|
|
|
2026-03-08 08:50:25 +00:00
|
|
|
local custom_ports_block=""
|
|
|
|
|
if [ -f ".custom_ports" ]; then
|
|
|
|
|
while IFS= read -r port_mapping; do
|
|
|
|
|
# Ignore empty lines or comments
|
|
|
|
|
[[ -z "$port_mapping" || "$port_mapping" =~ ^# ]] && continue
|
|
|
|
|
custom_ports_block+=" - \"${port_mapping}\"\n"
|
|
|
|
|
done < ".custom_ports"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-17 15:31:13 +00:00
|
|
|
# Only include d3v-net if the network already exists (i.e. Forgejo is installed)
|
|
|
|
|
local network_block=""
|
|
|
|
|
if docker network ls --format '{{.Name}}' 2>/dev/null | grep -q "^${DOCKER_NETWORK}$"; then
|
|
|
|
|
network_block=" networks:
|
|
|
|
|
- d3v-net
|
|
|
|
|
|
|
|
|
|
networks:
|
|
|
|
|
d3v-net:
|
|
|
|
|
external: true
|
|
|
|
|
name: ${DOCKER_NETWORK}"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-08 07:58:47 +00:00
|
|
|
cat > "$COMPOSE_FILE" <<YAML
|
|
|
|
|
services:
|
|
|
|
|
d3v-npmwg:
|
|
|
|
|
image: ${IMAGE_NAME}
|
|
|
|
|
container_name: ${CONTAINER_NAME}
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
cap_add:
|
|
|
|
|
- NET_ADMIN
|
|
|
|
|
- SYS_MODULE
|
2026-03-08 08:13:32 +00:00
|
|
|
sysctls:
|
|
|
|
|
- net.ipv4.ip_forward=1
|
|
|
|
|
- net.ipv4.conf.all.src_valid_mark=1
|
|
|
|
|
ports:
|
|
|
|
|
- "80:80" # HTTP
|
|
|
|
|
- "81:81" # Admin UI
|
|
|
|
|
- "443:443" # HTTPS
|
|
|
|
|
- "51820-51830:51820-51830/udp" # WireGuard Multi-Server Range
|
2026-03-08 08:50:25 +00:00
|
|
|
$(echo -e "$custom_ports_block" | sed '/^$/d') volumes:
|
2026-03-08 07:58:47 +00:00
|
|
|
- ./data:/data
|
|
|
|
|
- ./letsencrypt:/etc/letsencrypt
|
|
|
|
|
- ./wireguard:/etc/wireguard
|
|
|
|
|
environment:
|
|
|
|
|
WG_HOST: "${host}"
|
2026-03-17 15:31:13 +00:00
|
|
|
${network_block}
|
2026-03-08 07:58:47 +00:00
|
|
|
YAML
|
|
|
|
|
log_ok "docker-compose.yml created/updated."
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 15:44:36 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# Save iptables rules so they persist across reboots
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
save_iptables_rules() {
|
|
|
|
|
if command -v netfilter-persistent > /dev/null 2>&1; then
|
|
|
|
|
netfilter-persistent save > /dev/null 2>&1
|
|
|
|
|
log_ok "iptables rules saved (netfilter-persistent)."
|
|
|
|
|
elif command -v iptables-save > /dev/null 2>&1; then
|
|
|
|
|
mkdir -p /etc/iptables
|
|
|
|
|
iptables-save > /etc/iptables/rules.v4
|
|
|
|
|
# Install iptables-persistent silently so rules reload on boot
|
|
|
|
|
if ! dpkg -l iptables-persistent > /dev/null 2>&1; then
|
|
|
|
|
log_step "Installing iptables-persistent for rule persistence..."
|
|
|
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent > /dev/null 2>&1
|
|
|
|
|
log_ok "iptables-persistent installed."
|
|
|
|
|
fi
|
|
|
|
|
log_ok "iptables rules saved (/etc/iptables/rules.v4)."
|
|
|
|
|
else
|
|
|
|
|
log_warn "Could not save iptables rules — install iptables-persistent manually."
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 15:25:48 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# Ensure shared Docker network exists
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
ensure_docker_network() {
|
|
|
|
|
if ! docker network ls --format '{{.Name}}' | grep -q "^${DOCKER_NETWORK}$"; then
|
|
|
|
|
log_step "Creating shared Docker network '${DOCKER_NETWORK}'..."
|
|
|
|
|
docker network create "${DOCKER_NETWORK}" > /dev/null
|
|
|
|
|
log_ok "Network '${DOCKER_NETWORK}' created."
|
|
|
|
|
else
|
|
|
|
|
log_ok "Network '${DOCKER_NETWORK}' already exists."
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# 2. Install D3V-NPMWG
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
do_install() {
|
|
|
|
|
require_root
|
2026-03-07 13:49:44 +00:00
|
|
|
|
|
|
|
|
if [ -d "$INSTALL_DIR" ]; then
|
2026-03-07 14:19:13 +00:00
|
|
|
log_warn "D3V-NPMWG is already installed at ${INSTALL_DIR}."
|
|
|
|
|
log_warn "Use the Update option to pull the latest image, or Uninstall first."
|
2026-03-07 13:49:44 +00:00
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
separator
|
|
|
|
|
echo -e "${BOLD} D3V-NPMWG Installation${NC}"
|
|
|
|
|
separator
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# --- Dependencies ---
|
|
|
|
|
install_deps
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# --- Detect IP ---
|
|
|
|
|
log_step "Detecting server public IP..."
|
|
|
|
|
local detected_ip
|
|
|
|
|
detected_ip=$(detect_public_ip)
|
|
|
|
|
|
|
|
|
|
local wg_host=""
|
|
|
|
|
if [ -n "$detected_ip" ]; then
|
|
|
|
|
log_ok "Detected IP: ${BOLD}${detected_ip}${NC}"
|
|
|
|
|
read -rp "$(echo -e "${CYAN}[?]${NC} WG_HOST [${detected_ip}]: ")" wg_host
|
|
|
|
|
wg_host="${wg_host:-$detected_ip}"
|
2026-03-07 14:14:08 +00:00
|
|
|
else
|
2026-03-07 14:19:13 +00:00
|
|
|
log_warn "Could not auto-detect public IP."
|
|
|
|
|
read -rp "$(echo -e "${CYAN}[?]${NC} Enter server public IP or domain: ")" wg_host
|
2026-03-07 14:14:08 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
if [ -z "$wg_host" ]; then
|
|
|
|
|
log_err "WG_HOST cannot be empty. Aborting."
|
2026-03-07 13:49:44 +00:00
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# --- Create directory ---
|
|
|
|
|
log_step "Creating ${INSTALL_DIR}..."
|
2026-03-07 13:49:44 +00:00
|
|
|
mkdir -p "$INSTALL_DIR"
|
2026-03-07 14:19:13 +00:00
|
|
|
log_ok "Directory created."
|
|
|
|
|
|
2026-03-17 15:25:48 +00:00
|
|
|
# --- Ensure shared network ---
|
|
|
|
|
ensure_docker_network
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# --- Write docker-compose.yml ---
|
2026-03-08 07:58:47 +00:00
|
|
|
generate_docker_compose "$wg_host"
|
2026-03-07 14:19:13 +00:00
|
|
|
|
|
|
|
|
# --- Pull & Start ---
|
|
|
|
|
log_step "Pulling Docker image (this may take a few minutes)..."
|
|
|
|
|
local dc
|
|
|
|
|
dc=$(get_compose_cmd)
|
|
|
|
|
cd "$INSTALL_DIR"
|
|
|
|
|
$dc pull
|
|
|
|
|
log_ok "Image pulled."
|
|
|
|
|
|
|
|
|
|
log_step "Starting containers..."
|
|
|
|
|
$dc up -d
|
|
|
|
|
log_ok "Containers started."
|
|
|
|
|
|
|
|
|
|
# --- Verify ---
|
|
|
|
|
log_step "Waiting for container to become healthy (10s)..."
|
|
|
|
|
sleep 10
|
|
|
|
|
|
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "$CONTAINER_NAME"; then
|
|
|
|
|
echo ""
|
|
|
|
|
separator
|
|
|
|
|
echo -e "${GREEN}${BOLD} D3V-NPMWG INSTALLED SUCCESSFULLY!${NC}"
|
|
|
|
|
separator
|
|
|
|
|
echo -e " ${CYAN}Web Admin UI${NC} : ${BOLD}http://${wg_host}:81${NC}"
|
|
|
|
|
echo -e " ${CYAN}HTTP Proxy${NC} : port 80"
|
|
|
|
|
echo -e " ${CYAN}HTTPS Proxy${NC} : port 443"
|
|
|
|
|
echo -e " ${CYAN}WireGuard VPN${NC} : port 51820/udp"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " Open the Web UI in ~30s and create your admin account."
|
|
|
|
|
separator
|
2026-03-07 14:14:08 +00:00
|
|
|
else
|
2026-03-07 14:19:13 +00:00
|
|
|
log_err "Container did not start. Check logs:"
|
|
|
|
|
echo -e " docker logs ${CONTAINER_NAME}"
|
2026-03-07 14:14:08 +00:00
|
|
|
fi
|
2026-03-07 13:49:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# 3. Uninstall D3V-NPMWG
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
do_uninstall() {
|
|
|
|
|
require_root
|
|
|
|
|
|
|
|
|
|
if [ ! -d "$INSTALL_DIR" ]; then
|
|
|
|
|
log_warn "D3V-NPMWG is not installed at ${INSTALL_DIR}."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log_warn "This will stop and remove D3V-NPMWG and ALL its data."
|
|
|
|
|
read -rp "$(echo -e "${RED}Are you sure? (y/N): ${NC}")" confirm
|
|
|
|
|
if [[ ! "$confirm" =~ ^[yY]$ ]]; then
|
|
|
|
|
echo "Cancelled."
|
|
|
|
|
return
|
2026-03-07 13:49:44 +00:00
|
|
|
fi
|
2026-03-07 14:19:13 +00:00
|
|
|
|
|
|
|
|
log_step "Stopping containers..."
|
|
|
|
|
local dc
|
|
|
|
|
dc=$(get_compose_cmd)
|
|
|
|
|
cd "$INSTALL_DIR" && $dc down -v 2>/dev/null || true
|
|
|
|
|
cd /
|
|
|
|
|
|
|
|
|
|
log_step "Removing ${INSTALL_DIR}..."
|
|
|
|
|
rm -rf "$INSTALL_DIR"
|
|
|
|
|
log_ok "D3V-NPMWG uninstalled."
|
2026-03-07 13:49:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# 4. Purge — uninstall app + system deps (Docker)
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
do_purge() {
|
|
|
|
|
require_root
|
|
|
|
|
do_uninstall
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
log_warn "Do you also want to remove Docker and Docker Compose from this system?"
|
|
|
|
|
read -rp "$(echo -e "${RED}Remove Docker? (y/N): ${NC}")" confirm
|
|
|
|
|
if [[ ! "$confirm" =~ ^[yY]$ ]]; then
|
|
|
|
|
echo "Skipped Docker removal."
|
|
|
|
|
return
|
2026-03-07 14:11:09 +00:00
|
|
|
fi
|
2026-03-07 14:19:13 +00:00
|
|
|
|
|
|
|
|
log_step "Removing Docker packages..."
|
|
|
|
|
apt-get purge -y docker-ce docker-ce-cli containerd.io \
|
|
|
|
|
docker-compose-plugin docker-buildx-plugin 2>/dev/null || true
|
|
|
|
|
apt-get autoremove -y --purge 2>/dev/null || true
|
|
|
|
|
log_ok "Docker and related packages removed."
|
2026-03-07 14:11:09 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# 5. Reset admin password
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
do_reset_password() {
|
|
|
|
|
require_root
|
|
|
|
|
|
|
|
|
|
if ! docker ps --format '{{.Names}}' | grep -q "$CONTAINER_NAME"; then
|
|
|
|
|
log_err "Container ${CONTAINER_NAME} is not running. Start it first."
|
2026-03-07 13:49:44 +00:00
|
|
|
return
|
|
|
|
|
fi
|
2026-03-07 14:19:13 +00:00
|
|
|
|
2026-03-17 12:51:09 +00:00
|
|
|
read -rsp "$(echo -e "${CYAN}[?]${NC} New password: ")" new_pass
|
2026-03-07 14:19:13 +00:00
|
|
|
echo ""
|
2026-03-17 12:51:09 +00:00
|
|
|
if [ -z "$new_pass" ]; then
|
|
|
|
|
log_err "Password cannot be empty. Cancelled."
|
|
|
|
|
return
|
|
|
|
|
fi
|
2026-03-07 14:19:13 +00:00
|
|
|
|
2026-03-17 12:51:09 +00:00
|
|
|
log_step "Resetting admin password..."
|
2026-03-07 14:19:13 +00:00
|
|
|
|
2026-03-17 12:51:09 +00:00
|
|
|
local result
|
|
|
|
|
result=$(docker exec "$CONTAINER_NAME" node -e "
|
2026-03-07 14:19:13 +00:00
|
|
|
import('bcrypt').then(async bcrypt => {
|
2026-03-17 12:51:09 +00:00
|
|
|
const knex = (await import('/app/db.js')).default();
|
|
|
|
|
const user = await knex('user').where('is_deleted', 0).orderBy('id', 'asc').first();
|
|
|
|
|
if (!user) { console.error('NO_USER'); process.exit(2); }
|
2026-03-07 14:19:13 +00:00
|
|
|
const hash = await bcrypt.hash('${new_pass}', 13);
|
2026-03-17 12:51:09 +00:00
|
|
|
await knex('auth').where('user_id', user.id).update({ secret: hash });
|
|
|
|
|
await knex('user').where('id', user.id).update({ is_disabled: 0 });
|
|
|
|
|
console.log(user.email);
|
2026-03-07 14:19:13 +00:00
|
|
|
process.exit(0);
|
2026-03-17 12:51:09 +00:00
|
|
|
}).catch(e => { console.error(e.message); process.exit(1); });
|
|
|
|
|
" 2>&1)
|
|
|
|
|
|
|
|
|
|
local exit_code=$?
|
|
|
|
|
if [ $exit_code -eq 0 ]; then
|
|
|
|
|
log_ok "Password updated successfully."
|
|
|
|
|
echo -e " Email : ${BOLD}${result}${NC}"
|
2026-03-07 14:19:13 +00:00
|
|
|
else
|
2026-03-17 12:51:09 +00:00
|
|
|
log_err "Failed to reset password: ${result}"
|
2026-03-07 14:19:13 +00:00
|
|
|
fi
|
2026-03-07 13:49:44 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# 6. Update D3V-NPMWG
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
do_update() {
|
|
|
|
|
require_root
|
|
|
|
|
|
2026-03-07 13:49:44 +00:00
|
|
|
if [ ! -d "$INSTALL_DIR" ]; then
|
2026-03-07 14:19:13 +00:00
|
|
|
log_err "D3V-NPMWG is not installed. Install it first."
|
2026-03-07 13:49:44 +00:00
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
local dc
|
|
|
|
|
dc=$(get_compose_cmd)
|
2026-03-08 07:58:47 +00:00
|
|
|
cd "$INSTALL_DIR" || return
|
2026-03-07 14:19:13 +00:00
|
|
|
|
2026-03-08 07:58:47 +00:00
|
|
|
# Save existing WG_HOST before regenerating template
|
|
|
|
|
local current_wg_host=""
|
|
|
|
|
if [ -f "docker-compose.yml" ]; then
|
|
|
|
|
# Safely extract WG_HOST value ignoring quotes and spaces
|
|
|
|
|
current_wg_host=$(grep -E 'WG_HOST:' docker-compose.yml | awk -F'"' '{print $2}')
|
|
|
|
|
fi
|
|
|
|
|
if [ -z "$current_wg_host" ]; then
|
|
|
|
|
current_wg_host=$(detect_public_ip)
|
|
|
|
|
log_warn "Could not extract WG_HOST. Using ${current_wg_host}."
|
2026-03-08 07:53:19 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-03-08 07:58:47 +00:00
|
|
|
generate_docker_compose "$current_wg_host"
|
|
|
|
|
|
|
|
|
|
log_step "Pulling latest image..."
|
|
|
|
|
$dc pull
|
|
|
|
|
log_ok "Image pulled."
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
log_step "Recreating containers..."
|
|
|
|
|
$dc up -d
|
|
|
|
|
log_ok "D3V-NPMWG updated."
|
|
|
|
|
|
|
|
|
|
log_step "Cleaning old images..."
|
|
|
|
|
docker image prune -f > /dev/null 2>&1
|
|
|
|
|
log_ok "Done."
|
2026-03-07 14:03:59 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-08 08:50:25 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# 7. Toggle Port 81 (Admin UI)
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# x. Custom Stream Ports Manager
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
do_manage_ports() {
|
|
|
|
|
require_root
|
|
|
|
|
echo ""
|
|
|
|
|
log_step "TCP/UDP Stream Ports Manager"
|
|
|
|
|
echo "If you created a Stream in Nginx Proxy Manager (e.g., listening on port 10000),"
|
|
|
|
|
echo "you must expose that port down to the Docker container."
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
local custom_ports_file=".custom_ports"
|
|
|
|
|
touch "$custom_ports_file"
|
|
|
|
|
|
|
|
|
|
echo "Current custom exposed ports:"
|
|
|
|
|
if [ -s "$custom_ports_file" ]; then
|
|
|
|
|
cat -n "$custom_ports_file"
|
|
|
|
|
else
|
|
|
|
|
echo " (None)"
|
|
|
|
|
fi
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}[?]${NC} Enter new port mapping (e.g. 10000:10000) or 'clear' to remove all: ")" new_port
|
|
|
|
|
|
|
|
|
|
if [[ "$new_port" == "clear" ]]; then
|
|
|
|
|
> "$custom_ports_file"
|
|
|
|
|
log_ok "All custom ports cleared."
|
|
|
|
|
elif [[ -n "$new_port" ]]; then
|
|
|
|
|
echo "$new_port" >> "$custom_ports_file"
|
|
|
|
|
log_ok "Port $new_port added."
|
|
|
|
|
else
|
|
|
|
|
log_warn "No changes made."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log_step "Regenerating docker-compose.yml and restarting container..."
|
|
|
|
|
|
|
|
|
|
local dc
|
|
|
|
|
dc=$(get_compose_cmd)
|
|
|
|
|
|
|
|
|
|
local current_wg_host=""
|
|
|
|
|
if [ -f "docker-compose.yml" ]; then
|
|
|
|
|
current_wg_host=$(grep -E 'WG_HOST:' docker-compose.yml | awk -F'"' '{print $2}')
|
|
|
|
|
fi
|
|
|
|
|
if [ -z "$current_wg_host" ]; then
|
|
|
|
|
current_wg_host=$(detect_public_ip)
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
generate_docker_compose "$current_wg_host"
|
|
|
|
|
|
|
|
|
|
$dc up -d
|
|
|
|
|
log_ok "Container updated with new port configurations."
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 15:49:56 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# 7. Toggle Port 81 (Admin UI)
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
do_toggle_port_81() {
|
|
|
|
|
require_root
|
|
|
|
|
echo ""
|
|
|
|
|
log_warn "This feature uses iptables (DOCKER-USER chain) to block external access to port 81."
|
|
|
|
|
log_warn "When blocked, you can only access the Admin UI via the WireGuard VPN (http://10.8.0.1:81) or localhost."
|
|
|
|
|
echo ""
|
|
|
|
|
read -rp "$(echo -e "${CYAN}[?]${NC} Do you want to (B)lock or (U)nblock external access to port 81? [B/U]: ")" choice
|
|
|
|
|
if [[ "$choice" =~ ^[bB]$ ]]; then
|
|
|
|
|
log_step "Blocking external access to port 81..."
|
|
|
|
|
# Remove existing rule if any to avoid duplicates
|
|
|
|
|
iptables -D DOCKER-USER -p tcp --dport 81 -j DROP 2>/dev/null || true
|
|
|
|
|
# Add rule to block port 81
|
|
|
|
|
iptables -I DOCKER-USER -p tcp --dport 81 -j DROP
|
|
|
|
|
log_ok "External access to port 81 is now BLOCKED."
|
2026-03-17 15:44:36 +00:00
|
|
|
save_iptables_rules
|
2026-03-07 15:49:56 +00:00
|
|
|
elif [[ "$choice" =~ ^[uU]$ ]]; then
|
|
|
|
|
log_step "Unblocking external access to port 81..."
|
|
|
|
|
iptables -D DOCKER-USER -p tcp --dport 81 -j DROP 2>/dev/null || true
|
|
|
|
|
log_ok "External access to port 81 is now UNBLOCKED (Public)."
|
2026-03-17 15:44:36 +00:00
|
|
|
save_iptables_rules
|
2026-03-07 15:49:56 +00:00
|
|
|
else
|
|
|
|
|
log_err "Invalid choice. Cancelled."
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 15:25:48 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# Forgejo — Install / Uninstall / Update
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
do_forgejo_install() {
|
|
|
|
|
require_root
|
|
|
|
|
|
|
|
|
|
if [ -d "$FORGEJO_INSTALL_DIR" ]; then
|
|
|
|
|
log_warn "Forgejo is already installed at ${FORGEJO_INSTALL_DIR}."
|
|
|
|
|
log_warn "Use the Update option to pull the latest image."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
separator
|
|
|
|
|
echo -e "${BOLD} Forgejo Installation${NC}"
|
|
|
|
|
separator
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
install_deps
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
ensure_docker_network
|
|
|
|
|
|
|
|
|
|
# Ensure D3V-NPMWG is on d3v-net (runtime + persistent in compose)
|
|
|
|
|
if [ -f "$COMPOSE_FILE" ]; then
|
|
|
|
|
if ! grep -q "d3v-net" "$COMPOSE_FILE"; then
|
|
|
|
|
log_step "Updating D3V-NPMWG compose to join '${DOCKER_NETWORK}'..."
|
|
|
|
|
local current_wg_host=""
|
|
|
|
|
current_wg_host=$(grep -E 'WG_HOST:' "$COMPOSE_FILE" | awk -F'"' '{print $2}')
|
|
|
|
|
[ -z "$current_wg_host" ] && current_wg_host=$(detect_public_ip)
|
|
|
|
|
cd "$INSTALL_DIR"
|
|
|
|
|
generate_docker_compose "$current_wg_host"
|
|
|
|
|
$(get_compose_cmd) up -d --no-recreate 2>/dev/null || true
|
|
|
|
|
log_ok "D3V-NPMWG compose updated."
|
|
|
|
|
fi
|
|
|
|
|
# Connect running container immediately (no restart needed)
|
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
|
|
|
docker network connect "${DOCKER_NETWORK}" "${CONTAINER_NAME}" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log_step "Creating ${FORGEJO_INSTALL_DIR}..."
|
|
|
|
|
mkdir -p "${FORGEJO_INSTALL_DIR}"
|
|
|
|
|
log_ok "Directory created."
|
|
|
|
|
|
|
|
|
|
log_step "Generating Forgejo docker-compose.yml..."
|
|
|
|
|
cat > "$FORGEJO_COMPOSE_FILE" <<YAML
|
|
|
|
|
services:
|
|
|
|
|
forgejo:
|
|
|
|
|
image: ${FORGEJO_IMAGE}
|
|
|
|
|
container_name: ${FORGEJO_CONTAINER_NAME}
|
|
|
|
|
restart: unless-stopped
|
|
|
|
|
environment:
|
|
|
|
|
- USER_UID=1000
|
|
|
|
|
- USER_GID=1000
|
|
|
|
|
- FORGEJO__database__DB_TYPE=sqlite3
|
|
|
|
|
- FORGEJO__server__SSH_PORT=2222
|
|
|
|
|
- FORGEJO__server__SSH_LISTEN_PORT=22
|
|
|
|
|
- FORGEJO__server__HTTP_PORT=3000
|
|
|
|
|
volumes:
|
|
|
|
|
- ./data:/data
|
|
|
|
|
ports:
|
|
|
|
|
- "3000:3000"
|
|
|
|
|
- "2222:22"
|
|
|
|
|
networks:
|
|
|
|
|
- d3v-net
|
|
|
|
|
|
|
|
|
|
networks:
|
|
|
|
|
d3v-net:
|
|
|
|
|
external: true
|
|
|
|
|
name: ${DOCKER_NETWORK}
|
|
|
|
|
YAML
|
|
|
|
|
log_ok "docker-compose.yml created."
|
|
|
|
|
|
|
|
|
|
local dc
|
|
|
|
|
dc=$(get_compose_cmd)
|
|
|
|
|
cd "$FORGEJO_INSTALL_DIR"
|
|
|
|
|
|
|
|
|
|
log_step "Pulling Forgejo image (this may take a few minutes)..."
|
|
|
|
|
$dc pull
|
|
|
|
|
log_ok "Image pulled."
|
|
|
|
|
|
|
|
|
|
log_step "Starting Forgejo..."
|
|
|
|
|
$dc up -d
|
|
|
|
|
log_ok "Forgejo started."
|
|
|
|
|
|
|
|
|
|
log_step "Waiting for Forgejo to be ready (10s)..."
|
|
|
|
|
sleep 10
|
|
|
|
|
|
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${FORGEJO_CONTAINER_NAME}$"; then
|
|
|
|
|
local server_ip
|
|
|
|
|
server_ip=$(hostname -I | awk '{print $1}')
|
2026-03-17 15:43:37 +00:00
|
|
|
|
2026-03-17 15:44:36 +00:00
|
|
|
# Block direct external access to port 3000 and 2222 (HTTPS git via NPM only)
|
|
|
|
|
log_step "Blocking external access to ports 3000 and 2222..."
|
2026-03-17 15:43:37 +00:00
|
|
|
iptables -D DOCKER-USER -p tcp --dport 3000 -j DROP 2>/dev/null || true
|
|
|
|
|
iptables -I DOCKER-USER -p tcp --dport 3000 -j DROP
|
2026-03-17 15:44:36 +00:00
|
|
|
iptables -D DOCKER-USER -p tcp --dport 2222 -j DROP 2>/dev/null || true
|
|
|
|
|
iptables -I DOCKER-USER -p tcp --dport 2222 -j DROP
|
|
|
|
|
log_ok "Ports 3000 and 2222 are now private (HTTPS git via NPM proxy only)."
|
|
|
|
|
save_iptables_rules
|
2026-03-17 15:43:37 +00:00
|
|
|
|
2026-03-17 15:25:48 +00:00
|
|
|
echo ""
|
|
|
|
|
separator
|
|
|
|
|
echo -e "${GREEN}${BOLD} FORGEJO INSTALLED SUCCESSFULLY!${NC}"
|
|
|
|
|
separator
|
2026-03-17 15:43:37 +00:00
|
|
|
echo -e " ${CYAN}Git HTTPS${NC}: via NPM proxy after hostname setup below"
|
2026-03-17 15:25:48 +00:00
|
|
|
echo ""
|
|
|
|
|
separator
|
2026-03-17 15:43:37 +00:00
|
|
|
echo -e "${BOLD} Add Hostname in Nginx Proxy Manager${NC}"
|
2026-03-17 15:25:48 +00:00
|
|
|
separator
|
2026-03-17 15:43:37 +00:00
|
|
|
echo -e " ${YELLOW}Step 1:${NC} Open NPM Admin UI at ${BOLD}http://${server_ip}:81${NC}"
|
2026-03-17 15:25:48 +00:00
|
|
|
echo ""
|
2026-03-17 15:43:37 +00:00
|
|
|
echo -e " ${YELLOW}Step 2:${NC} Go to ${BOLD}Proxy Hosts${NC} → click ${BOLD}Add Proxy Host${NC}"
|
2026-03-17 15:25:48 +00:00
|
|
|
echo ""
|
2026-03-17 15:43:37 +00:00
|
|
|
echo -e " ${YELLOW}Step 3:${NC} ${BOLD}Details${NC} tab:"
|
|
|
|
|
echo -e " Domain Names : ${CYAN}git.yourdomain.com${NC}"
|
|
|
|
|
echo -e " Scheme : ${CYAN}http${NC}"
|
|
|
|
|
echo -e " Forward Hostname : ${CYAN}forgejo${NC} (container name)"
|
|
|
|
|
echo -e " Forward Port : ${CYAN}3000${NC}"
|
|
|
|
|
echo -e " [x] Cache Assets [x] Block Common Exploits"
|
2026-03-17 15:25:48 +00:00
|
|
|
echo ""
|
2026-03-17 15:43:37 +00:00
|
|
|
echo -e " ${YELLOW}Step 4:${NC} ${BOLD}SSL${NC} tab → select ${BOLD}Request a new SSL Certificate${NC}"
|
|
|
|
|
echo -e " [x] Force SSL [x] HTTP/2 Support"
|
2026-03-17 15:25:48 +00:00
|
|
|
echo ""
|
2026-03-17 15:43:37 +00:00
|
|
|
echo -e " ${YELLOW}Step 5:${NC} Click ${BOLD}Save${NC}."
|
2026-03-17 15:25:48 +00:00
|
|
|
echo ""
|
2026-03-17 15:43:37 +00:00
|
|
|
echo -e " ${YELLOW}Step 6:${NC} Open ${BOLD}https://git.yourdomain.com${NC} → complete Forgejo setup"
|
|
|
|
|
echo -e " Server Domain : ${CYAN}git.yourdomain.com${NC}"
|
|
|
|
|
echo -e " Base URL : ${CYAN}https://git.yourdomain.com${NC}"
|
|
|
|
|
echo -e " SSH Port : ${CYAN}2222${NC}"
|
2026-03-17 15:25:48 +00:00
|
|
|
separator
|
|
|
|
|
else
|
|
|
|
|
log_err "Forgejo did not start. Check: docker logs ${FORGEJO_CONTAINER_NAME}"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
do_forgejo_uninstall() {
|
|
|
|
|
require_root
|
|
|
|
|
|
|
|
|
|
if [ ! -d "$FORGEJO_INSTALL_DIR" ]; then
|
|
|
|
|
log_warn "Forgejo is not installed at ${FORGEJO_INSTALL_DIR}."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log_warn "This will stop and remove Forgejo and ALL its data (repos, users, issues)."
|
|
|
|
|
read -rp "$(echo -e "${RED}Are you sure? (y/N): ${NC}")" confirm
|
|
|
|
|
if [[ ! "$confirm" =~ ^[yY]$ ]]; then
|
|
|
|
|
echo "Cancelled."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local dc
|
|
|
|
|
dc=$(get_compose_cmd)
|
|
|
|
|
cd "$FORGEJO_INSTALL_DIR" && $dc down -v 2>/dev/null || true
|
|
|
|
|
cd /
|
|
|
|
|
|
|
|
|
|
log_step "Removing ${FORGEJO_INSTALL_DIR}..."
|
|
|
|
|
rm -rf "$FORGEJO_INSTALL_DIR"
|
|
|
|
|
log_ok "Forgejo uninstalled."
|
2026-03-17 15:45:25 +00:00
|
|
|
|
|
|
|
|
log_step "Unblocking ports 3000 and 2222..."
|
|
|
|
|
iptables -D DOCKER-USER -p tcp --dport 3000 -j DROP 2>/dev/null || true
|
|
|
|
|
iptables -D DOCKER-USER -p tcp --dport 2222 -j DROP 2>/dev/null || true
|
|
|
|
|
log_ok "Ports 3000 and 2222 unblocked."
|
|
|
|
|
save_iptables_rules
|
2026-03-17 15:25:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
do_forgejo_update() {
|
|
|
|
|
require_root
|
|
|
|
|
|
|
|
|
|
if [ ! -d "$FORGEJO_INSTALL_DIR" ]; then
|
|
|
|
|
log_err "Forgejo is not installed. Install it first."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local dc
|
|
|
|
|
dc=$(get_compose_cmd)
|
|
|
|
|
cd "$FORGEJO_INSTALL_DIR" || return
|
|
|
|
|
|
|
|
|
|
log_step "Pulling latest Forgejo image..."
|
|
|
|
|
$dc pull
|
|
|
|
|
log_ok "Image pulled."
|
|
|
|
|
|
|
|
|
|
log_step "Recreating Forgejo container..."
|
|
|
|
|
$dc up -d
|
|
|
|
|
log_ok "Forgejo updated."
|
|
|
|
|
|
|
|
|
|
log_step "Cleaning old images..."
|
|
|
|
|
docker image prune -f > /dev/null 2>&1
|
|
|
|
|
log_ok "Done."
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 16:05:15 +00:00
|
|
|
do_forgejo_runner_install() {
|
|
|
|
|
require_root
|
|
|
|
|
|
|
|
|
|
if docker ps -a --format '{{.Names}}' | grep -q "^${FORGEJO_RUNNER_CONTAINER}$"; then
|
|
|
|
|
log_warn "Forgejo Runner is already installed."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
separator
|
|
|
|
|
echo -e "${BOLD} Forgejo Runner Installation${NC}"
|
|
|
|
|
separator
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}[?]${NC} Forgejo URL [https://src.d3v.ac]: ")" forgejo_url
|
|
|
|
|
forgejo_url="${forgejo_url:-https://src.d3v.ac}"
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " Get token from: ${BOLD}${forgejo_url}/-/admin/runners${NC}"
|
|
|
|
|
echo -e " Or: repo ${BOLD}Settings → Actions → Runners → Create new Runner${NC}"
|
|
|
|
|
echo ""
|
|
|
|
|
read -rsp "$(echo -e "${CYAN}[?]${NC} Runner registration token: ")" runner_token
|
|
|
|
|
echo ""
|
|
|
|
|
if [ -z "$runner_token" ]; then
|
|
|
|
|
log_err "Token cannot be empty. Cancelled."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
mkdir -p "$FORGEJO_RUNNER_DIR"
|
|
|
|
|
|
|
|
|
|
log_step "Registering runner with Forgejo..."
|
|
|
|
|
docker run --rm \
|
|
|
|
|
-v "${FORGEJO_RUNNER_DIR}:/data" \
|
|
|
|
|
"${FORGEJO_RUNNER_IMAGE}" \
|
2026-03-17 16:18:22 +00:00
|
|
|
act_runner register \
|
2026-03-17 16:05:15 +00:00
|
|
|
--no-interactive \
|
2026-03-17 16:18:22 +00:00
|
|
|
--instance "${forgejo_url}" \
|
2026-03-17 16:05:15 +00:00
|
|
|
--token "${runner_token}" \
|
|
|
|
|
--name "$(hostname)-runner" \
|
|
|
|
|
--labels "ubuntu-latest:docker://node:20-bullseye,ubuntu-22.04:docker://node:20-bullseye"
|
|
|
|
|
|
|
|
|
|
if [ $? -ne 0 ]; then
|
|
|
|
|
log_err "Runner registration failed. Check the token and Forgejo URL."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
log_ok "Runner registered."
|
|
|
|
|
|
|
|
|
|
log_step "Starting Forgejo Runner..."
|
|
|
|
|
docker run -d \
|
|
|
|
|
--name "${FORGEJO_RUNNER_CONTAINER}" \
|
|
|
|
|
--restart unless-stopped \
|
|
|
|
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
|
|
|
-v "${FORGEJO_RUNNER_DIR}:/data" \
|
|
|
|
|
"${FORGEJO_RUNNER_IMAGE}" \
|
2026-03-17 16:18:22 +00:00
|
|
|
act_runner daemon
|
2026-03-17 16:05:15 +00:00
|
|
|
|
|
|
|
|
log_ok "Forgejo Runner started."
|
|
|
|
|
echo ""
|
|
|
|
|
separator
|
|
|
|
|
echo -e "${GREEN}${BOLD} FORGEJO RUNNER INSTALLED!${NC}"
|
|
|
|
|
separator
|
|
|
|
|
echo -e " Runner connected to: ${BOLD}${forgejo_url}${NC}"
|
|
|
|
|
echo -e " Push a commit to trigger your first workflow."
|
|
|
|
|
separator
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
do_forgejo_runner_uninstall() {
|
|
|
|
|
require_root
|
|
|
|
|
|
|
|
|
|
if ! docker ps -a --format '{{.Names}}' | grep -q "^${FORGEJO_RUNNER_CONTAINER}$"; then
|
|
|
|
|
log_warn "Forgejo Runner is not installed."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log_warn "This will stop and remove the Forgejo Runner."
|
|
|
|
|
read -rp "$(echo -e "${RED}Are you sure? (y/N): ${NC}")" confirm
|
|
|
|
|
if [[ ! "$confirm" =~ ^[yY]$ ]]; then
|
|
|
|
|
echo "Cancelled."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
docker stop "${FORGEJO_RUNNER_CONTAINER}" 2>/dev/null || true
|
|
|
|
|
docker rm "${FORGEJO_RUNNER_CONTAINER}" 2>/dev/null || true
|
|
|
|
|
rm -rf "$FORGEJO_RUNNER_DIR"
|
|
|
|
|
log_ok "Forgejo Runner uninstalled."
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-17 15:25:48 +00:00
|
|
|
show_forgejo_menu() {
|
|
|
|
|
while true; do
|
|
|
|
|
echo ""
|
|
|
|
|
separator
|
|
|
|
|
echo -e "${BOLD} Forgejo Manager${NC}"
|
|
|
|
|
separator
|
|
|
|
|
echo " 1) Install Forgejo"
|
|
|
|
|
echo " 2) Uninstall Forgejo"
|
|
|
|
|
echo " 3) Update Forgejo"
|
2026-03-17 16:05:15 +00:00
|
|
|
echo " 4) Install Runner"
|
|
|
|
|
echo " 5) Uninstall Runner"
|
|
|
|
|
echo " 6) Back"
|
2026-03-17 15:25:48 +00:00
|
|
|
separator
|
2026-03-17 16:05:15 +00:00
|
|
|
read -rp " Select [1-6]: " choice
|
2026-03-17 15:25:48 +00:00
|
|
|
echo ""
|
|
|
|
|
case "$choice" in
|
|
|
|
|
1) do_forgejo_install ;;
|
|
|
|
|
2) do_forgejo_uninstall ;;
|
|
|
|
|
3) do_forgejo_update ;;
|
2026-03-17 16:05:15 +00:00
|
|
|
4) do_forgejo_runner_install ;;
|
|
|
|
|
5) do_forgejo_runner_uninstall ;;
|
|
|
|
|
6) return ;;
|
2026-03-17 15:25:48 +00:00
|
|
|
*) log_err "Invalid option." ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# Interactive menu
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
show_menu() {
|
2026-03-07 13:49:44 +00:00
|
|
|
while true; do
|
2026-03-07 14:19:13 +00:00
|
|
|
echo ""
|
|
|
|
|
separator
|
|
|
|
|
echo -e "${BOLD} D3V-NPMWG Installation Manager${NC}"
|
|
|
|
|
separator
|
|
|
|
|
echo " 1) Install D3V-NPMWG"
|
|
|
|
|
echo " 2) Uninstall D3V-NPMWG"
|
|
|
|
|
echo " 3) Uninstall D3V-NPMWG + Docker (Purge)"
|
|
|
|
|
echo " 4) Reset Admin Password"
|
|
|
|
|
echo " 5) Update D3V-NPMWG"
|
2026-03-08 08:50:25 +00:00
|
|
|
echo " 6) Manage Custom Stream Ports"
|
|
|
|
|
echo " 7) Toggle Admin Port 81 (Block/Unblock)"
|
2026-03-17 15:25:48 +00:00
|
|
|
echo " 8) Forgejo"
|
|
|
|
|
echo " 9) Exit"
|
2026-03-07 14:19:13 +00:00
|
|
|
separator
|
2026-03-17 15:25:48 +00:00
|
|
|
read -rp " Select [1-9]: " choice
|
2026-03-07 14:19:13 +00:00
|
|
|
echo ""
|
|
|
|
|
case "$choice" in
|
|
|
|
|
1) do_install ;;
|
|
|
|
|
2) do_uninstall ;;
|
|
|
|
|
3) do_purge ;;
|
|
|
|
|
4) do_reset_password ;;
|
|
|
|
|
5) do_update ;;
|
2026-03-08 08:50:25 +00:00
|
|
|
6) do_manage_ports ;;
|
|
|
|
|
7) do_toggle_port_81 ;;
|
2026-03-17 15:25:48 +00:00
|
|
|
8) show_forgejo_menu ;;
|
|
|
|
|
9) echo "Bye!"; exit 0 ;;
|
2026-03-07 14:19:13 +00:00
|
|
|
*) log_err "Invalid option." ;;
|
2026-03-07 13:49:44 +00:00
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 14:19:13 +00:00
|
|
|
show_help() {
|
|
|
|
|
echo "Usage: $0 [command]"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "Commands:"
|
2026-03-07 15:49:56 +00:00
|
|
|
echo " install Install D3V-NPMWG and dependencies"
|
|
|
|
|
echo " uninstall Remove D3V-NPMWG (keeps Docker)"
|
|
|
|
|
echo " purge Remove D3V-NPMWG AND Docker"
|
|
|
|
|
echo " reset Reset web admin password"
|
|
|
|
|
echo " update Pull latest image and restart"
|
2026-03-08 08:50:25 +00:00
|
|
|
echo " manage-ports Add or remove custom exposed Stream TCP/UDP ports"
|
2026-03-07 15:49:56 +00:00
|
|
|
echo " toggle-port Block or unblock external access to Admin UI (Port 81) using iptables"
|
2026-03-17 15:25:48 +00:00
|
|
|
echo " forgejo Open Forgejo submenu (install/uninstall/update)"
|
2026-03-07 15:49:56 +00:00
|
|
|
echo " help Show this help"
|
2026-03-07 14:19:13 +00:00
|
|
|
echo ""
|
|
|
|
|
echo "Run without arguments to open the interactive menu."
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------
|
|
|
|
|
# Entry point
|
|
|
|
|
# -----------------------------------------------------------
|
2026-03-17 16:16:50 +00:00
|
|
|
self_update "$@"
|
|
|
|
|
|
2026-03-07 14:00:56 +00:00
|
|
|
if [ "$#" -eq 0 ]; then
|
2026-03-07 14:19:13 +00:00
|
|
|
show_menu
|
2026-03-07 13:49:44 +00:00
|
|
|
else
|
2026-03-07 14:00:56 +00:00
|
|
|
case "$1" in
|
2026-03-07 15:49:56 +00:00
|
|
|
install) do_install ;;
|
|
|
|
|
uninstall) do_uninstall ;;
|
|
|
|
|
purge) do_purge ;;
|
|
|
|
|
reset) do_reset_password ;;
|
|
|
|
|
update) do_update ;;
|
2026-03-08 08:50:25 +00:00
|
|
|
manage-ports) do_manage_ports ;;
|
2026-03-07 15:49:56 +00:00
|
|
|
toggle-port) do_toggle_port_81 ;;
|
2026-03-17 15:25:48 +00:00
|
|
|
forgejo) show_forgejo_menu ;;
|
2026-03-07 14:19:13 +00:00
|
|
|
help|-h|--help) show_help ;;
|
|
|
|
|
*)
|
|
|
|
|
log_err "Unknown command: $1"
|
|
|
|
|
show_help
|
2026-03-07 14:03:59 +00:00
|
|
|
exit 1
|
|
|
|
|
;;
|
2026-03-07 13:49:44 +00:00
|
|
|
esac
|
|
|
|
|
fi
|