diff --git a/README.md b/README.md index 2d7155a..1ba7b9a 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,13 @@ CLICD 是一个面向 LXC 的轻量容器管理面板,提供 Web 控制台、C ## 安装 -推荐使用 GitHub Actions 构建出的 Release 产物。下载 `clicd-linux-amd64.tar.gz` 后在目标服务器上执行: +推荐使用最新 Release 一键安装。在目标服务器上执行: + +```bash +curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo sh +``` + +也可以下载 GitHub Actions 构建出的 Release 产物 `clicd-linux-amd64.tar.gz` 后手动安装: ```bash tar -xzf clicd-linux-amd64.tar.gz @@ -66,64 +72,6 @@ Password: 随机 16 位密码 journalctl -u clicd --no-pager -n 80 | grep -E "Username:|Password:" ``` -## GitHub Actions 构建 - -仓库内置 `.github/workflows/build.yml`: - -- 推送到 `main` 或 `master` 时自动构建 Linux amd64 产物。 -- 创建 `v*` 标签时自动发布 GitHub Release。 -- 支持手动 `workflow_dispatch` 构建。 - -发布版本示例: - -```bash -git tag v1.0.0 -git push origin v1.0.0 -``` - -Release 会包含: - -```text -clicd-linux-amd64.tar.gz -clicd-linux-amd64 -SHA256SUMS -``` - -## CLI 模式 - -进入 CLI: - -```bash -clicd cli -``` - -仅使用 CLI,不自动拉起 Web 服务: - -```bash -systemctl stop clicd -systemctl disable clicd -clicd cli --no-web -``` - -重新启用 Web 控制台: - -```bash -systemctl enable --now clicd -``` - -## 常用服务命令 - -```bash -systemctl status clicd -systemctl restart clicd -journalctl -u clicd -f -``` - -## 注意事项 - -- 需要 root 权限安装和运行。 -- 宿主机需要支持 LXC。 -- NAT 和端口映射依赖 iptables。 -- 安全告警依赖 conntrack 或 `/proc/net/nf_conntrack`。 -- IPv6 分配要求宿主机拥有可用公网 IPv6 地址段。 -- 配置文件位于 `/root/.clicd/config.json`,其中包含敏感信息,不要提交到公开仓库。 +![alt text](/img/image.png) +![alt text](/img/image-1.png) +![alt text](/img/image-2.png) diff --git a/img/image-1.png b/img/image-1.png new file mode 100644 index 0000000..ebd02cf Binary files /dev/null and b/img/image-1.png differ diff --git a/img/image-2.png b/img/image-2.png new file mode 100644 index 0000000..a176976 Binary files /dev/null and b/img/image-2.png differ diff --git a/img/image.png b/img/image.png new file mode 100644 index 0000000..dfde8ea Binary files /dev/null and b/img/image.png differ diff --git a/install.sh b/install.sh index dbf800f..c4e658d 100644 --- a/install.sh +++ b/install.sh @@ -1,105 +1,302 @@ -#!/bin/bash -set -euo pipefail +#!/bin/sh +set -eu + +REPO="${CLICD_REPO:-MengMengCode/CLICD}" +VERSION="${CLICD_VERSION:-latest}" +ASSET="clicd-linux-amd64.tar.gz" echo "=====================================" echo " CLICD Installation" echo "=====================================" -if [ "$EUID" -ne 0 ]; then +log() { + echo "[clicd] $*" +} + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +has_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +is_systemd() { + has_cmd systemctl && [ -d /run/systemd/system ] +} + +is_openrc() { + has_cmd rc-service && has_cmd rc-update +} + +if [ "$(id -u)" -ne 0 ]; then echo "Please run as root: sudo ./install.sh" - echo "Or: curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo bash" + echo "Or: curl -fsSL https://raw.githubusercontent.com/${REPO}/main/install.sh | sudo sh" exit 1 fi -if [ ! -f "./clicd" ]; then - REPO="${CLICD_REPO:-MengMengCode/CLICD}" - VERSION="${CLICD_VERSION:-latest}" - ASSET="clicd-linux-amd64.tar.gz" +OS_ID="unknown" +OS_LIKE="" +if [ -r /etc/os-release ]; then + . /etc/os-release + OS_ID="${ID:-unknown}" + OS_LIKE="${ID_LIKE:-}" +fi + +install_apk() { + log "Installing dependencies with apk..." + apk update + apk add --no-cache \ + ca-certificates \ + curl \ + wget \ + tar \ + gzip \ + xz \ + lxc \ + lxc-download \ + lxc-openrc \ + lxc-bridge \ + lxc-templates \ + bridge-utils \ + iproute2 \ + iptables \ + dnsmasq + + for pkg in lxcfs shadow conntrack-tools quota-tools e2fsprogs xfsprogs; do + apk add --no-cache "$pkg" >/dev/null 2>&1 || log "Optional package not installed: $pkg" + done +} + +install_apt() { + log "Installing dependencies with apt..." + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y \ + ca-certificates \ + curl \ + wget \ + tar \ + gzip \ + xz-utils \ + lxc \ + lxc-templates \ + lxcfs \ + bridge-utils \ + uidmap \ + iproute2 \ + iptables \ + conntrack \ + quota \ + e2fsprogs \ + xfsprogs \ + dnsmasq-base +} + +enable_el_repos() { + if has_cmd dnf; then + dnf install -y 'dnf-command(config-manager)' >/dev/null 2>&1 || true + dnf install -y epel-release || true + dnf config-manager --set-enabled crb >/dev/null 2>&1 || true + dnf config-manager --set-enabled powertools >/dev/null 2>&1 || true + elif has_cmd yum; then + yum install -y yum-utils >/dev/null 2>&1 || true + yum install -y epel-release || true + yum-config-manager --enable powertools >/dev/null 2>&1 || true + fi +} + +install_dnf() { + log "Installing dependencies with dnf..." + enable_el_repos + dnf install -y \ + ca-certificates \ + curl \ + wget \ + tar \ + gzip \ + xz \ + lxc \ + lxc-templates \ + bridge-utils \ + iproute \ + iptables \ + conntrack-tools \ + shadow-utils \ + quota \ + e2fsprogs \ + xfsprogs \ + dnsmasq + + dnf install -y lxcfs >/dev/null 2>&1 || log "Optional package not installed: lxcfs" +} + +install_yum() { + log "Installing dependencies with yum..." + enable_el_repos + yum install -y \ + ca-certificates \ + curl \ + wget \ + tar \ + gzip \ + xz \ + lxc \ + lxc-templates \ + bridge-utils \ + iproute \ + iptables \ + conntrack-tools \ + shadow-utils \ + quota \ + e2fsprogs \ + xfsprogs \ + dnsmasq + + yum install -y lxcfs >/dev/null 2>&1 || log "Optional package not installed: lxcfs" +} + +install_dependencies() { + case "$OS_ID" in + ubuntu|debian) + install_apt + ;; + alpine) + install_apk + ;; + centos|rhel|rocky|almalinux|fedora) + if has_cmd dnf; then + install_dnf + elif has_cmd yum; then + install_yum + else + die "dnf/yum not found on $OS_ID" + fi + ;; + *) + if has_cmd apt-get; then + install_apt + elif has_cmd apk; then + install_apk + elif has_cmd dnf; then + install_dnf + elif has_cmd yum; then + install_yum + else + die "Unsupported Linux distribution: ${OS_ID} ${OS_LIKE}" + fi + ;; + esac + + has_cmd lxc-create || die "lxc-create is still missing after dependency installation." + has_cmd iptables || die "iptables is still missing after dependency installation." + has_cmd ip || die "iproute2/ip command is still missing after dependency installation." +} + +configure_kernel_networking() { + log "Enabling kernel forwarding settings..." + cat > /etc/sysctl.d/99-clicd.conf << 'EOF' +net.ipv4.ip_forward = 1 +net.ipv6.conf.all.forwarding = 1 +net.bridge.bridge-nf-call-iptables = 0 +net.bridge.bridge-nf-call-ip6tables = 0 +EOF + + modprobe br_netfilter >/dev/null 2>&1 || true + sysctl --system >/dev/null 2>&1 || true +} + +setup_lxc_services() { + log "Configuring LXC services..." + + if is_systemd; then + systemctl enable --now lxcfs >/dev/null 2>&1 || true + systemctl enable --now lxc-net >/dev/null 2>&1 || true + systemctl enable --now lxc >/dev/null 2>&1 || true + return + fi + + if is_openrc; then + rc-update add cgroups default >/dev/null 2>&1 || true + rc-service cgroups start >/dev/null 2>&1 || true + rc-update add lxc default >/dev/null 2>&1 || true + rc-service lxc start >/dev/null 2>&1 || true + rc-update add lxcfs default >/dev/null 2>&1 || true + rc-service lxcfs start >/dev/null 2>&1 || true + return + fi + + die "No supported service manager found. CLICD supports systemd or OpenRC." +} + +setup_subids() { + log "Setting up subordinate UID/GID ranges..." + touch /etc/subuid /etc/subgid + grep -q '^root:' /etc/subuid 2>/dev/null || echo 'root:100000:65536' >> /etc/subuid + grep -q '^root:' /etc/subgid 2>/dev/null || echo 'root:100000:65536' >> /etc/subgid +} + +try_enable_project_quota() { + root_src="$(findmnt -no SOURCE / 2>/dev/null || true)" + root_fs="$(findmnt -no FSTYPE / 2>/dev/null || true)" + + if [ "$root_fs" != "ext4" ] || [ -z "$root_src" ] || [ ! -b "$root_src" ]; then + log "Project quota auto-enable skipped for root filesystem: ${root_fs:-unknown}" + return + fi + + if ! has_cmd tune2fs; then + log "Project quota auto-enable skipped because tune2fs is unavailable." + return + fi + + if tune2fs -l "$root_src" 2>/dev/null | grep -q 'project'; then + log "Ext4 project quota support already appears to be enabled." + return + fi + + log "Ext4 project quota is not enabled. Disk limits will fall back to loopback images." +} + +download_release_if_needed() { + if [ -f "./clicd" ]; then + return + fi if [ "$VERSION" = "latest" ]; then - DOWNLOAD_URL="https://github.com/${REPO}/releases/latest/download/${ASSET}" + download_url="https://github.com/${REPO}/releases/latest/download/${ASSET}" else - DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${ASSET}" + download_url="https://github.com/${REPO}/releases/download/${VERSION}/${ASSET}" fi - echo "clicd binary not found in current directory." - echo "Downloading release package: ${DOWNLOAD_URL}" + log "clicd binary not found in current directory." + log "Downloading release package: ${download_url}" - TMP_DIR="$(mktemp -d)" - trap 'rm -rf "$TMP_DIR"' EXIT + tmp_dir="$(mktemp -d)" + trap 'rm -rf "$tmp_dir"' 0 - if command -v curl >/dev/null 2>&1; then - curl -fL "$DOWNLOAD_URL" -o "$TMP_DIR/$ASSET" - elif command -v wget >/dev/null 2>&1; then - wget -O "$TMP_DIR/$ASSET" "$DOWNLOAD_URL" + if has_cmd curl; then + curl -fL "$download_url" -o "$tmp_dir/$ASSET" + elif has_cmd wget; then + wget -O "$tmp_dir/$ASSET" "$download_url" else - echo "ERROR: curl or wget is required to download the release package." - exit 1 + die "curl or wget is required to download the release package." fi - tar -xzf "$TMP_DIR/$ASSET" -C "$TMP_DIR" - cd "$TMP_DIR/clicd-linux-amd64" -fi + tar -xzf "$tmp_dir/$ASSET" -C "$tmp_dir" + cd "$tmp_dir/clicd-linux-amd64" + [ -f "./clicd" ] || die "Downloaded release package did not contain clicd." +} -if ! command -v lxc-create >/dev/null 2>&1; then - echo "LXC is not installed. Installing dependencies..." - if command -v apt-get >/dev/null 2>&1; then - apt-get update - apt-get install -y lxc lxc-templates bridge-utils xz-utils quota - elif command -v yum >/dev/null 2>&1; then - yum install -y epel-release - yum install -y lxc lxc-templates xz quota - elif command -v dnf >/dev/null 2>&1; then - dnf install -y lxc lxc-templates xz quota - else - echo "Could not detect package manager. Please install LXC manually." - exit 1 - fi -fi +install_binary() { + cp ./clicd /usr/local/bin/clicd + chmod +x /usr/local/bin/clicd + log "Installed binary: /usr/local/bin/clicd" +} -# Setup subordinate UID/GID for unprivileged containers -echo "Setting up subordinate UID/GID ranges..." -grep -q '^root:' /etc/subuid 2>/dev/null || echo 'root:100000:65536' >> /etc/subuid -grep -q '^root:' /etc/subgid 2>/dev/null || echo 'root:100000:65536' >> /etc/subgid - -# Enable ext4 project quota if supported -if tune2fs -l /dev/sda1 2>/dev/null | grep -q 'Filesystem features'; then - echo "Enabling ext4 project quota..." - mkdir -p /etc/initramfs-tools/hooks /etc/initramfs-tools/scripts/local-premount - - # Hook to copy tune2fs into initramfs - cat > /etc/initramfs-tools/hooks/tune2fs-hook << 'HOOK' -#!/bin/sh -PREREQ="" -prereqs() { echo "$PREREQ"; } -case "$1" in prereqs) prereqs; exit 0;; esac -. /usr/share/initramfs-tools/hook-functions -copy_exec /sbin/tune2fs /sbin/tune2fs -copy_exec /usr/sbin/setquota /usr/sbin/setquota -HOOK - chmod +x /etc/initramfs-tools/hooks/tune2fs-hook - - # Script to run tune2fs before mount - cat > /etc/initramfs-tools/scripts/local-premount/prjquota << 'SCRIPT' -#!/bin/sh -PREREQ="" -prereqs() { echo "$PREREQ"; } -case "$1" in prereqs) prereqs; exit 0;; esac -/sbin/tune2fs -O project -Q prjquota /dev/sda1 2>/dev/null -SCRIPT - chmod +x /etc/initramfs-tools/scripts/local-premount/prjquota - - update-initramfs -u -k all 2>/dev/null || true - - # Add prjquota to fstab if not already there - grep -q 'prjquota' /etc/fstab 2>/dev/null || sed -i 's|ext4 rw,|ext4 rw,prjquota,|' /etc/fstab -fi - -cp ./clicd /usr/local/bin/clicd -chmod +x /usr/local/bin/clicd -echo "Installed binary: /usr/local/bin/clicd" - -cat > /etc/systemd/system/clicd.service << 'EOF' +install_systemd_service() { + cat > /etc/systemd/system/clicd.service << 'EOF' [Unit] Description=CLICD - LXC Container Manager After=network.target lxc.service @@ -115,23 +312,81 @@ Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin WantedBy=multi-user.target EOF -systemctl daemon-reload -systemctl enable clicd -systemctl restart clicd + systemctl daemon-reload + systemctl enable clicd + systemctl restart clicd +} +install_openrc_service() { + cat > /etc/init.d/clicd << 'EOF' +#!/sbin/openrc-run + +name="CLICD" +description="CLICD - LXC Container Manager" +command="/usr/local/bin/clicd" +command_args="server" +command_background=true +pidfile="/run/clicd.pid" +output_log="/var/log/clicd.log" +error_log="/var/log/clicd.err" + +depend() { + need net + after lxc +} +EOF + + chmod +x /etc/init.d/clicd + rc-update add clicd default + rc-service clicd restart +} + +install_service() { + log "Installing CLICD service..." + + if is_systemd; then + install_systemd_service + elif is_openrc; then + install_openrc_service + else + die "No supported service manager found. CLICD supports systemd or OpenRC." + fi +} + +print_summary() { + echo "" + echo "=====================================" + echo " Installation Complete" + echo "=====================================" + echo " Web: http://YOUR_SERVER_IP:8999" + echo " Binary: /usr/local/bin/clicd" + if is_systemd; then + echo " Service: systemctl {start|stop|restart|status} clicd" + echo " Logs: journalctl -u clicd -f" + elif is_openrc; then + echo " Service: rc-service clicd {start|stop|restart|status}" + echo " Logs: tail -f /var/log/clicd.log /var/log/clicd.err" + fi + echo "=====================================" + echo "" + echo "Initial credentials, if this was the first run:" + if is_systemd; then + journalctl -u clicd --no-pager -n 80 | grep -E "Username:|Password:" || true + else + grep -E "Username:|Password:" /var/log/clicd.log /var/log/clicd.err 2>/dev/null || true + fi + echo "" + echo "If no password is shown, this server already had /root/.clicd/config.json." + echo "The existing admin password cannot be recovered from the bcrypt hash." +} + +install_dependencies +configure_kernel_networking +setup_lxc_services +setup_subids +try_enable_project_quota +download_release_if_needed +install_binary +install_service sleep 2 - -echo "" -echo "=====================================" -echo " Installation Complete" -echo "=====================================" -echo " Web: http://YOUR_SERVER_IP:8999" -echo " Service: systemctl {start|stop|restart|status} clicd" -echo " Logs: journalctl -u clicd -f" -echo "=====================================" -echo "" -echo "Initial credentials, if this was the first run:" -journalctl -u clicd --no-pager -n 80 | grep -E "Username:|Password:" || true -echo "" -echo "If no password is shown, this server already had /root/.clicd/config.json." -echo "The existing admin password cannot be recovered from the bcrypt hash." +print_summary