diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cdeab40 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.git +.github +docs +*.md +!README.md +Dockerfile +data diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..3da4c6e --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,82 @@ +# Multi-arch image → Docker Hub + GHCR. +# 需要:仓库 Variables 里设 DOCKERHUB_USER;Secrets 里设 DOCKERHUB_TOKEN(Docker Hub Access Token, Read & Write) +# PR 只构建不推送,用来验证 Dockerfile。 +name: docker + +on: + push: + tags: ["v*"] + pull_request: + paths: + - Dockerfile + - .dockerignore + - docker/** + - go.mod + - go.sum + - .github/workflows/docker.yml + workflow_dispatch: + +jobs: + image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write # 推 GHCR 用 + env: + PUSH: ${{ github.event_name != 'pull_request' }} + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - if: env.PUSH == 'true' + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - if: env.PUSH == 'true' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: meta + uses: docker/metadata-action@v5 + with: + images: | + docker.io/${{ vars.DOCKERHUB_USER || 'githubflyideas' }}/fogping + ghcr.io/${{ github.repository_owner }}/fogping + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=ref,event=pr + + - uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: ${{ env.PUSH == 'true' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: VERSION=${{ github.ref_type == 'tag' && github.ref_name || 'dev' }} + provenance: false # 避免部分平台(如 RouterOS)解析 manifest 时遇到 attestation 条目 + cache-from: type=gha + cache-to: type=gha,mode=max + + # 冒烟测试:amd64 单架构 load 到本地,起容器打 /api/version + - name: smoke test (amd64) + run: | + docker buildx build --load --platform linux/amd64 -t fogping:smoke --cache-from type=gha . + docker run -d --name fp -p 8518:8518 \ + -e FOGPING_EDIT=1 -e FOGPING_USER=admin -e FOGPING_PASSWD=pw \ + fogping:smoke + for i in $(seq 1 20); do + curl -fsS http://127.0.0.1:8518/api/version && break + sleep 1 + done + curl -fsS http://127.0.0.1:8518/api/version | grep -q '"editable":true' + test "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8518/api/targets)" = 401 + docker logs fp diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..37d9523 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,66 @@ +# syntax=docker/dockerfile:1 +# fogping multi-arch image: linux/amd64, linux/arm64, linux/arm/v7 +# +# 与 json-ping 的区别:fogping 依赖 mattn/go-sqlite3(cgo),不能 CGO_ENABLED=0 交叉编译。 +# 这里用 tonistiigi/xx:构建机原生架构跑 clang,链接目标架构的 musl,静态产出,不走 QEMU。 + +ARG GO_VERSION=1.24 +ARG XX_VERSION=1.6.1 + +FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx + +FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS build +COPY --from=xx / / +RUN apk add --no-cache clang lld +ARG TARGETPLATFORM +# 目标架构的 C 运行时与头文件(sqlite3.c 编译要用) +RUN xx-apk add --no-cache musl-dev gcc + +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . + +ARG VERSION=dev +ENV CGO_ENABLED=1 +# -extldflags -static:musl 全静态,运行层不需要任何 libc +# sqlite_omit_load_extension:静态二进制里 dlopen 无意义,顺便去掉链接警告 +RUN xx-go build -trimpath -tags "timetzdata sqlite_omit_load_extension" \ + -ldflags="-s -w -X main.version=${VERSION} -linkmode external -extldflags -static" \ + -o /out/fogping . \ + && xx-verify --static /out/fogping + +# busybox:musl 约 1MB:提供 sh(入口脚本)和 wget(健康检查) +FROM busybox:1.37-musl + +LABEL org.opencontainers.image.title="fogping" \ + org.opencontainers.image.description="SmokePing-like latency monitor: one binary, embedded SQLite, targets edited in the web UI" \ + org.opencontainers.image.source="https://github.com/githubflyideas/fogping" \ + org.opencontainers.image.licenses="Apache-2.0" + +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=build /out/fogping /usr/local/bin/fogping +COPY --chmod=0755 docker/entrypoint.sh /usr/local/bin/docker-entrypoint + +# 程序以工作目录为根,数据库落在 ./data/fogping.db → 容器内 /data/data/fogping.db +RUN mkdir -p /data/data && chown -R 65532:65532 /data +WORKDIR /data + +ENV TZ=UTC +VOLUME /data +EXPOSE 8518 + +# 默认非 root;bind mount 时推荐 --user $(id -u):$(id -g) 与宿主目录属主对齐。 +# ICMP 用的是非特权 ping socket:Docker 20.10+ 默认在容器 netns 里放开 ping_group_range,无需 NET_RAW。 +USER 65532:65532 + +# /api/version 不经过登录,开启 user=/passwd= 后健康检查依然有效 +HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \ + CMD wget -q -O /dev/null http://127.0.0.1:8518/api/version || exit 1 + +# 参数原样传给 fogping,例如: +# docker run ... githubflyideas/fogping --edit user=admin passwd=change-me +# 不方便改命令行的场景(Docker Desktop / NAS / RouterOS)用环境变量: +# FOGPING_EDIT=1 FOGPING_USER=admin FOGPING_PASSWD=change-me FOGPING_DAYS=90 +ENTRYPOINT ["docker-entrypoint"] +CMD [] diff --git a/README.md b/README.md index 69b7679..0355469 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,41 @@ Open `http://localhost:8518`, log in(user=admin passwd=admin1234 ) `./fogping --help` prints the common commands with copy-ready examples. The rest of this page covers each step in detail. +## Docker + +Images for linux/amd64, arm64 and arm/v7 (Raspberry Pi, NAS, RouterOS). Data lives in `/data`. + +```bash +# first run: --edit so you can add targets in the web UI +docker run -d --name fogping --restart unless-stopped \ + --user $(id -u):$(id -g) -p 8518:8518 \ + -v ~/fogping:/data githubflyideas/fogping \ + --edit user=admin passwd=change-me + +# done editing: recreate without --edit (data in ~/fogping is kept) +docker rm -f fogping +docker run -d --name fogping --restart unless-stopped \ + --user $(id -u):$(id -g) -p 8518:8518 \ + -v ~/fogping:/data githubflyideas/fogping \ + user=admin passwd=change-me +``` + +Everything after the image name is passed to fogping as-is (`--edit`, `--days=90`, +`user=` / `passwd=`). Where you can set environment variables but not a command +(Docker Desktop, NAS container managers, RouterOS), use these instead: + +| Variable | Same as | +|---|---| +| `FOGPING_EDIT=1` | `--edit` | +| `FOGPING_USER=admin` / `FOGPING_PASSWD=change-me` | `user=admin passwd=change-me` (comma-separated for several) | +| `FOGPING_DAYS=90` | `--days=90` | + +`--edit` without a login is refused, as on bare metal. PING targets work without +extra capabilities on Docker 20.10+; `--user` keeps the mounted directory writable by you. + ## Contents +- [Docker](#docker) - [Requirements](#requirements) - [Install](#install) - [Allow ICMP (ping) without root](#allow-icmp-ping-without-root) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..c441795 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# Map FOGPING_* env vars to flags, for runners that can set env but not a command +# (Docker Desktop, NAS container managers, RouterOS /container). +# Command-line args are kept and come last, so they win over env on conflict. +set -e + +case "${FOGPING_EDIT:-}" in + 1|true|TRUE|yes|on) set -- --edit "$@" ;; +esac + +if [ -n "${FOGPING_DAYS:-}" ]; then + set -- "--days=${FOGPING_DAYS}" "$@" +fi + +if [ -n "${FOGPING_USER:-}" ] || [ -n "${FOGPING_PASSWD:-}" ]; then + set -- "user=${FOGPING_USER:-}" "passwd=${FOGPING_PASSWD:-}" "$@" +fi + +exec fogping "$@"