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
69 changes: 69 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
# Vet + test on every OS. Each runner compiles and tests its OWN build-tagged
# firewall backend (pf/nft/wfp), so a backend that fails to build — e.g. a
# missing RenderRules on a non-host OS — is caught here, not at release time.
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: go vet ./...
- run: go test ./...
# Race detector once (ubuntu): the runner toggles posture from a ticker
# loop with a recovery probe — catch data races there. Skipped on windows
# where -race needs a gcc toolchain that isn't preinstalled.
- if: matrix.os == 'ubuntu-latest'
run: go test -race ./...

# Verify gofmt cleanliness once (formatting is OS-independent).
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: gofmt
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "These files need gofmt:"; echo "$unformatted"; exit 1
fi

# Cross-compile all five release targets and assert every artifact exists.
build-all:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: make build-all
- name: verify artifacts
run: |
set -eu
for f in \
dist/dezhban-darwin-arm64 \
dist/dezhban-darwin-amd64 \
dist/dezhban-linux-amd64 \
dist/dezhban-linux-arm64 \
dist/dezhban-windows-amd64.exe; do
test -f "$f" || { echo "missing artifact: $f"; exit 1; }
done
echo "all 5 artifacts present"
46 changes: 45 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ PLATFORMS := \
linux/arm64 \
windows/amd64

.PHONY: build vet test build-all clean
# Config used by the dev-loop targets. Override on the command line, e.g.
# make rules CONFIG=configs/dezhban.vpn-guard.json
CONFIG ?= configs/dezhban.local.json
MODE ?= guard

.PHONY: build vet test build-all clean lint \
run-dry validate rules doctor \
install-local reinstall uninstall-local panic

build: ## Build for the host platform into ./$(BINARY)
go build $(LDFLAGS) -o $(BINARY) $(PKG)
Expand All @@ -30,6 +37,43 @@ vet: ## Static checks
test: ## Run all tests
go test ./...

lint: ## golangci-lint if installed, else gofmt + vet
@if command -v golangci-lint >/dev/null 2>&1; then \
golangci-lint run; \
else \
echo "golangci-lint not found; running gofmt + go vet"; \
test -z "$$(gofmt -l .)" || { echo "gofmt needed:"; gofmt -l .; exit 1; }; \
go vet ./...; \
fi

# --- dev loop (no root) -----------------------------------------------------

run-dry: ## Build + run the monitor in dry-run (no firewall touch)
CONFIG=$(CONFIG) sh scripts/dev.sh

validate: ## Load + validate CONFIG without side effects
go run $(PKG) validate --config $(CONFIG)

rules: ## Print the ruleset for MODE (guard|fullblock|legacy) without applying
go run $(PKG) print-rules --mode $(MODE) --config $(CONFIG)

doctor: ## Diagnose VPN guard config (add ARGS=--discover on macOS)
go run $(PKG) doctor --config $(CONFIG) $(ARGS)

# --- service lifecycle (sudo) ----------------------------------------------

install-local: ## Validate, build, install config + service, start it
CONFIG=$(CONFIG) sh scripts/install-local.sh

reinstall: ## Tear down then install fresh
CONFIG=$(CONFIG) sh scripts/reinstall.sh

uninstall-local: ## Stop + unregister the service
sh scripts/uninstall-local.sh

panic: ## Force-remove dezhban's rules (lockout escape hatch)
sh scripts/panic.sh

build-all: ## Cross-compile every platform into ./$(DIST)
@mkdir -p $(DIST)
@for p in $(PLATFORMS); do \
Expand Down
44 changes: 32 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,25 @@ linked).
## Usage

```
dezhban <command> [flags]
dezhban [-v] <command> [flags]

Commands:
run Run the monitor→decision→enforcement loop (root)
block Manually block network egress (root)
unblock Remove dezhban's firewall rules (root)
status Show version, config, service, and block state
panic Force-remove dezhban's rules even with no daemon (root)
install Register dezhban as a boot-persistent OS service (root)
uninstall Remove the OS service (root)
start Start the installed service (root)
stop Stop the installed service (removes firewall rules) (root)
detect-vpn Print detected VPN tunnel interfaces for config
version Print the version
run Run the monitor→decision→enforcement loop (root)
block Manually block network egress (root)
unblock Remove dezhban's firewall rules (root)
status Show version, config, service, and block state
validate Load + validate a config file (no root, no effects)
print-rules Print the ruleset a block/guard would apply, without applying it
doctor Diagnose VPN guard config (tunnels, endpoints, lockout risks)
panic Force-remove dezhban's rules even with no daemon (root)
install Register dezhban as a boot-persistent OS service (root)
uninstall Remove the OS service (root)
start Start the installed service (root)
stop Stop the installed service (removes firewall rules) (root)
detect-vpn Print detected VPN tunnel interfaces for config
version Print the version

Global: -v / --verbose override the configured log level to debug
```

Privileged commands require root/admin and print a clear error otherwise.
Expand All @@ -118,6 +123,21 @@ sudo dezhban panic # standalone teardown, no daem
- `block --guard` — install the VPN interface guard (see below).
- `unblock --force` — accepted for symmetry (`unblock` is already unconditional).

### Diagnose & test safely (no root)

Inspect and validate before you risk a block — none of these touch the firewall:

```bash
dezhban validate --config <config> # parse + validate, summarize
dezhban print-rules --mode guard --config <config> # exact ruleset, not applied
dezhban doctor --config <config> # tunnels, subnets, endpoint sanity
dezhban doctor --discover --config <config> # macOS: find the VPN's real server IP
```

`print-rules --mode` takes `guard`, `fullblock`, or `legacy`. See
[docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) for the lockout-recovery
runbook and [docs/CONFIG.md](docs/CONFIG.md) for the full config reference.

## Configuration

JSON, with durations as strings (e.g. `"30s"`). See
Expand Down
Loading
Loading