├── .github └── workflows │ └── docker-publish.yml ├── Dockerfile ├── README.md ├── backup.sh ├── entrypoint.sh └── main.conf /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker Image 2 | 3 | on: 4 | workflow_dispatch: # 手动触发 5 | schedule: 6 | - cron: '0 1 * * *' # Asia/Shanghai 时间10点(UTC 时间1:00) 7 | 8 | jobs: 9 | build-and-push: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | 16 | - name: Get latest release tag 17 | id: get_latest_tag 18 | run: | 19 | LATEST_TAG=$(curl -s https://api.github.com/repos/nezhahq/nezha/releases/latest | grep -oP '"tag_name": "\K(.*)(?=")') 20 | echo "Latest tag: $LATEST_TAG" 21 | echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT 22 | 23 | - name: Get current Docker image version 24 | id: get_current_version 25 | run: | 26 | # 从 Docker Hub 获取当前镜像的版本号 27 | CURRENT_TAG=$(curl -s https://hub.docker.com/v2/repositories/${{ secrets.DOCKER_USERNAME }}/argo-nezha/tags/ | jq -r '.results[] | select(.name != "latest") | .name' | sort -V | tail -n 1) 28 | if [ -z "$CURRENT_TAG" ]; then 29 | CURRENT_TAG="0.0.0" # 如果没有找到版本号,默认使用 0.0.0 30 | fi 31 | echo "Current Docker image version: $CURRENT_TAG" 32 | echo "current_tag=$CURRENT_TAG" >> $GITHUB_OUTPUT 33 | 34 | - name: Compare versions 35 | id: compare_versions 36 | run: | 37 | LATEST_TAG="${{ steps.get_latest_tag.outputs.latest_tag }}" 38 | CURRENT_TAG="${{ steps.get_current_version.outputs.current_tag }}" 39 | 40 | # 去掉版本号中的 "v" 前缀 41 | LATEST_VERSION=$(echo "$LATEST_TAG" | sed 's/^v//') 42 | CURRENT_VERSION=$(echo "$CURRENT_TAG" | sed 's/^v//') 43 | 44 | # 使用 sort -V 比较版本号 45 | HIGHER_VERSION=$(echo -e "$LATEST_VERSION\n$CURRENT_VERSION" | sort -V | tail -n 1) 46 | 47 | if [[ "$HIGHER_VERSION" == "$LATEST_VERSION" && "$LATEST_VERSION" != "$CURRENT_VERSION" ]]; then 48 | echo "New version available, continuing..." 49 | echo "should_build=true" >> $GITHUB_OUTPUT 50 | else 51 | echo "No new version available, stopping..." 52 | echo "should_build=false" >> $GITHUB_OUTPUT 53 | fi 54 | 55 | - name: Set up Docker Buildx 56 | if: steps.compare_versions.outputs.should_build == 'true' 57 | uses: docker/setup-buildx-action@v2 58 | 59 | - name: Log in to Docker Hub 60 | if: steps.compare_versions.outputs.should_build == 'true' 61 | uses: docker/login-action@v2 62 | with: 63 | username: ${{ secrets.DOCKER_USERNAME }} 64 | password: ${{ secrets.DOCKER_PASSWORD }} 65 | 66 | - name: Build and push Docker image 67 | if: steps.compare_versions.outputs.should_build == 'true' 68 | uses: docker/build-push-action@v4 69 | with: 70 | context: . 71 | platforms: linux/amd64,linux/arm64 72 | push: true 73 | tags: | 74 | ${{ secrets.DOCKER_USERNAME }}/argo-nezha:latest 75 | ${{ secrets.DOCKER_USERNAME }}/argo-nezha:${{ steps.get_latest_tag.outputs.latest_tag }} 76 | 77 | - name: Send Telegram notification 78 | if: steps.compare_versions.outputs.should_build == 'true' 79 | run: | 80 | MESSAGE="New argo-nezha image pushed! 🚀%0A%0A" 81 | MESSAGE+="Version: ${{ steps.get_latest_tag.outputs.latest_tag }}%0A" 82 | MESSAGE+="Image URL: https://hub.docker.com/r/${{ secrets.DOCKER_USERNAME }}/argo-nezha/tags" 83 | curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \ 84 | -d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \ 85 | -d text="$MESSAGE" 86 | 87 | cleanup-runs: 88 | runs-on: ubuntu-latest 89 | needs: build-and-push # 确保在 build-and-push 任务完成后执行 90 | permissions: 91 | actions: write # 需要 write 权限来删除运行日志 92 | contents: read # 需要 read 权限来访问仓库信息 93 | steps: 94 | - name: Delete old workflow runs 95 | uses: Mattraks/delete-workflow-runs@v2 96 | with: 97 | token: ${{ secrets.GH_TOKEN }} # 使用默认的 GITHUB_TOKEN 98 | repository: ${{ github.repository }} # 当前仓库 99 | retain_days: 7 # 保留最近 7 天的运行日志 100 | keep_minimum_runs: 6 # 至少保留 6 条运行日志 101 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/nezhahq/nezha AS app 2 | 3 | FROM nginx:stable-alpine 4 | 5 | RUN apk add --no-cache \ 6 | aws-cli \ 7 | tar \ 8 | gzip \ 9 | tzdata \ 10 | openssl \ 11 | sqlite \ 12 | coreutils 13 | 14 | COPY --from=cloudflare/cloudflared:latest /usr/local/bin/cloudflared /usr/local/bin/cloudflared 15 | COPY --from=app /etc/ssl/certs /etc/ssl/certs 16 | 17 | COPY main.conf /etc/nginx/conf.d/main.conf 18 | 19 | ENV TZ=Asia/Shanghai 20 | 21 | WORKDIR /dashboard 22 | 23 | COPY --from=app /dashboard/app /dashboard/app 24 | 25 | RUN mkdir -p /dashboard/data && chmod -R 777 /dashboard 26 | 27 | EXPOSE 8008 28 | 29 | ENV ARGO_DOMAIN="" \ 30 | CF_TOKEN="" \ 31 | R2_ACCESS_KEY_ID="" \ 32 | R2_BUCKET_NAME="" \ 33 | R2_ENDPOINT_URL="" \ 34 | R2_SECRET_ACCESS_KEY="" 35 | 36 | COPY backup.sh /backup.sh 37 | COPY entrypoint.sh /entrypoint.sh 38 | 39 | RUN chmod +x /backup.sh && chmod +x /entrypoint.sh 40 | 41 | CMD ["/entrypoint.sh"] 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Argo Nezha Dashboard V1 2 | 3 | Nezha Dashboard 是一个基于 [Nezha](https://github.com/nezhahq/nezha) 的项目,提供了一个强大的监控和管理界面。本项目使用 Docker 进行部署,并集成了 Cloudflare Tunnel 来提供安全的访问,项目优势: 4 | 5 | 1. 不暴露公网 IP,安全可靠 6 | 2. 单栈转双栈,纯 IPv6 环境也能使用 7 | 3. 自动备份,启动时自动还原备份文件,方便在线上容器平台使用 8 | 9 | ## 最近更新 10 | 2025-01-01 11 | - 修复备份的数据库可能受损的问题 12 | - 修复无法删除7天前备份文件的问题 13 | - 修改备份时间为每天2点、14点 14 | 15 | ## 功能 16 | 17 | - **监控和管理**: 提供实时的系统监控和管理功能。 18 | - **自动备份**: 支持自动备份到 Cloudflare R2 存储。 19 | - **安全访问**: 通过 nginx 和 Cloudflare Tunnel 提供安全的访问。 20 | - **自定义配置**: 支持通过环境变量进行自定义配置。 21 | 22 | ## 快速开始 23 | 24 | ### 环境变量 25 | 26 | 在运行项目之前,需要设置以下环境变量: 27 | 28 | - `R2_ACCESS_KEY_ID`: Cloudflare R2 访问密钥 ID。 29 | - `R2_SECRET_ACCESS_KEY`: Cloudflare R2 访问密钥。 30 | - `R2_ENDPOINT_URL`: Cloudflare R2 端点 URL。 31 | - `R2_BUCKET_NAME`: Cloudflare R2 存储桶名称。 32 | - `CF_TOKEN`: Cloudflare Tunnel 令牌。 33 | - `ARGO_DOMAIN`: 对外访问的域名。 34 | 35 | ### Tunnel 设置 36 | 37 | 在运行项目之前,需要 38 | 1. **CloudFlare开启GRPC流量代理** 39 | 2. **设置 Tunnel Public hostname** 40 | 41 | - Type: HTTPS 42 | - URL: localhost:443 43 | - Additional application settings 44 | - TLS 45 | - No TLS Verify on 46 | - HTTP2 connection on 47 | 48 | ### 构建和运行 49 | 50 | 1. **克隆仓库**: 51 | 52 | ```bash 53 | git clone https://github.com/yourusername/argo-nezha.git 54 | cd nezha-dashboard 55 | ``` 56 | 57 | 2. **构建 Docker 镜像**: 58 | 59 | ```bash 60 | docker build -t argo-nezha . 61 | ``` 62 | 63 | 3. **运行 Docker 容器**: 64 | 65 | ```bash 66 | docker run -d \ 67 | -e R2_ACCESS_KEY_ID="your_access_key_id" \ 68 | -e R2_SECRET_ACCESS_KEY="your_secret_access_key" \ 69 | -e R2_ENDPOINT_URL="your_endpoint_url" \ 70 | -e R2_BUCKET_NAME="your_bucket_name" \ 71 | -e CF_TOKEN="your_cf_token" \ 72 | -e ARGO_DOMAIN="your_domain" \ 73 | -p 443:443 \ 74 | argo-nezha 75 | ``` 76 | 77 | ## Dashboard 配置 78 | Agent对接地址【域名/IP:端口】 79 | 80 | Public hostname:443 81 | 82 | ## Agent 安装 83 | dashboard 右上角复制安装命令即可 84 | 85 | ## 备份和恢复 86 | 87 | 项目支持自动备份到 Cloudflare R2 存储,并在启动时尝试恢复最新备份。备份脚本 `/backup.sh` 会在每天凌晨 2 点执行。 88 | 89 | ## 许可证 90 | 91 | 本项目采用 [MIT 许可证](LICENSE)。 92 | -------------------------------------------------------------------------------- /backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 设置默认值 4 | R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID:-""} 5 | R2_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY:-""} 6 | R2_ENDPOINT_URL=${R2_ENDPOINT_URL:-""} 7 | R2_BUCKET_NAME=${R2_BUCKET_NAME:-""} 8 | 9 | # 检查必要的环境变量 10 | if [ -z "$R2_ACCESS_KEY_ID" ] || [ -z "$R2_SECRET_ACCESS_KEY" ] || [ -z "$R2_ENDPOINT_URL" ] || [ -z "$R2_BUCKET_NAME" ]; then 11 | echo "Warning: R2 environment variables are not set, skipping backup/restore" 12 | exit 0 13 | fi 14 | 15 | # R2配置 16 | export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY_ID" 17 | export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY" 18 | export AWS_DEFAULT_REGION="auto" 19 | export AWS_ENDPOINT_URL="$R2_ENDPOINT_URL" 20 | export BUCKET_NAME="$R2_BUCKET_NAME" 21 | 22 | # 恢复功能 23 | restore_backup() { 24 | echo "Checking for latest backup in R2..." 25 | LATEST_BACKUP=$(aws s3 ls "s3://${BUCKET_NAME}/backups/nezha_backup_" | sort | tail -n 1 | awk '{print $4}' || echo "") 26 | 27 | if [ -n "$LATEST_BACKUP" ]; then 28 | echo "Found backup: ${LATEST_BACKUP}" 29 | echo "Downloading and restoring backup..." 30 | aws s3 cp "s3://${BUCKET_NAME}/backups/${LATEST_BACKUP}" /tmp/ || true 31 | if [ -f "/tmp/${LATEST_BACKUP}" ]; then 32 | rm -rf /dashboard/data/* 33 | cd /dashboard && tar -xzf "/tmp/${LATEST_BACKUP}" 34 | rm "/tmp/${LATEST_BACKUP}" 35 | echo "Backup restored successfully" 36 | else 37 | echo "Failed to download backup" 38 | fi 39 | else 40 | echo "No backup found in R2, starting with fresh data directory" 41 | fi 42 | } 43 | 44 | # 备份功能 45 | create_backup() { 46 | TIMESTAMP=$(date +%Y%m%d_%H%M%S) 47 | BACKUP_FILE="nezha_backup_${TIMESTAMP}.tar.gz" 48 | BACKUP_DIR="/tmp/nezha_backup_${TIMESTAMP}" 49 | 50 | # 创建备份目录 51 | mkdir -p "${BACKUP_DIR}/data" 52 | 53 | # 备份 SQLite 数据库 54 | echo "Backing up SQLite database..." 55 | sqlite3 "/dashboard/data/sqlite.db" "VACUUM INTO '${BACKUP_DIR}/data/sqlite.db'" 56 | if [ $? -ne 0 ]; then 57 | echo "Error: Failed to backup SQLite database!" 58 | rm -rf "$BACKUP_DIR" 59 | return 1 60 | fi 61 | 62 | # 备份 config.yaml 63 | echo "Backing up config.yaml..." 64 | cp "/dashboard/data/config.yaml" "${BACKUP_DIR}/data/config.yaml" 65 | if [ $? -ne 0 ]; then 66 | echo "Error: Failed to backup config.yaml!" 67 | rm -rf "$BACKUP_DIR" 68 | return 1 69 | fi 70 | 71 | # 压缩备份文件 72 | echo "Compressing backup files..." 73 | tar -czf "/tmp/${BACKUP_FILE}" -C "$BACKUP_DIR" . 74 | if [ $? -ne 0 ]; then 75 | echo "Error: Failed to compress backup files!" 76 | rm -rf "$BACKUP_DIR" 77 | return 1 78 | fi 79 | 80 | # 上传到 R2 81 | echo "Uploading backup to R2..." 82 | aws s3 cp "/tmp/${BACKUP_FILE}" "s3://${BUCKET_NAME}/backups/${BACKUP_FILE}" 83 | if [ $? -ne 0 ]; then 84 | echo "Error: Failed to upload backup to R2!" 85 | rm "/tmp/${BACKUP_FILE}" 86 | rm -rf "$BACKUP_DIR" 87 | return 1 88 | fi 89 | 90 | # 清理临时文件 91 | rm "/tmp/${BACKUP_FILE}" 92 | rm -rf "$BACKUP_DIR" 93 | 94 | # 删除7天前的备份 95 | OLD_DATE=$(date -d "7 days ago" +%Y%m%d) 96 | echo "Cleaning up old backups before: $OLD_DATE" 97 | aws s3 ls "s3://${BUCKET_NAME}/backups/" | grep "nezha_backup_" | while read -r line; do 98 | backup_file=$(echo "$line" | awk '{print $4}') 99 | backup_date=$(echo "$backup_file" | grep -o "[0-9]\{8\}") 100 | if [ ! -z "$backup_date" ] && [ "$backup_date" -lt "$OLD_DATE" ]; then 101 | echo "Deleting old backup: $backup_file" 102 | aws s3 rm "s3://${BUCKET_NAME}/backups/$backup_file" 103 | fi 104 | done 105 | 106 | echo "Backup process completed successfully!" 107 | } 108 | 109 | # 根据参数执行不同的操作 110 | case "$1" in 111 | "restore") 112 | restore_backup 113 | ;; 114 | "backup") 115 | create_backup 116 | ;; 117 | *) 118 | echo "Usage: $0 {backup|restore}" 119 | exit 1 120 | ;; 121 | esac 122 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 设置默认值 4 | ARGO_DOMAIN=${ARGO_DOMAIN:-""} 5 | CF_TOKEN=${CF_TOKEN:-""} 6 | 7 | # 配置定时备份任务 8 | echo "Setting up backup cron job..." 9 | echo "0 2,14 * * * /backup.sh backup >> /dashboard/backup.log 2>&1" > /var/spool/cron/crontabs/root 10 | 11 | # 尝试恢复备份 12 | /backup.sh restore 13 | 14 | # 启动 crond 15 | echo "Starting crond ..." 16 | crond 17 | 18 | # 启动 dashboard app 19 | echo "Starting dashboard app..." 20 | /dashboard/app & 21 | sleep 3 22 | 23 | # 检查并生成证书 24 | if [ -n "$ARGO_DOMAIN" ]; then 25 | echo "Generating certificate for domain: $ARGO_DOMAIN" 26 | openssl genrsa -out /dashboard/nezha.key 2048 27 | openssl req -new -subj "/CN=$ARGO_DOMAIN" -key /dashboard/nezha.key -out /dashboard/nezha.csr 28 | openssl x509 -req -days 36500 -in /dashboard/nezha.csr -signkey /dashboard/nezha.key -out /dashboard/nezha.pem 29 | else 30 | echo "Warning: ARGO_DOMAIN is not set, skipping certificate generation" 31 | fi 32 | 33 | # 启动 Nginx 34 | echo "Starting nginx..." 35 | nginx -g "daemon off;" & 36 | sleep 3 37 | 38 | # 启动 cloudflared 39 | if [ -n "$CF_TOKEN" ]; then 40 | echo "Starting cloudflared..." 41 | cloudflared --no-autoupdate tunnel run --protocol http2 --token "$CF_TOKEN" >/dev/null 2>&1 & 42 | else 43 | echo "Warning: CF_TOKEN is not set, skipping cloudflared" 44 | fi 45 | 46 | # 等待所有后台进程 47 | wait 48 | -------------------------------------------------------------------------------- /main.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl; 3 | listen [::]:443 ssl; 4 | http2 on; # Nginx > 1.25.1,请注释上面两行,启用此行 5 | 6 | server_name $ARGO_DOMAIN; # 替换为你的域名 7 | ssl_certificate /dashboard/nezha.pem; # 域名证书路径 8 | ssl_certificate_key /dashboard/nezha.key; # 域名私钥路径 9 | # ssl_stapling on; 10 | ssl_session_timeout 1d; 11 | ssl_session_cache shared:SSL:10m; # 如果与其他配置冲突,请注释此项 12 | ssl_protocols TLSv1.2 TLSv1.3; 13 | 14 | underscores_in_headers on; 15 | set_real_ip_from 0.0.0.0/0; # 替换为你的 CDN 回源 IP 地址段 16 | real_ip_header CF-Connecting-IP; # 替换为你的 CDN 提供的私有 header,此处为 CloudFlare 默认 17 | # 如果你使用nginx作为最外层,把上面两行注释掉 18 | 19 | # grpc 相关 20 | location ^~ /proto.NezhaService/ { 21 | grpc_set_header Host $host; 22 | grpc_set_header nz-realip $http_CF_Connecting_IP; # 替换为你的 CDN 提供的私有 header,此处为 CloudFlare 默认 23 | # grpc_set_header nz-realip $remote_addr; # 如果你使用nginx作为最外层,就把上面一行注释掉,启用此行 24 | grpc_read_timeout 600s; 25 | grpc_send_timeout 600s; 26 | grpc_socket_keepalive on; 27 | client_max_body_size 10m; 28 | grpc_buffer_size 4m; 29 | grpc_pass grpc://dashboard; 30 | } 31 | # websocket 相关 32 | location ~* ^/api/v1/ws/(server|terminal|file)(.*)$ { 33 | proxy_set_header Host $host; 34 | proxy_set_header nz-realip $http_cf_connecting_ip; # 替换为你的 CDN 提供的私有 header,此处为 CloudFlare 默认 35 | # proxy_set_header nz-realip $remote_addr; # 如果你使用nginx作为最外层,就把上面一行注释掉,启用此行 36 | proxy_set_header Origin https://$host; 37 | proxy_set_header Upgrade $http_upgrade; 38 | proxy_set_header Connection "upgrade"; 39 | proxy_read_timeout 3600s; 40 | proxy_send_timeout 3600s; 41 | proxy_pass http://localhost:8008; 42 | } 43 | # web 44 | location / { 45 | proxy_set_header Host $host; 46 | proxy_set_header nz-realip $http_cf_connecting_ip; # 替换为你的 CDN 提供的私有 header,此处为 CloudFlare 默认 47 | # proxy_set_header nz-realip $remote_addr; # 如果你使用nginx作为最外层,就把上面一行注释掉,启用此行 48 | proxy_read_timeout 3600s; 49 | proxy_send_timeout 3600s; 50 | proxy_buffer_size 128k; 51 | proxy_buffers 4 256k; 52 | proxy_busy_buffers_size 256k; 53 | proxy_max_temp_file_size 0; 54 | proxy_pass http://localhost:8008; 55 | } 56 | } 57 | 58 | upstream dashboard { 59 | server localhost:8008; 60 | keepalive 512; 61 | } 62 | --------------------------------------------------------------------------------