Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,46 @@ on:
pull_request:
branches: [ main ]

permissions:
contents: read

jobs:
platform-doctor:
name: Platform Doctor
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: false

- name: Install Platform Doctor
run: go install github.com/chaosslabs/platform-doctor/cmd/platform-doctor@e8db1a5be9a72fc6aa9fcb5f5e0ae047f49325e1

- name: Scan repository
run: platform-doctor scan . --output "$RUNNER_TEMP/platform-doctor-report.md"

- name: Add report to job summary
if: always()
run: |
if [ -f "$RUNNER_TEMP/platform-doctor-report.md" ]; then
cat "$RUNNER_TEMP/platform-doctor-report.md" >> "$GITHUB_STEP_SUMMARY"
fi

- name: Upload Platform Doctor report
if: always()
uses: actions/upload-artifact@v4
with:
name: platform-doctor-report
path: ${{ runner.temp }}/platform-doctor-report.md
retention-days: 30

test:
name: Test and Lint
runs-on: ubuntu-latest
Expand Down
19 changes: 14 additions & 5 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ on:
branches: [ main ]
workflow_dispatch: # Allow manual triggering

permissions:
contents: read

jobs:
e2e:
name: End-to-End Tests
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
issues: write

steps:
- name: Checkout code
Expand Down Expand Up @@ -105,8 +105,17 @@ jobs:
path: dashboard/playwright-report/
retention-days: 7

- name: Comment PR on failure
if: failure() && github.event_name == 'push'
report-failure:
name: Report E2E failure
runs-on: ubuntu-latest
needs: e2e
if: failure() && needs.e2e.result == 'failure' && github.event_name == 'push'
permissions:
contents: read
# Required only to create the failure issue; test jobs stay read-only.
issues: write
steps:
- name: Create failure issue
uses: actions/github-script@v7
with:
script: |
Expand Down
53 changes: 43 additions & 10 deletions .github/workflows/release-on-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ on:
type: string

permissions:
contents: write
packages: write
contents: read

jobs:
release:
name: Build and Release
name: Build and Validate
runs-on: ubuntu-latest

outputs:
tag: ${{ steps.tag.outputs.tag_name }}
release_exists: ${{ steps.release_check.outputs.release_exists }}

steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down Expand Up @@ -222,19 +224,50 @@ jobs:
NETKIT_DASHBOARD_BASE_PATH: /netkit
NETKIT_DOCKER_IMAGE: netkit:dashboard-prefix-validation

- name: Upload validated release artifacts
uses: actions/upload-artifact@v4
with:
name: release-artifacts
path: |
dist/*
RELEASE_NOTES.md
if-no-files-found: error
retention-days: 1

publish:
name: Publish Release
runs-on: ubuntu-latest
needs: release
permissions:
# Required only to create/update GitHub releases.
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.release.outputs.tag }}
persist-credentials: false

- name: Download validated release artifacts
uses: actions/download-artifact@v4
with:
name: release-artifacts

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Delete existing release (if manual trigger)
if: github.event_name == 'workflow_dispatch' && steps.release_check.outputs.release_exists == 'true'
if: github.event_name == 'workflow_dispatch' && needs.release.outputs.release_exists == 'true'
run: |
echo "🗑️ Deleting existing release to recreate with updated artifacts..."
gh release delete "${{ steps.tag.outputs.tag_name }}" --yes
gh release delete "${{ needs.release.outputs.tag }}" --yes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag_name }}
name: "Release ${{ steps.tag.outputs.tag_name }}"
tag_name: ${{ needs.release.outputs.tag }}
name: "Release ${{ needs.release.outputs.tag }}"
body_path: RELEASE_NOTES.md
draft: false
prerelease: false
Expand All @@ -253,7 +286,7 @@ jobs:
- name: Extract version from tag
id: docker_meta
run: |
VERSION=${{ steps.tag.outputs.tag_name }}
VERSION=${{ needs.release.outputs.tag }}
VERSION=${VERSION#v}
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "tags=${{ secrets.DOCKERHUB_USERNAME }}/netkit:${VERSION},${{ secrets.DOCKERHUB_USERNAME }}/netkit:latest" >> $GITHUB_OUTPUT
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ dashboard/test-results
dashboard/playwrite-report
bin/

# macOS Finder metadata
.DS_Store

# Embedded dashboard files (generated during build)
internal/dashboard/out/

6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ FROM alpine:latest
ARG NETKIT_DASHBOARD_BASE_PATH=""
ENV NETKIT_DASHBOARD_BASE_PATH=${NETKIT_DASHBOARD_BASE_PATH}

RUN apk --no-cache add ca-certificates
RUN apk --no-cache add ca-certificates \
&& addgroup -S -g 10001 netkit \
&& adduser -S -D -H -u 10001 -G netkit netkit

WORKDIR /app

Expand All @@ -68,5 +70,7 @@ COPY --from=builder /build/netkit .
# Expose ports (proxy, admin, dashboard)
EXPOSE 8080 8081 3000

USER 10001:10001

# Run the proxy server with embedded dashboard
CMD ["./netkit", "serve", "--port", "8080", "--admin-port", "8081", "--dashboard", "--dashboard-port", "3000", "--log-level", "info"]
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,8 @@ codes are not captured. CONNECT records leave payload sizes at zero (unmeasured)
and omit request headers to avoid retaining proxy credentials. Transfer errors
after establishment do not change the setup result. Requests made through the
dashboard's destination-header API follow its separate HTTP forwarding path.

## Maintainer operations

See [RUNBOOK.md](RUNBOOK.md) for release validation, local troubleshooting,
CLI/container rollback, and the applicability of cost checks to this distributed tool.
89 changes: 89 additions & 0 deletions RUNBOOK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Netkit maintainer runbook

Netkit is a CLI and container image distributed to users, not a hosted service.
There is no centrally operated Netkit production deployment or service on-call.
Repository maintainers own release quality; users own their installations,
network exposure, infrastructure, and any upstream services they call.

## Validate a release

Run `make test`, `make e2e`, and `make test-docker-dashboard` before publishing.
The Docker check verifies the non-root runtime user, embedded dashboard,
JavaScript assets, and same-origin health endpoint. The tag workflow also checks
an image built with the `/netkit` URL prefix before publication.

The Release on Tag workflow publishes platform binaries and checksums to GitHub
Releases and images to Docker Hub. Build/test jobs have read-only repository
access. The publishing job needs `contents: write` to publish releases; Docker
Hub uses its own credentials and needs no GitHub `packages: write` permission.
E2E failure reporting alone needs `issues: write`. Platform Doctor currently
flags these explicit job permissions even though they are necessary.

## Diagnose a local installation

Start the CLI with `netkit serve --admin-port 8081`, then check:

```sh
curl --fail http://localhost:8081/healthz
curl --fail http://localhost:3000/api/admin/healthz
```

Inspect terminal output (or `docker logs CONTAINER`) for bind errors, upstream
connection failures, and dashboard configuration errors. Confirm the configured
ports and `NETKIT_DASHBOARD_BASE_PATH`. Keep proxy and admin access on trusted
networks. Request history is in memory and disappears when the process restarts;
redact captured credentials and bodies before sharing diagnostic reports.

## Rollback a CLI or container installation

Rollback means restoring a user's previous working version, not reverting a
central service deployment. Record the current version, startup arguments, and
previous working version before upgrading.

For a CLI installed from a release:

1. Stop the running Netkit process using the same terminal or service manager
that started it. This interrupts proxy connections and clears request history.
2. Download the correct OS/architecture binary and `checksums.txt` from the
previous known-good GitHub release. Verify the binary against its checksum
(`sha256sum` on Linux or `shasum -a 256` on macOS).
3. Replace the installed executable, make it executable on Unix, and restart
with the same arguments. For example, after downloading and verifying the
macOS ARM64 binary, for an installation in `$HOME/.local/bin`:

```sh
install -m 755 ./netkit-darwin-arm64 "$HOME/.local/bin/netkit"
"$HOME/.local/bin/netkit" serve --admin-port 8081
```

For a container, restore the previous image tag or digest in the user's Docker,
Compose, or orchestrator configuration and recreate the container with the same
ports, environment, and arguments. Avoid `latest` when reproducible upgrades and
rollback matter. Check `/healthz`, dashboard loading, and a request to a known
upstream after restarting either installation.

If a published version is broken, maintainers should document the affected
version and recommend the previous working version, revert or fix the regression
in a pull request, and publish a new patch release. Do not move an existing tag
or overwrite its artifacts as a rollback procedure.

## Budget and cost alerts: applicability

Netkit has no hosted-service cloud budget, billing account, or customer spend
limit to configure. Installing the CLI does not provision cloud infrastructure.
Users are responsible for any hosting, network egress, and paid upstream API
charges generated by their own requests. Netkit does not enforce those budgets.

Repository distribution can still consume GitHub Actions minutes/artifact
storage and Docker Hub allowances. The repository/billing account owner should
review those providers' usage before enabling paid capacity and after unexpected
build activity. No dollar budget or live cost alert is configured by this
repository, and this document does not claim otherwise. If paid usage is enabled,
the account owner must choose a monthly cap and configure provider notifications
at 80% and 100% of that cap. At 80%, review repeated builds and artifact retention;
at 100%, pause discretionary builds until the owner approves further spending.

This is maintainer guidance, not evidence that external billing settings or
notifications have been enabled. A cloud-budget finding against this CLI should
be treated as a check-applicability limitation, not as a missing production
control.
5 changes: 5 additions & 0 deletions scripts/validate-docker-dashboard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ docker run -d \
-p "127.0.0.1:${dashboard_port}:3000" \
"$image" >/dev/null

if [ "$(docker exec "$container" id -u)" != "10001" ]; then
echo "Docker image must run as the non-root netkit user (UID 10001)" >&2
exit 1
fi

for _ in 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; do
if curl -fsS "$dashboard_url" > "$tmp_dashboard"; then
break
Expand Down
Loading