ping-rust 0.1.16

Menu-driven installer and manager for the shoes proxy server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
name: CI

on:
  push:
  pull_request:

permissions:
  contents: read

jobs:
  ubuntu:
    name: Ubuntu ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-22.04, ubuntu-24.04]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
      - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
        with:
          components: rustfmt, clippy
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
      - name: Check one-click installer
        shell: bash
        run: |
          set -euo pipefail
          bash -n scripts/install.sh
          bash scripts/install.sh --help >/dev/null

          # 最低 install-self 协议版本:单一来源常量,解析结果必须唯一非空。
          mapfile -t min_lines < <(
            grep -E '^readonly MIN_INSTALL_SELF_VERSION=[0-9]+\.[0-9]+\.[0-9]+$' \
              scripts/install.sh || true
          )
          test "${#min_lines[@]}" -eq 1 \
            || { echo "error: MIN_INSTALL_SELF_VERSION must appear exactly once" >&2; exit 1; }
          min_install_self_version="${min_lines[0]#readonly MIN_INSTALL_SELF_VERSION=}"
          test -n "${min_install_self_version}"
          printf 'MIN_INSTALL_SELF_VERSION=%s\n' "${min_install_self_version}"
          grep -Fq "不支持 install-self" scripts/install.sh \
            || { echo "error: missing install-self protocol error path" >&2; exit 1; }
          grep -Fq "\${MIN_INSTALL_SELF_VERSION}" scripts/install.sh \
            || { echo "error: protocol error must reference MIN_INSTALL_SELF_VERSION" >&2; exit 1; }

          if bash scripts/install.sh --version invalid >/dev/null 2>&1; then
            echo "error: installer accepted an invalid version" >&2
            exit 1
          fi
          if bash scripts/install.sh --install-dir relative >/dev/null 2>&1; then
            echo "error: installer accepted a relative install directory" >&2
            exit 1
          fi
          if bash scripts/install.sh --install-dir / >/dev/null 2>&1; then
            echo "error: installer accepted root install directory" >&2
            exit 1
          fi
          if bash scripts/install.sh --install-dir /opt/../tmp >/dev/null 2>&1; then
            echo "error: installer accepted ParentDir install directory" >&2
            exit 1
          fi
          if bash scripts/install.sh --install-dir /usr/local/bin/foo/.. >/dev/null 2>&1; then
            echo "error: installer accepted trailing ParentDir install directory" >&2
            exit 1
          fi

          # Stage-0 有界下载:本地 mock curl(与 acceptance 夹具同构)。
          # mock 故意不强制 --max-filesize,专门覆盖“缺 Content-Length 仍写盘”
          # 时的落盘后复核;超限 fail-closed,且不得执行未校验二进制。
          asset_root="$(mktemp -d)"
          install_root="$(mktemp -d)"
          executed_marker="${asset_root}/executed"
          target="$(uname -m)"
          case "$target" in
            x86_64 | amd64) asset="ping-rust-x86_64-unknown-linux-musl.tar.gz" ;;
            aarch64 | arm64) asset="ping-rust-aarch64-unknown-linux-musl.tar.gz" ;;
            *) echo "error: unsupported CI arch: $target" >&2; exit 1 ;;
          esac
          {
            printf '%s\n' '#!/bin/sh'
            printf 'printf executed > %q\n' "$executed_marker"
            printf '%s\n' \
              'if [ "${1:-}" = --version ]; then' \
              '  printf "ping-rust 0.0.0\n"' \
              '  exit 0' \
              'fi' \
              'if [ "${1:-}" = install-self ] && [ "${2:-}" = --help ]; then' \
              '  exit 0' \
              'fi' \
              'exit 42'
          } > "${asset_root}/fake-bin"
          chmod 0755 "${asset_root}/fake-bin"
          tar -czf "${asset_root}/${asset}" -C "$asset_root" \
            --transform 's/^fake-bin$/ping-rust/' fake-bin
          (
            cd "$asset_root"
            sha256sum "$asset" > SHA256SUMS
          )
          # mock 故意忽略 --max-filesize(模拟旧 curl / 无 Content-Length),
          # 把 fixture 写 stdout,由 install.sh 的 head -c + 落盘复核拦截。
          curl() {
            local output="" url=""
            while [[ "$#" -gt 0 ]]; do
              case "$1" in
                -o | --output) output="$2"; shift 2 ;;
                --max-filesize | --retry | --retry-delay | --proto) shift 2 ;;
                --tlsv1.2 | --fail | --location | --silent | --show-error) shift ;;
                *) url="$1"; shift ;;
              esac
            done
            test -n "$url"
            if [[ -n "$output" ]]; then
              cp "$asset_root/${url##*/}" "$output"
            else
              cat "$asset_root/${url##*/}"
            fi
          }
          export -f curl
          export asset_root

          # 正例:正常体积通过大小门禁与 checksum;执行仅发生在 checksum 之后。
          # install-self 本体由假二进制拒绝,但不得报体积错误。
          if err="$(bash scripts/install.sh \
            --version v0.0.0 \
            --install-dir "$install_root" \
            --quiet \
            --no-bootstrap 2>&1)"; then
            echo "error: installer unexpectedly succeeded with fake binary" >&2
            exit 1
          fi
          printf '%s\n' "$err" | grep -Fq '超过大小上限' \
            && { printf 'error: false size rejection:\n%s\n' "$err" >&2; exit 1; }
          printf '%s\n' "$err" | grep -Fq 'Rust 安装阶段失败' \
            || { printf 'error: expected install-self failure:\n%s\n' "$err" >&2; exit 1; }
          test -e "$executed_marker"
          rm -f -- "$executed_marker"
          test ! -e "${install_root}/ping-rust"

          # 负例 0:二进制缺少 install-self --help 时,须报协议错误并提示旧 tag 脚本。
          {
            printf '%s\n' '#!/bin/sh'
            printf 'printf executed > %q\n' "$executed_marker"
            printf '%s\n' \
              'if [ "${1:-}" = --version ]; then' \
              '  printf "ping-rust 0.0.0\n"' \
              '  exit 0' \
              'fi' \
              'exit 42'
          } > "${asset_root}/fake-bin"
          chmod 0755 "${asset_root}/fake-bin"
          tar -czf "${asset_root}/${asset}" -C "$asset_root" \
            --transform 's/^fake-bin$/ping-rust/' fake-bin
          (
            cd "$asset_root"
            sha256sum "$asset" > SHA256SUMS
          )
          rm -f -- "$executed_marker"
          if err="$(bash scripts/install.sh \
            --version v0.0.0 \
            --install-dir "$install_root" \
            --quiet \
            --no-bootstrap 2>&1)"; then
            echo "error: installer accepted binary without install-self" >&2
            exit 1
          fi
          printf '%s\n' "$err" | grep -Fq '不支持 install-self' \
            || { printf 'error: missing install-self protocol error:\n%s\n' "$err" >&2; exit 1; }
          printf '%s\n' "$err" | grep -Fq "${min_install_self_version}" \
            || { printf 'error: protocol error missing min version:\n%s\n' "$err" >&2; exit 1; }
          printf '%s\n' "$err" | grep -Fq '对应 tag' \
            || { printf 'error: protocol error missing tag script hint:\n%s\n' "$err" >&2; exit 1; }
          test -e "$executed_marker"
          rm -f -- "$executed_marker"
          test ! -e "${install_root}/ping-rust"

          # 恢复支持 install-self 的假二进制,继续体积负例。
          {
            printf '%s\n' '#!/bin/sh'
            printf 'printf executed > %q\n' "$executed_marker"
            printf '%s\n' \
              'if [ "${1:-}" = --version ]; then' \
              '  printf "ping-rust 0.0.0\n"' \
              '  exit 0' \
              'fi' \
              'if [ "${1:-}" = install-self ] && [ "${2:-}" = --help ]; then' \
              '  exit 0' \
              'fi' \
              'exit 42'
          } > "${asset_root}/fake-bin"
          chmod 0755 "${asset_root}/fake-bin"
          tar -czf "${asset_root}/${asset}" -C "$asset_root" \
            --transform 's/^fake-bin$/ping-rust/' fake-bin
          (
            cd "$asset_root"
            sha256sum "$asset" > SHA256SUMS
          )

          # 负例 1:SHA256SUMS 超 64 KiB,必须在 checksum/执行前失败。
          dd if=/dev/zero of="${asset_root}/SHA256SUMS" bs=65537 count=1 status=none
          if err="$(bash scripts/install.sh \
            --version v0.0.0 \
            --install-dir "$install_root" \
            --quiet \
            --no-bootstrap 2>&1)"; then
            echo "error: installer accepted oversized SHA256SUMS" >&2
            exit 1
          fi
          printf '%s\n' "$err" | grep -Fq 'SHA256SUMS超过大小上限' \
            || { printf 'error: missing SHA256SUMS size error:\n%s\n' "$err" >&2; exit 1; }
          test ! -e "$executed_marker"
          test ! -e "${install_root}/ping-rust"
          # 安装目标不得越过限制(不得留下可执行产物)。
          if find "$install_root" -type f 2>/dev/null | grep -q .; then
            echo "error: install_root not empty after oversized SHA256SUMS" >&2
            exit 1
          fi

          # 负例 2:归档超 64 MiB;mock 忽略 --max-filesize,由 head 硬限制 + 复核拦截。
          # 不把 65MiB fixture 内容打印到 Actions 日志。
          (
            cd "$asset_root"
            sha256sum "$asset" > SHA256SUMS
          )
          dd if=/dev/zero of="${asset_root}/${asset}" bs=1M count=65 status=none
          # 同步确认 head 硬上限:stdout 路径最多落 max+1 字节。
          hard_cap_probe="$(mktemp)"
          head -c $((67108864 + 1)) <"${asset_root}/${asset}" >"$hard_cap_probe"
          hard_cap_size="$(stat -c%s -- "$hard_cap_probe")"
          rm -f -- "$hard_cap_probe"
          test "$hard_cap_size" -eq $((67108864 + 1)) \
            || { echo "error: head hard-cap probe size=$hard_cap_size" >&2; exit 1; }
          if err="$(bash scripts/install.sh \
            --version v0.0.0 \
            --install-dir "$install_root" \
            --quiet \
            --no-bootstrap 2>&1)"; then
            echo "error: installer accepted oversized archive" >&2
            exit 1
          fi
          printf '%s\n' "$err" | grep -Fq '发布归档超过大小上限' \
            || { printf 'error: missing archive size error:\n%s\n' "$err" >&2; exit 1; }
          test ! -e "$executed_marker"
          test ! -e "${install_root}/ping-rust"
          if find "$install_root" -type f 2>/dev/null | grep -q .; then
            echo "error: install_root not empty after oversized archive" >&2
            exit 1
          fi
          # 不得把大二进制写进日志:仅核对错误文案长度。
          test "${#err}" -lt 4096 \
            || { echo "error: failure output unexpectedly large (${#err} bytes)" >&2; exit 1; }
          rm -rf -- "$asset_root" "$install_root"
      - run: cargo fmt --all -- --check
      - run: cargo test --locked
      - name: Install stable shoes for chain traffic acceptance
        if: matrix.os == 'ubuntu-24.04'
        shell: bash
        env:
          SHOES_ARCHIVE_SHA256: e60ccde92490624d9ff399dd58e69d4d6943b33c5cc4f0b0d1509b0c644fbc2a
        run: |
          set -euo pipefail
          archive="$RUNNER_TEMP/shoes-v0.2.7.tar.gz"
          directory="$RUNNER_TEMP/shoes-v0.2.7"
          curl --fail --location --retry 3 --proto '=https' --tlsv1.2 \
            --output "$archive" \
            https://github.com/cfal/shoes/releases/download/v0.2.7/shoes-x86_64-unknown-linux-musl.tar.gz
          echo "$SHOES_ARCHIVE_SHA256  $archive" | sha256sum --check --strict
          mkdir -p "$directory"
          tar -xzf "$archive" -C "$directory"
          test -x "$directory/shoes"
      - name: Prove stable shoes chain traffic has no direct fallback
        if: matrix.os == 'ubuntu-24.04'
        env:
          PING_RUST_SHOES_E2E_BIN: ${{ runner.temp }}/shoes-v0.2.7/shoes
        run: cargo test --locked --test chain_proxy_e2e -- --nocapture
      - run: cargo clippy --locked --all-targets -- -D warnings
      - run: cargo build --locked --release
      - name: Prove current tree install-self protocol
        if: matrix.os == 'ubuntu-24.04'
        shell: bash
        run: |
          set -euo pipefail
          test -x target/release/ping-rust
          target/release/ping-rust install-self --help >/dev/null
          target/release/ping-rust install-self --help \
            | grep -Eq 'install-self|安装|install'
      - name: Gate public latest against install-self protocol
        # 仅 PR→main 或 push main:防止 main 一键入口先于 Release 宣传新协议。
        # 普通 feature branch push 不跑网络门禁,便于从候选分支先 tag/release。
        if: >
          matrix.os == 'ubuntu-24.04' &&
          (
            (github.event_name == 'pull_request' && github.base_ref == 'main') ||
            (github.event_name == 'push' && github.ref == 'refs/heads/main')
          )
        env:
          GH_TOKEN: ${{ github.token }}
        shell: bash
        run: |
          set -euo pipefail
          mapfile -t min_lines < <(
            grep -E '^readonly MIN_INSTALL_SELF_VERSION=[0-9]+\.[0-9]+\.[0-9]+$' \
              scripts/install.sh || true
          )
          test "${#min_lines[@]}" -eq 1 \
            || { echo "error: MIN_INSTALL_SELF_VERSION must appear exactly once" >&2; exit 1; }
          min_version="${min_lines[0]#readonly MIN_INSTALL_SELF_VERSION=}"
          test -n "${min_version}"
          printf 'min_install_self_version=%s\n' "${min_version}"

          # fail-closed:API/HTTP/字段异常或无 release 均不得当成功。
          set +e
          latest_tag="$(
            gh api \
              -H "Accept: application/vnd.github+json" \
              "repos/${{ github.repository }}/releases/latest" \
              --jq .tag_name
          )"
          api_status=$?
          set -e
          if [ "${api_status}" -ne 0 ]; then
            echo "error: failed to query public latest release (fail-closed, exit=${api_status})" >&2
            exit 1
          fi
          if [ -z "${latest_tag}" ] || [ "${latest_tag}" = "null" ]; then
            echo "error: latest release missing tag_name (fail-closed)" >&2
            exit 1
          fi
          # 拒绝意外空白/多行。
          if [ "$(printf '%s' "${latest_tag}" | wc -l)" -ne 0 ] \
            || ! printf '%s' "${latest_tag}" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+$'; then
            echo "error: latest tag is not strict three-part semver: ${latest_tag}" >&2
            exit 1
          fi
          printf 'public_latest_tag=%s\n' "${latest_tag}"
          latest_version="${latest_tag#v}"
          if ! printf '%s\n' "${min_version}" \
            | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
            echo "error: min version is not strict three-part semver: ${min_version}" >&2
            exit 1
          fi

          # 严格三段数字比较;不得把格式异常当成功。
          IFS=. read -r min_maj min_min min_pat <<<"${min_version}"
          IFS=. read -r lat_maj lat_min lat_pat <<<"${latest_version}"
          for part in "${min_maj}" "${min_min}" "${min_pat}" \
            "${lat_maj}" "${lat_min}" "${lat_pat}"; do
            printf '%s\n' "${part}" | grep -Eq '^[0-9]+$' \
              || { echo "error: non-numeric semver component: ${part}" >&2; exit 1; }
          done
          cmp="eq"
          if [ "${lat_maj}" -lt "${min_maj}" ] \
            || { [ "${lat_maj}" -eq "${min_maj}" ] && [ "${lat_min}" -lt "${min_min}" ]; } \
            || {
              [ "${lat_maj}" -eq "${min_maj}" ] \
                && [ "${lat_min}" -eq "${min_min}" ] \
                && [ "${lat_pat}" -lt "${min_pat}" ]
            }; then
            cmp="lt"
          elif [ "${lat_maj}" -gt "${min_maj}" ] \
            || { [ "${lat_maj}" -eq "${min_maj}" ] && [ "${lat_min}" -gt "${min_min}" ]; } \
            || {
              [ "${lat_maj}" -eq "${min_maj}" ] \
                && [ "${lat_min}" -eq "${min_min}" ] \
                && [ "${lat_pat}" -gt "${min_pat}" ]
            }; then
            cmp="gt"
          fi
          case "${cmp}" in
            lt)
              cat >&2 <<EOF
          error: 公开 latest Release (${latest_tag}) 低于 install.sh 要求的最低 install-self 协议版本 (v${min_version})。
          禁止先合并/更新 main 一键入口。请先在候选分支创建 annotated tag 并让 Release workflow 全绿,再合并 main。
          EOF
              exit 1
              ;;
            eq | gt)
              printf 'ok: public latest %s satisfies min install-self v%s\n' \
                "${latest_tag}" "${min_version}"
              ;;
            *)
              echo "error: unexpected semver comparison result: ${cmp}" >&2
              exit 1
              ;;
          esac

  distro-build:
    name: ${{ matrix.name }}
    runs-on: ubuntu-24.04
    container: ${{ matrix.image }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: Debian 12
            image: debian:12
            family: apt
          - name: Rocky Linux 9
            image: rockylinux:9
            family: rpm
          - name: AlmaLinux 9
            image: almalinux:9
            family: rpm
    steps:
      - name: Install build prerequisites
        shell: bash
        run: |
          if [ "${{ matrix.family }}" = "apt" ]; then
            apt-get update
            DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
              build-essential ca-certificates curl git gzip pkg-config tar
          else
            dnf install -y \
              ca-certificates gcc gcc-c++ git gzip make \
              pkgconf-pkg-config tar
          fi
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
      - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        with:
          key: ${{ matrix.name }}
      - run: cargo test --locked
      - name: Install stable shoes for chain traffic acceptance
        if: matrix.name == 'Debian 12'
        shell: bash
        env:
          SHOES_ARCHIVE_SHA256: e60ccde92490624d9ff399dd58e69d4d6943b33c5cc4f0b0d1509b0c644fbc2a
        run: |
          set -euo pipefail
          archive="$RUNNER_TEMP/shoes-v0.2.7.tar.gz"
          directory="$RUNNER_TEMP/shoes-v0.2.7"
          curl --fail --location --retry 3 --proto '=https' --tlsv1.2 \
            --output "$archive" \
            https://github.com/cfal/shoes/releases/download/v0.2.7/shoes-x86_64-unknown-linux-musl.tar.gz
          echo "$SHOES_ARCHIVE_SHA256  $archive" | sha256sum --check --strict
          mkdir -p "$directory"
          tar -xzf "$archive" -C "$directory"
          test -x "$directory/shoes"
      - name: Prove stable shoes chain traffic has no direct fallback
        if: matrix.name == 'Debian 12'
        env:
          PING_RUST_SHOES_E2E_BIN: ${{ runner.temp }}/shoes-v0.2.7/shoes
        run: cargo test --locked --test chain_proxy_e2e -- --nocapture
      - run: cargo build --locked --release