diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f4153a1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,140 @@ +name: release + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + version: + description: "Release version, for example v2.8.4" + required: true + type: string + +permissions: + contents: write + +jobs: + test: + env: + GOTOOLCHAIN: local + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.22.x" + - name: Test + run: go test -v ./... + + build: + env: + CGO_ENABLED: "0" + GOTOOLCHAIN: local + needs: test + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - goos: darwin + goarch: amd64 + - goos: darwin + goarch: arm64 + - goos: freebsd + goarch: 386 + - goos: freebsd + goarch: amd64 + - goos: freebsd + goarch: arm64 + - goos: linux + goarch: 386 + - goos: linux + goarch: amd64 + - goos: linux + goarch: arm + - goos: linux + goarch: arm64 + - goos: openbsd + goarch: 386 + - goos: openbsd + goarch: amd64 + - goos: openbsd + goarch: arm64 + - goos: windows + goarch: 386 + - goos: windows + goarch: amd64 + - goos: windows + goarch: arm64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.22.x" + + - name: Resolve version + id: version + shell: bash + run: | + version="${{ github.event.inputs.version || github.ref_name }}" + version="${version#v}" + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Build archive + shell: bash + run: | + target="webhook-${{ matrix.goos }}-${{ matrix.goarch }}" + mkdir -p "dist/$target" + + binary="webhook" + if [ "${{ matrix.goos }}" = "windows" ]; then + binary="webhook.exe" + fi + + GOOS="${{ matrix.goos }}" GOARCH="${{ matrix.goarch }}" \ + go build -trimpath -ldflags="-s -w -X main.version=${{ steps.version.outputs.version }}" \ + -o "dist/$target/$binary" . + + cp LICENSE README.md "dist/$target/" + tar -czf "dist/$target.tar.gz" -C dist "$target" + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: webhook-${{ matrix.goos }}-${{ matrix.goarch }} + path: dist/*.tar.gz + if-no-files-found: error + + publish: + needs: build + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Generate checksums + shell: bash + run: | + cd dist + sha256sum *.tar.gz > checksums.txt + + - name: Resolve tag + id: tag + shell: bash + run: | + tag="${{ github.event.inputs.version || github.ref_name }}" + echo "tag=$tag" >> "$GITHUB_OUTPUT" + + - name: Publish GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.tag.outputs.tag }} + name: ${{ steps.tag.outputs.tag }} + files: | + dist/*.tar.gz + dist/checksums.txt + generate_release_notes: true diff --git a/README.md b/README.md index 18826c4..9a46e43 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,26 @@ If you are using Debian linux ("stretch" or later), you can install webhook usin #### FreeBSD If you are using FreeBSD, you can install webhook using `pkg install webhook`. -### Download prebuilt binaries -Prebuilt binaries for different architectures are available at [GitHub Releases](https://github.com/adnanh/webhook/releases). - -## Configuration -Next step is to define some hooks you want [webhook][w] to serve. +### Download prebuilt binaries +Prebuilt binaries for different architectures are available at [GitHub Releases](https://github.com/xtulnx/webhook/releases). + +### One-line install or update +The install scripts download the latest GitHub Release asset for your platform and replace the local binary. + +Linux, macOS, FreeBSD, or OpenBSD: +```bash +curl -fsSL https://raw.githubusercontent.com/xtulnx/webhook/master/scripts/install.sh | sh +``` + +Windows PowerShell: +```powershell +irm https://raw.githubusercontent.com/xtulnx/webhook/master/scripts/install.ps1 | iex +``` + +Set `WEBHOOK_VERSION` to install a specific release tag, `WEBHOOK_REPO` to install from a fork, or `WEBHOOK_INSTALL_DIR` to choose the destination directory. + +## Configuration +Next step is to define some hooks you want [webhook][w] to serve. [webhook][w] supports JSON or YAML configuration files, but we'll focus primarily on JSON in the following example. Begin by creating an empty file named `hooks.json`. This file will contain an array of hooks the [webhook][w] will serve. Check [Hook definition page](docs/Hook-Definition.md) to see the detailed description of what properties a hook can contain, and how to use them. diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..d53c85c --- /dev/null +++ b/README_CN.md @@ -0,0 +1,456 @@ +# webhook 中文使用指南 + +`webhook` 是一个用 Go 编写的轻量级 HTTP 回调服务。它可以把请求中的 header、query、payload、文件等数据映射到命令参数或环境变量,并在规则校验通过后执行指定命令。 + +本仓库是从 `adnanh/webhook` fork 而来的增强版本,仓库地址为 [xtulnx/webhook](https://github.com/xtulnx/webhook)。除了上游原有能力外,本版本补充了 YAML 总配置、Admin 管理界面、命令超时、并发限制、反向代理真实 IP、PID 文件、一键安装脚本和 GitHub Release 编译发布流程。 + +![Admin 登录界面](images/img01.webp) + +## 主要特性 + +- 支持 JSON/YAML hook 配置。 +- 支持 `-c`、`-config`、`--config` 加载 YAML 总配置文件。 +- 支持 Admin UI 在线管理已加载的 hooks 文件。 +- Admin 登录使用 TOTP 动态验证码,登录后使用短期 JWT 会话。 +- 支持全局和单 hook 的命令执行超时。 +- 支持全局和单 hook 的并发执行限制。 +- 支持反向代理后的真实客户端 IP 提取,适配 `ip-whitelist`。 +- 支持 multipart 上传文件以临时环境变量形式传给命令。 +- 支持 PID 文件、日志文件、热重载、自定义响应头、TLS、Unix socket。 +- 提供 Linux/macOS/BSD 的 shell 一键安装脚本和 Windows PowerShell 一键安装脚本。 +- 提供 GitHub Actions 自动编译、打包、校验和发布脚本。 + +## 快速安装或更新 + +Linux、macOS、FreeBSD、OpenBSD: + +```bash +curl -fsSL https://raw.githubusercontent.com/xtulnx/webhook/master/scripts/install.sh | sh +``` + +Windows PowerShell: + +```powershell +irm https://raw.githubusercontent.com/xtulnx/webhook/master/scripts/install.ps1 | iex +``` + +安装脚本默认安装最新 GitHub Release,并进行 SHA256 校验。再次执行同一条命令即可更新到最新版本。 + +可通过环境变量调整安装行为: + +```bash +curl -fsSL https://raw.githubusercontent.com/xtulnx/webhook/master/scripts/install.sh | \ + WEBHOOK_VERSION=v2.8.4 WEBHOOK_INSTALL_DIR="$HOME/.local/bin" sh +``` + +PowerShell 示例: + +```powershell +$env:WEBHOOK_VERSION = "v2.8.4" +$env:WEBHOOK_INSTALL_DIR = "$env:USERPROFILE\bin" +irm https://raw.githubusercontent.com/xtulnx/webhook/master/scripts/install.ps1 | iex +``` + +常用变量: + +- `WEBHOOK_REPO`: GitHub 仓库,默认 `xtulnx/webhook`。 +- `WEBHOOK_VERSION`: 版本 tag,默认 `latest`。 +- `WEBHOOK_INSTALL_DIR`: 安装目录。Unix 默认 `/usr/local/bin`,Windows 默认 `%LOCALAPPDATA%\Programs\webhook`。 +- `WEBHOOK_BINARY_NAME`: 安装后的 binary 名称,默认 `webhook` 或 `webhook.exe`。 + +## 从源码构建 + +需要 Go 1.21 或更新版本。 + +```bash +git clone https://github.com/xtulnx/webhook.git +cd webhook +go build +./webhook -version +``` + +运行测试: + +```bash +go test ./... +``` + +生成本地 release 包: + +```bash +make release +make release-windows +``` + +## 最小可用示例 + +创建 hook 文件 `hooks.yaml`: + +```yaml +- id: deploy + execute-command: /usr/local/bin/deploy.sh + command-working-directory: /srv/app + http-methods: + - POST + response-message: Deploy triggered + trigger-rule: + match: + type: value + value: my-secret + parameter: + source: header + name: X-Webhook-Secret +``` + +启动服务: + +```bash +webhook -hooks hooks.yaml -verbose +``` + +触发 hook: + +```bash +curl -X POST \ + -H "X-Webhook-Secret: my-secret" \ + http://127.0.0.1:9000/hooks/deploy +``` + +默认监听地址是 `0.0.0.0:9000`,默认 hook URL 前缀是 `/hooks/`。 + +## 使用 YAML 总配置文件 + +本 fork 新增了总配置文件能力。可以把命令行参数写进 YAML 文件,再用 `-c`、`-config` 或 `--config` 启动。 + +示例 `webhook.yaml`: + +```yaml +ip: 0.0.0.0 +port: 1987 +urlprefix: wh +hooks: + - /etc/webhook/admin.yaml + - /etc/webhook/hooks.yaml +hotreload: true +nopanic: true +verbose: true +logfile: /var/log/webhook.log +pidfile: /var/run/webhook.pid +command-timeout: 30s +max-concurrency: 4 +``` + +启动: + +```bash +webhook -c /etc/webhook/webhook.yaml +``` + +命令行参数优先级高于配置文件。例如: + +```bash +webhook -c /etc/webhook/webhook.yaml -port 9000 -debug +``` + +完整示例可参考 [webhook.yaml.example](webhook.yaml.example)。 + +## Admin 管理界面 + +本 fork 内置 Admin UI 和 API,可在线查看、新增、编辑和删除已加载的 hooks 文件。 + +![Hook 结构化编辑器](images/img02.webp) + +启用方式: + +```yaml +admin: true +admin-path: admin +admin-totp-secret: "BASE32_TOTP_SECRET" +admin-jwt-secret: "replace-with-a-long-random-secret" +admin-session-ttl: 12h +``` + +启动后访问: + +```text +http://127.0.0.1:1987/admin/ +``` + +生成 TOTP Base32 密钥: + +```bash +python3 -c 'import base64, os; print(base64.b32encode(os.urandom(20)).decode().rstrip("="))' +``` + +把生成的密钥配置到 Google Authenticator、Microsoft Authenticator、1Password 等认证器中,然后在 Admin 登录页输入当前 6 位动态码。 + +注意事项: + +- `admin-path` 不能和 `urlprefix` 相同。 +- `admin-totp-secret` 必须是 Base32 编码。 +- `admin-jwt-secret` 必须设置为足够长的随机字符串。 +- 如果启用了 `template: true`,Admin 写入会进入只读模式,避免覆盖模板配置。 +- 生产环境建议放在 HTTPS 或受保护的反向代理后面。 + +![参数、规则与 JSON 预览](images/img03.webp) + +## Hook 配置要点 + +Hook 至少需要 `id` 和 `execute-command`: + +```yaml +- id: example + execute-command: /usr/local/bin/example.sh +``` + +常用字段: + +- `command-working-directory`: 命令工作目录。 +- `pass-arguments-to-command`: 把请求值作为命令参数传入。 +- `pass-environment-to-command`: 把请求值作为环境变量传入。 +- `pass-file-to-command`: 把文件参数传入命令。 +- `parse-parameters-as-json`: 把指定参数按 JSON 解析。 +- `trigger-rule`: 触发规则,支持 `and`、`or`、`not`、`match`。 +- `response-message`: 成功响应文本。 +- `success-http-response-code`: 成功响应状态码。 +- `trigger-rule-mismatch-http-response-code`: 规则不匹配时的状态码。 +- `include-command-output-in-response`: 把命令输出写入响应。 +- `include-command-output-in-response-on-error`: 命令失败时也返回命令输出。 +- `command-timeout`: 覆盖全局命令超时。 +- `max-concurrency`: 覆盖全局并发限制。 +- `keep-file-environment`: 暴露上传文件临时路径给命令。 + +更完整的字段说明请参考 [docs/Hook-Definition.md](docs/Hook-Definition.md)。 + +## 命令超时与并发限制 + +全局命令超时: + +```bash +webhook -hooks hooks.yaml -command-timeout 30s +``` + +单个 hook 覆盖: + +```yaml +- id: slow-task + execute-command: /usr/local/bin/slow-task.sh + command-timeout: 2m +``` + +`0` 表示禁用超时: + +```yaml +- id: no-timeout-task + execute-command: /usr/local/bin/task.sh + command-timeout: "0" +``` + +全局并发限制: + +```bash +webhook -hooks hooks.yaml -max-concurrency 2 +``` + +单个 hook 覆盖: + +```yaml +- id: deploy + execute-command: /usr/local/bin/deploy.sh + max-concurrency: 1 +``` + +当并发达到上限时,请求会返回 `503 Service Unavailable`。 + +## 反向代理与真实 IP + +如果 webhook 部署在 Nginx、Traefik、Caddy 等反向代理后面,直接使用 `RemoteAddr` 会拿到代理 IP,而不是真实客户端 IP。本 fork 支持可信代理配置,配合 `ip-whitelist` 使用。 + +配置示例: + +```yaml +real-ip-header: X-Real-Ip +trusted-proxies: "127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" +``` + +Nginx 示例: + +```nginx +location / { + proxy_pass http://127.0.0.1:1987; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +} +``` + +只有当请求来源属于 `trusted-proxies` 时,webhook 才会信任 `real-ip-header`,防止客户端伪造 IP header 绕过白名单。 + +## 上传文件处理 + +multipart 表单中,符合条件的 JSON 文件或被 `parse-parameters-as-json` 指定的文件会被解析到 payload。 + +如果命令需要直接读取上传文件,可启用: + +```yaml +- id: upload + execute-command: /usr/local/bin/handle-upload.sh + keep-file-environment: true +``` + +字段名为 `pkg` 的上传文件会在命令运行期间暴露为: + +```text +HOOK_FILE_PKG=/tmp/... +HOOK_FILENAME_PKG=original-file-name.tar.gz +``` + +命令结束后临时文件会被清理。建议 multipart 字段名只使用字母、数字和下划线,方便 shell 脚本读取。 + +## HTTPS、日志和 PID 文件 + +HTTPS: + +```bash +webhook -hooks hooks.yaml -secure -cert /etc/webhook/cert.pem -key /etc/webhook/key.pem +``` + +指定 TLS 最低版本: + +```bash +webhook -hooks hooks.yaml -secure -tls-min-version 1.2 +``` + +日志文件: + +```bash +webhook -hooks hooks.yaml -verbose -logfile /var/log/webhook.log +``` + +PID 文件: + +```bash +webhook -hooks hooks.yaml -pidfile /var/run/webhook.pid +``` + +热重载: + +```bash +webhook -hooks hooks.yaml -hotreload +``` + +## systemd 示例 + +`/etc/systemd/system/webhook.service`: + +```ini +[Unit] +Description=Webhook service +After=network.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/webhook -c /etc/webhook/webhook.yaml +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +启用并启动: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now webhook +sudo systemctl status webhook +``` + +## GitHub 编译发布流程 + +本仓库新增 [release workflow](.github/workflows/release.yml)。当推送 `v*` tag 时,会自动: + +1. 运行测试。 +2. 为 Linux、macOS、FreeBSD、OpenBSD、Windows 构建多架构 binary。 +3. 打包为 `webhook-{os}-{arch}.tar.gz`。 +4. 生成 `checksums.txt`。 +5. 发布到 GitHub Releases。 + +发布新版本: + +```bash +git tag v2.8.4 +git push origin v2.8.4 +``` + +也可以在 GitHub Actions 页面手动运行 `release` workflow,并填写版本号。 + +发布前建议确认: + +- `webhook.go` 中默认版本号已更新,或确认 workflow 注入的 tag 版本符合预期。 +- GitHub Actions 对仓库有 `contents: write` 权限。 +- 目标 tag 不要重复使用,除非你明确要覆盖 Release。 + +## 常见问题 + +### 一键安装下载失败 + +检查 GitHub Release 是否已经存在对应平台资产。例如 Linux amd64 需要: + +```text +webhook-linux-amd64.tar.gz +``` + +也可以指定仓库和版本: + +```bash +curl -fsSL https://raw.githubusercontent.com/xtulnx/webhook/master/scripts/install.sh | \ + WEBHOOK_REPO=xtulnx/webhook WEBHOOK_VERSION=v2.8.4 sh +``` + +### Admin 无法登录 + +优先检查: + +- `admin: true` 是否启用。 +- `admin-totp-secret` 是否为 Base32。 +- 认证器时间是否准确。 +- `admin-jwt-secret` 是否为空。 +- 请求路径是否为配置的 `admin-path`。 + +### IP 白名单在反向代理后不生效 + +需要同时配置: + +- 反向代理写入 `X-Real-IP` 或 `X-Forwarded-For`。 +- webhook 设置 `real-ip-header`。 +- webhook 设置 `trusted-proxies`,并包含代理服务器 IP。 + +### Hook 文件无法通过 Admin 修改 + +如果使用 `template: true`,Admin 写入会被禁用。请关闭模板模式,或手动编辑模板源文件。 + +## 目录说明 + +- `webhook.go`: 主入口和 CLI 参数。 +- `config.go`: YAML 总配置加载。 +- `admin.go`、`admin_store.go`、`admin_ui.go`: Admin UI/API 和配置写入。 +- `execution.go`: 命令超时和并发控制。 +- `realip.go`: 反向代理真实 IP 处理。 +- `internal/hook/`: hook 解析、规则判断和请求映射。 +- `internal/middleware/`: HTTP 日志和请求辅助中间件。 +- `internal/pidfile/`: PID 文件处理。 +- `adminui/`: 内嵌 Admin 前端页面。 +- `scripts/`: 一键安装脚本。 +- `.github/workflows/`: GitHub Actions 构建和发布脚本。 + +## 安全建议 + +- 不要提交真实密钥、生产 hook 定义或 TOTP/JWT secret。 +- Admin UI 建议放在 HTTPS 后面,并限制访问来源。 +- `trusted-proxies` 只填写你真正控制的代理地址。 +- `include-command-output-in-response` 可能泄露命令输出,生产环境谨慎开启。 +- hook 执行的脚本要自行做好权限、参数校验和日志记录。 + +## 许可证 + +本项目沿用上游 MIT License。详见 [LICENSE](LICENSE)。 diff --git a/images/img01.webp b/images/img01.webp new file mode 100644 index 0000000..f0729ca Binary files /dev/null and b/images/img01.webp differ diff --git a/images/img02.webp b/images/img02.webp new file mode 100644 index 0000000..4046071 Binary files /dev/null and b/images/img02.webp differ diff --git a/images/img03.webp b/images/img03.webp new file mode 100644 index 0000000..e2f98a2 Binary files /dev/null and b/images/img03.webp differ diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 0000000..667b671 --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,83 @@ +[CmdletBinding()] +param( + [string] $Repo = $env:WEBHOOK_REPO, + [string] $InstallDir = $env:WEBHOOK_INSTALL_DIR, + [string] $Version = $env:WEBHOOK_VERSION, + [string] $BinaryName = $env:WEBHOOK_BINARY_NAME +) + +$ErrorActionPreference = "Stop" + +if ([string]::IsNullOrWhiteSpace($Repo)) { + $Repo = "xtulnx/webhook" +} +if ([string]::IsNullOrWhiteSpace($InstallDir)) { + $InstallDir = Join-Path $env:LOCALAPPDATA "Programs\webhook" +} +if ([string]::IsNullOrWhiteSpace($Version)) { + $Version = "latest" +} +if ([string]::IsNullOrWhiteSpace($BinaryName)) { + $BinaryName = "webhook.exe" +} +if (-not $BinaryName.EndsWith(".exe", [StringComparison]::OrdinalIgnoreCase)) { + $BinaryName = "$BinaryName.exe" +} + +switch ($env:PROCESSOR_ARCHITECTURE) { + "AMD64" { $Arch = "amd64" } + "ARM64" { $Arch = "arm64" } + "x86" { $Arch = "386" } + default { + throw "Unsupported architecture: $env:PROCESSOR_ARCHITECTURE" + } +} + +$Asset = "webhook-windows-$Arch.tar.gz" +if ($Version -eq "latest") { + $Url = "https://github.com/$Repo/releases/latest/download/$Asset" + $ChecksumsUrl = "https://github.com/$Repo/releases/latest/download/checksums.txt" +} else { + $Url = "https://github.com/$Repo/releases/download/$Version/$Asset" + $ChecksumsUrl = "https://github.com/$Repo/releases/download/$Version/checksums.txt" +} + +$TempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString()) +New-Item -ItemType Directory -Path $TempDir | Out-Null + +try { + $Archive = Join-Path $TempDir $Asset + Write-Host "Downloading $Repo $Version for windows/$Arch..." + Invoke-WebRequest -Uri $Url -OutFile $Archive + $ChecksumsPath = Join-Path $TempDir "checksums.txt" + Invoke-WebRequest -Uri $ChecksumsUrl -OutFile $ChecksumsPath + + $ChecksumLine = Get-Content $ChecksumsPath | Where-Object { $_ -match "\s$([regex]::Escape($Asset))$" } | Select-Object -First 1 + if (-not $ChecksumLine) { + throw "Checksum for $Asset not found" + } + + $ExpectedHash = ($ChecksumLine -split "\s+")[0].ToLowerInvariant() + $ActualHash = (Get-FileHash -Algorithm SHA256 -Path $Archive).Hash.ToLowerInvariant() + if ($ActualHash -ne $ExpectedHash) { + throw "Checksum mismatch for $Asset" + } + + tar -xzf $Archive -C $TempDir + + $Source = Join-Path $TempDir "webhook-windows-$Arch\webhook.exe" + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null + Copy-Item -Path $Source -Destination (Join-Path $InstallDir $BinaryName) -Force + + Write-Host "Installed $BinaryName to $InstallDir" + & (Join-Path $InstallDir $BinaryName) -version + + $UserPath = [Environment]::GetEnvironmentVariable("Path", "User") + $Paths = $UserPath -split ";" | Where-Object { $_ } + if ($Paths -notcontains $InstallDir) { + [Environment]::SetEnvironmentVariable("Path", (($Paths + $InstallDir) -join ";"), "User") + Write-Host "Added $InstallDir to the user PATH. Open a new terminal to use webhook directly." + } +} finally { + Remove-Item -Path $TempDir -Recurse -Force -ErrorAction SilentlyContinue +} diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..3b7310a --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,121 @@ +#!/bin/sh +set -eu + +repo="${WEBHOOK_REPO:-xtulnx/webhook}" +install_dir="${WEBHOOK_INSTALL_DIR:-/usr/local/bin}" +version="${WEBHOOK_VERSION:-latest}" +binary_name="${WEBHOOK_BINARY_NAME:-webhook}" + +need() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "error: required command not found: $1" >&2 + exit 1 + fi +} + +detect_os() { + os="$(uname -s | tr '[:upper:]' '[:lower:]')" + case "$os" in + darwin|freebsd|linux|openbsd) printf '%s' "$os" ;; + *) echo "error: unsupported OS: $os" >&2; exit 1 ;; + esac +} + +detect_arch() { + arch="$(uname -m)" + case "$arch" in + x86_64|amd64) printf 'amd64' ;; + aarch64|arm64) printf 'arm64' ;; + i386|i686) printf '386' ;; + armv6l|armv7l|arm) printf 'arm' ;; + *) echo "error: unsupported architecture: $arch" >&2; exit 1 ;; + esac +} + +download() { + url="$1" + output="$2" + + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" -o "$output" + elif command -v wget >/dev/null 2>&1; then + wget -q "$url" -O "$output" + else + echo "error: curl or wget is required" >&2 + exit 1 + fi +} + +verify_checksum() { + checksums="$1" + archive="$2" + asset="$3" + + expected="$(grep " $asset\$" "$checksums" | awk '{print $1}')" + if [ -z "$expected" ]; then + echo "error: checksum for $asset not found" >&2 + exit 1 + fi + + if command -v sha256sum >/dev/null 2>&1; then + actual="$(sha256sum "$archive" | awk '{print $1}')" + elif command -v shasum >/dev/null 2>&1; then + actual="$(shasum -a 256 "$archive" | awk '{print $1}')" + elif command -v sha256 >/dev/null 2>&1; then + actual="$(sha256 -q "$archive")" + else + echo "warning: no SHA256 command found; skipping checksum verification" >&2 + return + fi + + if [ "$actual" != "$expected" ]; then + echo "error: checksum mismatch for $asset" >&2 + exit 1 + fi +} + +install_binary() { + src="$1" + dst="$2" + + mkdir -p "$install_dir" 2>/dev/null || true + if [ -w "$install_dir" ]; then + install -m 0755 "$src" "$dst" + elif command -v sudo >/dev/null 2>&1; then + sudo mkdir -p "$install_dir" + sudo install -m 0755 "$src" "$dst" + else + echo "error: $install_dir is not writable and sudo is not available" >&2 + exit 1 + fi +} + +need uname +need tar + +os="$(detect_os)" +arch="$(detect_arch)" +asset="webhook-$os-$arch.tar.gz" + +if [ "$version" = "latest" ]; then + url="https://github.com/$repo/releases/latest/download/$asset" + checksums_url="https://github.com/$repo/releases/latest/download/checksums.txt" +else + url="https://github.com/$repo/releases/download/$version/$asset" + checksums_url="https://github.com/$repo/releases/download/$version/checksums.txt" +fi + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT INT TERM + +archive="$tmp/$asset" +echo "Downloading $repo $version for $os/$arch..." +download "$url" "$archive" +download "$checksums_url" "$tmp/checksums.txt" +verify_checksum "$tmp/checksums.txt" "$archive" "$asset" + +tar -xzf "$archive" -C "$tmp" +install_binary "$tmp/webhook-$os-$arch/webhook" "$install_dir/$binary_name" + +echo "Installed $binary_name to $install_dir" +"$install_dir/$binary_name" -version diff --git a/webhook.go b/webhook.go index 1c57733..1945615 100644 --- a/webhook.go +++ b/webhook.go @@ -25,9 +25,7 @@ import ( "github.com/gorilla/mux" ) -const ( - version = "2.8.3" -) +var version = "2.8.3" var ( ip = flag.String("ip", "0.0.0.0", "ip the webhook should serve hooks on")