├── .envrc ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── deploy.yml │ └── update-flake-lock.yaml ├── .gitignore ├── deploy.sh ├── flake.lock ├── flake.nix ├── fly.toml ├── go.mod ├── go.sum ├── main.go └── shell.nix /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | groups: 8 | actions-deps: 9 | patterns: 10 | - '*' 11 | 12 | - package-ecosystem: "gomod" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | groups: 17 | go-deps: 18 | patterns: 19 | - '*' 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | lints: 10 | name: Lints 11 | runs-on: ubuntu-latest 12 | permissions: 13 | id-token: write 14 | contents: read 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: DeterminateSystems/flake-checker-action@main 18 | with: 19 | fail-mode: true 20 | - uses: DeterminateSystems/nix-installer-action@main 21 | with: 22 | determinate: true 23 | - uses: DeterminateSystems/flakehub-cache-action@main 24 | - name: Check Go fmt 25 | run: nix develop --command sh -c 'gofmt -l . && test -z "$(gofmt -l .)"' 26 | 27 | build-x86_64-linux: 28 | name: Build x86_64 Linux 29 | runs-on: ubuntu-latest 30 | 31 | permissions: 32 | contents: write 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: DeterminateSystems/nix-installer-action@main 36 | with: 37 | determinate: true 38 | - name: Build server 39 | run: nix build --print-build-logs .#packages.x86_64-linux.bonk 40 | - name: Build docker image 41 | run: | 42 | nix build .#packages.x86_64-linux.dockerImage 43 | - name: Fix hash mismatches 44 | if: failure() && github.event_name == 'pull_request' 45 | id: fix-hashes 46 | run: | 47 | git stash --include-untracked 48 | git fetch --depth=1 origin "$GITHUB_HEAD_REF" 49 | git checkout -B "$GITHUB_HEAD_REF" "${{ github.event.pull_request.head.sha }}" 50 | 51 | determinate-nixd fix hashes --auto-apply 52 | 53 | if ! git diff --quiet; then 54 | git config user.name "github-actions[bot]" 55 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 56 | git add --update --ignore-removal . 57 | git commit -m "[dependabot skip] Automatically fix Nix hashes" 58 | git push origin "$GITHUB_HEAD_REF" 59 | fi 60 | 61 | git checkout - 62 | git stash pop || true 63 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy bonk 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | deploy: 9 | name: Deploy to Prod 10 | runs-on: ubuntu-latest 11 | permissions: 12 | id-token: write 13 | contents: read 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: DeterminateSystems/nix-installer-action@main 17 | with: 18 | determinate: true 19 | - uses: DeterminateSystems/flakehub-cache-action@main 20 | - uses: DeterminateSystems/flake-checker-action@main 21 | - uses: superfly/flyctl-actions/setup-flyctl@master 22 | - run: ./deploy.sh 23 | env: 24 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/update-flake-lock.yaml: -------------------------------------------------------------------------------- 1 | name: update-flake-lock 2 | 3 | on: 4 | workflow_dispatch: # enable manual triggering 5 | schedule: 6 | - cron: "0 0 * * 0" # every Sunday at midnight 7 | 8 | jobs: 9 | lockfile: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | id-token: write 13 | contents: write 14 | pull-requests: write 15 | issues: write 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: DeterminateSystems/nix-installer-action@main 19 | with: 20 | determinate: true 21 | - uses: DeterminateSystems/flakehub-cache-action@main 22 | - uses: DeterminateSystems/update-flake-lock@main 23 | with: 24 | pr-title: Update flake.lock 25 | pr-labels: | 26 | dependencies 27 | automated 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | bonk 3 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nix-shell 2 | #!nix-shell -i bash ./shell.nix 3 | 4 | set -euxo pipefail 5 | 6 | PROJECT_NAME=bonk-api 7 | 8 | nix build .#packages.x86_64-linux.dockerImage 9 | # note: will write auth token to XDG_RUNTIME_DIR 10 | flyctl auth token | skopeo login -u x --password-stdin registry.fly.io 11 | skopeo \ 12 | --insecure-policy \ 13 | copy docker-archive:"$(realpath ./result)" \ 14 | docker://registry.fly.io/$PROJECT_NAME:latest \ 15 | --format v2s2 16 | 17 | flyctl deploy -i registry.fly.io/$PROJECT_NAME:latest --remote-only 18 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "locked": { 5 | "lastModified": 1733328505, 6 | "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", 7 | "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", 8 | "revCount": 69, 9 | "type": "tarball", 10 | "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz" 11 | }, 12 | "original": { 13 | "type": "tarball", 14 | "url": "https://flakehub.com/f/edolstra/flake-compat/1" 15 | } 16 | }, 17 | "nixpkgs": { 18 | "locked": { 19 | "lastModified": 1748460289, 20 | "narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=", 21 | "rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102", 22 | "revCount": 807377, 23 | "type": "tarball", 24 | "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.807377%2Brev-96ec055edbe5ee227f28cdbc3f1ddf1df5965102/01972133-94d2-786e-bfeb-34136c7f0b07/source.tar.gz" 25 | }, 26 | "original": { 27 | "type": "tarball", 28 | "url": "https://flakehub.com/f/NixOS/nixpkgs/0.1" 29 | } 30 | }, 31 | "root": { 32 | "inputs": { 33 | "flake-compat": "flake-compat", 34 | "nixpkgs": "nixpkgs" 35 | } 36 | } 37 | }, 38 | "root": "root", 39 | "version": 7 40 | } 41 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "bonk-api"; 3 | 4 | inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1"; 5 | inputs.flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1"; 6 | 7 | outputs = 8 | { self 9 | , nixpkgs 10 | , ... 11 | } @ inputs: 12 | let 13 | supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; 14 | forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f { 15 | pkgs = import nixpkgs { inherit system; }; 16 | }); 17 | in 18 | { 19 | devShells = forEachSupportedSystem ({ pkgs }: { 20 | default = pkgs.mkShell { 21 | name = "bonk"; 22 | packages = with pkgs; [ 23 | go 24 | flyctl 25 | skopeo 26 | codespell 27 | nixpkgs-fmt 28 | ]; 29 | }; 30 | }); 31 | 32 | packages = forEachSupportedSystem ({ pkgs }: rec { 33 | default = bonk; 34 | 35 | bonk = pkgs.buildGoModule { 36 | pname = "bonk"; 37 | version = "unreleased"; 38 | 39 | src = ./.; 40 | 41 | goSum = ./go.sum; 42 | vendorHash = "sha256-YyhnlizXJeDFgeT0wVkIK8e/L30IipbOU4OuPEEnseE="; 43 | }; 44 | 45 | dockerImage = 46 | let 47 | linuxPkgs = nixpkgs.legacyPackages.x86_64-linux; 48 | in 49 | pkgs.dockerTools.buildLayeredImage { 50 | name = "bonk"; 51 | contents = [ linuxPkgs.cacert bonk ]; 52 | maxLayers = 300; 53 | config = { 54 | ExposedPorts."80/tcp" = { }; 55 | Cmd = [ "${self.packages.x86_64-linux.bonk}/bin/bonk" ]; 56 | }; 57 | }; 58 | }); 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | app = "bonk-api" 2 | 3 | kill_signal = "SIGINT" 4 | kill_timeout = 5 5 | processes = [] 6 | 7 | [build] 8 | image = "flyio/bonk-api:latest" 9 | 10 | [env] 11 | 12 | [experimental] 13 | allowed_public_ports = [] 14 | auto_rollback = true 15 | 16 | [[services]] 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DeterminateSystems/bonk 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.1 6 | 7 | require tailscale.com v1.84.1 8 | 9 | require ( 10 | filippo.io/edwards25519 v1.1.0 // indirect 11 | github.com/akutz/memconn v0.1.0 // indirect 12 | github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect 13 | github.com/aws/aws-sdk-go-v2 v1.36.0 // indirect 14 | github.com/aws/aws-sdk-go-v2/config v1.29.5 // indirect 15 | github.com/aws/aws-sdk-go-v2/credentials v1.17.58 // indirect 16 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 // indirect 17 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 // indirect 18 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 // indirect 19 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect 20 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect 21 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 // indirect 22 | github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect 23 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 // indirect 24 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 // indirect 25 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 // indirect 26 | github.com/aws/smithy-go v1.22.2 // indirect 27 | github.com/coder/websocket v1.8.12 // indirect 28 | github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect 29 | github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect 30 | github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect 31 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 32 | github.com/gaissmai/bart v0.18.0 // indirect 33 | github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect 34 | github.com/go-ole/go-ole v1.3.0 // indirect 35 | github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect 36 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 37 | github.com/google/btree v1.1.2 // indirect 38 | github.com/google/go-cmp v0.6.0 // indirect 39 | github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect 40 | github.com/google/uuid v1.6.0 // indirect 41 | github.com/gorilla/csrf v1.7.3 // indirect 42 | github.com/gorilla/securecookie v1.1.2 // indirect 43 | github.com/hdevalence/ed25519consensus v0.2.0 // indirect 44 | github.com/illarion/gonotify/v3 v3.0.2 // indirect 45 | github.com/jmespath/go-jmespath v0.4.0 // indirect 46 | github.com/jsimonetti/rtnetlink v1.4.0 // indirect 47 | github.com/klauspost/compress v1.17.11 // indirect 48 | github.com/mdlayher/genetlink v1.3.2 // indirect 49 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect 50 | github.com/mdlayher/sdnotify v1.0.0 // indirect 51 | github.com/mdlayher/socket v0.5.0 // indirect 52 | github.com/miekg/dns v1.1.58 // indirect 53 | github.com/mitchellh/go-ps v1.0.0 // indirect 54 | github.com/prometheus-community/pro-bing v0.4.0 // indirect 55 | github.com/safchain/ethtool v0.3.0 // indirect 56 | github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect 57 | github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect 58 | github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect 59 | github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect 60 | github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect 61 | github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect 62 | github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect 63 | github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 // indirect 64 | github.com/vishvananda/netns v0.0.4 // indirect 65 | github.com/x448/float16 v0.8.4 // indirect 66 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect 67 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect 68 | golang.org/x/crypto v0.37.0 // indirect 69 | golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect 70 | golang.org/x/mod v0.23.0 // indirect 71 | golang.org/x/net v0.38.0 // indirect 72 | golang.org/x/sync v0.13.0 // indirect 73 | golang.org/x/sys v0.32.0 // indirect 74 | golang.org/x/term v0.31.0 // indirect 75 | golang.org/x/text v0.24.0 // indirect 76 | golang.org/x/time v0.10.0 // indirect 77 | golang.org/x/tools v0.30.0 // indirect 78 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect 79 | golang.zx2c4.com/wireguard/windows v0.5.3 // indirect 80 | gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 // indirect 81 | ) 82 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q= 2 | 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM= 3 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 4 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 5 | filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc= 6 | filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA= 7 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= 8 | github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 9 | github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= 10 | github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= 11 | github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= 12 | github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= 13 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 14 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 15 | github.com/aws/aws-sdk-go-v2 v1.36.0 h1:b1wM5CcE65Ujwn565qcwgtOTT1aT4ADOHHgglKjG7fk= 16 | github.com/aws/aws-sdk-go-v2 v1.36.0/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= 17 | github.com/aws/aws-sdk-go-v2/config v1.29.5 h1:4lS2IB+wwkj5J43Tq/AwvnscBerBJtQQ6YS7puzCI1k= 18 | github.com/aws/aws-sdk-go-v2/config v1.29.5/go.mod h1:SNzldMlDVbN6nWxM7XsUiNXPSa1LWlqiXtvh/1PrJGg= 19 | github.com/aws/aws-sdk-go-v2/credentials v1.17.58 h1:/d7FUpAPU8Lf2KUdjniQvfNdlMID0Sd9pS23FJ3SS9Y= 20 | github.com/aws/aws-sdk-go-v2/credentials v1.17.58/go.mod h1:aVYW33Ow10CyMQGFgC0ptMRIqJWvJ4nxZb0sUiuQT/A= 21 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27 h1:7lOW8NUwE9UZekS1DYoiPdVAqZ6A+LheHWb+mHbNOq8= 22 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.27/go.mod h1:w1BASFIPOPUae7AgaH4SbjNbfdkxuggLyGfNFTn8ITY= 23 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31 h1:lWm9ucLSRFiI4dQQafLrEOmEDGry3Swrz0BIRdiHJqQ= 24 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.31/go.mod h1:Huu6GG0YTfbPphQkDSo4dEGmQRTKb9k9G7RdtyQWxuI= 25 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31 h1:ACxDklUKKXb48+eg5ROZXi1vDgfMyfIA/WyvqHcHI0o= 26 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.31/go.mod h1:yadnfsDwqXeVaohbGc/RaD287PuyRw2wugkh5ZL2J6k= 27 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= 28 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= 29 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= 30 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= 31 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12 h1:O+8vD2rGjfihBewr5bT+QUfYUHIxCVgG61LHoT59shM= 32 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.12/go.mod h1:usVdWJaosa66NMvmCrr08NcWDBRv4E6+YFG2pUdw1Lk= 33 | github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= 34 | github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= 35 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.14 h1:c5WJ3iHz7rLIgArznb3JCSQT3uUMiz9DLZhIX+1G8ok= 36 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.14/go.mod h1:+JJQTxB6N4niArC14YNtxcQtwEqzS3o9Z32n7q33Rfs= 37 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13 h1:f1L/JtUkVODD+k1+IiSJUUv8A++2qVr+Xvb3xWXETMU= 38 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.13/go.mod h1:tvqlFoja8/s0o+UruA1Nrezo/df0PzdunMDDurUfg6U= 39 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.13 h1:3LXNnmtH3TURctC23hnC0p/39Q5gre3FI7BNOiDcVWc= 40 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.13/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= 41 | github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= 42 | github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= 43 | github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= 44 | github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= 45 | github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= 46 | github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= 47 | github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0= 48 | github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= 49 | github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc= 50 | github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g= 51 | github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= 52 | github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 53 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 54 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 55 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 56 | github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk= 57 | github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ= 58 | github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q= 59 | github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= 60 | github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= 61 | github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= 62 | github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI= 63 | github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40= 64 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 65 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 66 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 67 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 68 | github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo= 69 | github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY= 70 | github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= 71 | github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= 72 | github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY= 73 | github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= 74 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 75 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 76 | github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo= 77 | github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g= 78 | github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg= 79 | github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU= 80 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 81 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 82 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 83 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 84 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 85 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 86 | github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I= 87 | github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= 88 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 89 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 90 | github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= 91 | github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= 92 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 93 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 94 | github.com/gorilla/csrf v1.7.3 h1:BHWt6FTLZAb2HtWT5KDBf6qgpZzvtbp9QWDRKZMXJC0= 95 | github.com/gorilla/csrf v1.7.3/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= 96 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 97 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 98 | github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= 99 | github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= 100 | github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk= 101 | github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U= 102 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= 103 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= 104 | github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g= 105 | github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= 106 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 107 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 108 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 109 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 110 | github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= 111 | github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= 112 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 113 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 114 | github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= 115 | github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= 116 | github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= 117 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 118 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 119 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 120 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 121 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 122 | github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= 123 | github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= 124 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg= 125 | github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= 126 | github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= 127 | github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= 128 | github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= 129 | github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= 130 | github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= 131 | github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= 132 | github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= 133 | github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= 134 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 135 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 136 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 137 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 138 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= 139 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 140 | github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= 141 | github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= 142 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 143 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 144 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 145 | github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4= 146 | github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4= 147 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 148 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 149 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 150 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 151 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 152 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 153 | github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0= 154 | github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= 155 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 156 | github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ= 157 | github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4= 158 | github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= 159 | github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= 160 | github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869 h1:SRL6irQkKGQKKLzvQP/ke/2ZuB7Py5+XuqtOgSj+iMM= 161 | github.com/tailscale/golang-x-crypto v0.0.0-20250404221719-a5573b049869/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ= 162 | github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= 163 | github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= 164 | github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= 165 | github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= 166 | github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU= 167 | github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= 168 | github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA= 169 | github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc= 170 | github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14= 171 | github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ= 172 | github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M= 173 | github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y= 174 | github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251 h1:h/41LFTrwMxB9Xvvug0kRdQCU5TlV1+pAMQw0ZtDE3U= 175 | github.com/tailscale/wireguard-go v0.0.0-20250304000100-91a0587fb251/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= 176 | github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek= 177 | github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg= 178 | github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= 179 | github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= 180 | github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg= 181 | github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE= 182 | github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= 183 | github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= 184 | github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 185 | github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= 186 | github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 187 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 188 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 189 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= 190 | go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= 191 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= 192 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= 193 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 194 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 195 | golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= 196 | golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= 197 | golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= 198 | golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 199 | golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= 200 | golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= 201 | golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= 202 | golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 203 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 204 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 205 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 206 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 207 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 208 | golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 209 | golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 210 | golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 211 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 212 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 213 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 214 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 215 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 216 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 217 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 218 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 219 | golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= 220 | golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 221 | golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= 222 | golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= 223 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= 224 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= 225 | golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= 226 | golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= 227 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 228 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 229 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 230 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 231 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 232 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 233 | gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k= 234 | gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM= 235 | honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= 236 | honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= 237 | howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= 238 | howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= 239 | software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= 240 | software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= 241 | tailscale.com v1.84.1 h1:xtuiYeAIUR+dRztPzzqUsjj+Fv/06vz28zoFaP1k/Os= 242 | tailscale.com v1.84.1/go.mod h1:6/S63NMAhmncYT/1zIPDJkvCuZwMw+JnUuOfSPNazpo= 243 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Erase your computer if you call this API. 2 | // 3 | // Based on the Tailscale tshello. 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "crypto/tls" 10 | "encoding/json" 11 | "errors" 12 | "flag" 13 | "fmt" 14 | "html" 15 | "io/ioutil" 16 | "log" 17 | "net/http" 18 | "net/url" 19 | "os" 20 | "regexp" 21 | "strings" 22 | "time" 23 | 24 | "tailscale.com/client/tailscale" 25 | "tailscale.com/client/tailscale/apitype" 26 | "tailscale.com/tsnet" 27 | ) 28 | 29 | var ( 30 | addr = flag.String("addr", ":80", "address to listen on") 31 | jwt string = "" 32 | ) 33 | 34 | type DeviceListResponse struct { 35 | Response []DeviceList `json:response` 36 | } 37 | 38 | type DeviceList struct { 39 | Devices []Device `json:devices` 40 | Rows int `json:rows` 41 | PageSize int `json:page_size` 42 | Page int `json:page` 43 | } 44 | 45 | type Device struct { 46 | DeviceUDID string `json:deviceudid` 47 | LocalHostName string `json:LocalHostname` 48 | } 49 | 50 | var tsclient *tailscale.LocalClient 51 | 52 | func main() { 53 | flag.Parse() 54 | 55 | log.Println("Logging in to Mosyle ...") 56 | err := mosyleLogin() 57 | if err != nil { 58 | log.Fatalf("failed to login to mosyle: %v", err) 59 | } 60 | 61 | go func() { 62 | DailyRefresh: 63 | for { 64 | time.Sleep(20 * time.Hour) 65 | 66 | err := mosyleLogin() 67 | if err == nil { 68 | continue DailyRefresh 69 | } 70 | 71 | log.Printf("failed to login to mosyle, trying again several more times: %v\n", err) 72 | for i := 0; i <= 60; i += 1 { 73 | time.Sleep(220 * time.Second) 74 | err := mosyleLogin() 75 | if err == nil { 76 | continue DailyRefresh 77 | } 78 | log.Printf("Failure #%d/60: %v\n", i, err) 79 | } 80 | log.Fatalf("No luck after a bunch of attempts") 81 | } 82 | }() 83 | 84 | log.Println("Verifying we can fetch machines from Mosyle...") 85 | m, err := enumerateMachines() 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | log.Println("Current machines:", m) 90 | 91 | s := &tsnet.Server{ 92 | AuthKey: os.Getenv("TS_AUTHKEY"), 93 | Ephemeral: true, 94 | Hostname: "bonk", 95 | } 96 | 97 | tsclient_, err := s.LocalClient() 98 | if err != nil { 99 | log.Fatal(err) 100 | } 101 | tsclient = tsclient_ 102 | 103 | defer s.Close() 104 | ln, err := s.Listen("tcp", *addr) 105 | if err != nil { 106 | log.Println(err) 107 | } 108 | defer ln.Close() 109 | 110 | if *addr == ":443" { 111 | ln = tls.NewListener(ln, &tls.Config{ 112 | GetCertificate: tsclient.GetCertificate, 113 | }) 114 | } 115 | 116 | http.HandleFunc("/erase/", withEraseContext(erase)) 117 | http.HandleFunc("/erase-self", withEraseContext(eraseSelf)) 118 | http.HandleFunc("/erase-all", withEraseContext(eraseAll)) 119 | http.HandleFunc("/", notFound) 120 | 121 | log.Fatal(http.Serve(ln, nil)) 122 | } 123 | 124 | type ctxKey struct{} 125 | type eraseContext struct { 126 | client *apitype.WhoIsResponse 127 | devices []Device 128 | } 129 | 130 | func withEraseContext(fn http.HandlerFunc) http.HandlerFunc { 131 | return func(w http.ResponseWriter, r *http.Request) { 132 | client, err := tsclient.WhoIs(r.Context(), r.RemoteAddr) 133 | if err != nil { 134 | log.Printf("Could not identify client: %v", err) 135 | http.Error(w, "Unauthorized", 401) 136 | return 137 | } 138 | 139 | if r.Method != "POST" { 140 | http.Error(w, "Method not allowed, only POSTs can erase", 405) 141 | return 142 | } 143 | 144 | devices, err := enumerateMachines() 145 | if err != nil { 146 | log.Fatal(err) 147 | } 148 | 149 | ctx := eraseContext{client, devices} 150 | fn(w, r.WithContext(context.WithValue(r.Context(), ctxKey{}, ctx))) 151 | } 152 | } 153 | 154 | func eraseSelf(w http.ResponseWriter, r *http.Request) { 155 | bonk(w, r, r.Context().Value(ctxKey{}).(eraseContext).client.Node.ComputedName) 156 | } 157 | 158 | var nameRegex = regexp.MustCompile("/erase/([^/]+)") 159 | 160 | func erase(w http.ResponseWriter, r *http.Request) { 161 | name := nameRegex.FindStringSubmatch(r.URL.Path)[1] 162 | bonk(w, r, name) 163 | } 164 | 165 | // This sends the erase requests one-by-one synchronously. It would be 166 | // nicer to submit them in parallel and return a job ID or something 167 | // which can later be queried for progress, but at the current scale (4 168 | // machines) I think waiting a few seconds per machine is still 169 | // acceptable. 170 | func eraseAll(w http.ResponseWriter, r *http.Request) { 171 | context := r.Context().Value(ctxKey{}).(eraseContext) 172 | anyFailed := false 173 | messages := make([]string, 0, len(context.devices)) 174 | for _, device := range context.devices { 175 | if err := sendErase(device); err != nil { 176 | anyFailed = true 177 | messages = append(messages, fmt.Sprintf("could not bonk %s: %s\n", device.LocalHostName, err)) 178 | } else { 179 | messages = append(messages, fmt.Sprintf("bonking %s!\n", device.LocalHostName)) 180 | } 181 | } 182 | if anyFailed { 183 | w.WriteHeader(500) 184 | } 185 | for _, msg := range messages { 186 | w.Write([]byte("IT'S A BONK PARTY!")) 187 | w.Write([]byte(msg)) 188 | } 189 | } 190 | 191 | func bonk(w http.ResponseWriter, r *http.Request, name string) { 192 | 193 | context := r.Context().Value(ctxKey{}).(eraseContext) 194 | 195 | device, err := getDeviceFromName(context.devices, name) 196 | if err != nil { 197 | log.Fatal(err) 198 | } 199 | if device == nil { 200 | // REVIEW: is this actually a thing that mosyle does? 201 | device, err = getDeviceFromName(context.devices, strings.TrimSuffix(name, "-1")) 202 | if err != nil { 203 | log.Fatal(err) 204 | } 205 | } 206 | 207 | if device == nil { 208 | fmt.Fprintf(w, "I don't know who %s is, %s!\n", 209 | html.EscapeString(name), 210 | html.EscapeString(firstLabel(context.client.Node.ComputedName)), 211 | ) 212 | log.Printf("no known device by name %s", name) 213 | 214 | } else { 215 | if err = sendErase(*device); err != nil { 216 | log.Printf("Failed to erase %s:", name, err) 217 | } 218 | 219 | fmt.Fprintf(w, ` 220 | ⠀⠀⠀⠀⠀⠀⢀⣁⣤⣶⣶⡒⠒⠲⠾⣭⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 221 | ⠀⠀⠀⠀⠀⠀⣿⡀⣸⠟⠛⠃⠀⣀⣀⠈⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⠀⠀⠀⠀⠀⠀⠀ 222 | ⠀⠀⠀⡠⠂⢠⠏⠀⠉⠀⠀⠀⠰⣿⠟⠀⠙⢧⡀⠀⠀⠀⠀⠀⠀⢀⠀⠀⢀⢀⡀⣼⣧⡾⠃⠀⠀⠀⠀⠀ 223 | ⢀⠔⠀⣠⠔⠁⠀⠀⠀⠀⠀⠀⠀⠰⢄⡠⣶⢾⣽⡆⠀⠀⠀⠀⠄⢡⡀⢰⣾⣿⡀⠈⠵⠟⠛⠀⠀⠀⠀⠀ 224 | ⠀⣠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡟⠋⠉⠀⠀⠀⠀⣰⢦⣼⡷⣼⡏⢯⢉⣡⠖⠋⣩⡇⠀⠀⠀⠀ 225 | ⣰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⢺⣿⡄⣿⡄⣿⢿⠈⢁⡴⠋⠀⢀⣴⣋⡀⠀⠀⠀⠀ 226 | ⡇⠀⠀⢰⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠒⢛⣡⣸⡏⢹⡟⠻⠏⢀⡴⠋⠀⣠⣖⠻⠿⠿⣤⡀⠀⠀⠀ 227 | ⡇⠀⠀⠈⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡟⠀⠛⢡⡞⠻⠟⠁⢀⡴⠋⢀⣤⣞⣛⣻⡆⠀⠀⠉⢇⠀⠀⠀ 228 | ⣇⠀⠀⠀⠈⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠇⠀⠠⠏⠀⠀⢀⠴⠋⣀⠴⣿⠛⠛⠁⠈⠁⠀⠀⠀⠈⢧⠀⡄ 229 | ⠸⡄⠀⠀⠀⠀⡇⠀⠀⢰⠃⠀⠈⣇⠀⠸⣦⡀⠀⠀⢀⡔⠁⣠⠞⠁⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡀⠀ 230 | ⠀⠙⣄⠀⠀⠀⣿⠀⠀⢸⠒⠒⠒⠻⡀⠀⣷⠬⣉⡶⠋⣠⠞⠁⠀⠀⠀⡇⠀⠀⠀⡀⠀⢠⠀⠀⠀⠘⡇⠀ 231 | ⠀⠀⠈⠑⠦⠤⣽⣄⠀⢸⠤⠤⠤⠤⢷⡀⠸⣷⠋⣠⢾⡁⠀⠀⠀⠀⠀⡇⢠⠇⠀⢹⠀⢸⠃⠀⠀⣸⠃⠀ 232 | ⠀⠀⠀⠀⠀⠀⠀⢹⠀⢸⠀⠀⠀⠀⠀⢈⠦⣀⣙⣻⡞⠃⠀⠀⠀⢀⡼⢡⠧⠤⠤⢸⠀⣾⠤⠤⠚⠁⠀⠀ 233 | ⠀⠀⠀⠀⠀⠀⠀⢸⡀⠸⡄⠀⠀⠀⠀⣧⠴⠃⠉⠉⠁⠀⠀⠰⣾⡭⠔⠁⠀⠀⠀⡜⠀⡇⠀⠀⠀⠀⠀⠀ 234 | ⠀⠀⠀⠀⠀⠀⠀⠀⠳⢤⣼⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠄⠀⠀⠀⠀⠀⢰⣥⣴⠃⠀⠀⠀⠀⠀⠀ 235 | ⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⠤⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 236 | `+"%s is getting bonked! See you soon!\n", 237 | html.EscapeString(name), 238 | ) 239 | } 240 | } 241 | 242 | func notFound(w http.ResponseWriter, r *http.Request) { 243 | http.Error(w, "Not found. Try /erase-self or /erase/", 404) 244 | return 245 | } 246 | 247 | func firstLabel(s string) string { 248 | if i := strings.Index(s, "."); i != -1 { 249 | return s[:i] 250 | } 251 | return s 252 | } 253 | 254 | func getDeviceFromName(devices []Device, name string) (*Device, error) { 255 | matching_udids := []Device{} 256 | for _, dev := range devices { 257 | if dev.LocalHostName == name { 258 | matching_udids = append(matching_udids, dev) 259 | } 260 | } 261 | 262 | if len(matching_udids) == 0 { 263 | return nil, nil 264 | } 265 | 266 | if len(matching_udids) > 1 { 267 | return nil, errors.New("Multiple machines with matching names") 268 | } 269 | 270 | return &matching_udids[0], nil 271 | } 272 | 273 | func mosyleLogin() error { 274 | data, err := json.Marshal(map[string]string{ 275 | "email": os.Getenv("MOSYLE_EMAIL"), 276 | "password": os.Getenv("MOSYLE_PASSWORD"), 277 | }) 278 | if err != nil { 279 | return err 280 | } 281 | 282 | req, err := http.NewRequest(http.MethodPost, "https://businessapi.mosyle.com/v1/login", bytes.NewBuffer(data)) 283 | if err != nil { 284 | return err 285 | } 286 | 287 | req.Header.Set("Content-Type", "application/json") 288 | // !!!: We don't use `Set` here because Mosyle is sensitive to the case of the 289 | // `accesstoken` header -- http will canonicalize `accesstoken` to 290 | // `Accesstoken`, and Mosyle won't accept that. 291 | // https://stackoverflow.com/a/26352765 292 | req.Header["accessToken"] = []string{os.Getenv("MOSYLE_ACCESS_TOKEN")} 293 | 294 | client := http.Client{ 295 | Timeout: 30 * time.Second, 296 | } 297 | 298 | res, err := client.Do(req) 299 | if err != nil { 300 | return err 301 | } 302 | 303 | if res.StatusCode == 200 { 304 | if len(res.Header["Authorization"]) == 1 { 305 | jwt = res.Header["Authorization"][0] 306 | fmt.Println("Refreshed the Mosyle JWT") 307 | return nil 308 | } else { 309 | return fmt.Errorf("no jwt with the body") 310 | } 311 | } else { 312 | body, _ := ioutil.ReadAll(res.Body) 313 | fmt.Printf("logging in failed\n response: %v\nbody: %v\n", res, string(body)) 314 | return fmt.Errorf("non-200 response while logging in") 315 | } 316 | } 317 | 318 | func enumerateMachines() ([]Device, error) { 319 | data := url.Values{ 320 | "operation": {"list"}, 321 | "options[os]": {"mac"}, 322 | } 323 | 324 | req, err := http.NewRequest(http.MethodPost, "https://businessapi.mosyle.com/v1/devices", strings.NewReader(data.Encode())) 325 | if err != nil { 326 | return nil, err 327 | } 328 | 329 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 330 | req.Header.Set("Authorization", jwt) 331 | // !!!: We don't use `Set` here because Mosyle is sensitive to the case of the 332 | // `accesstoken` header -- http will canonicalize `accesstoken` to 333 | // `Accesstoken`, and Mosyle won't accept that. 334 | // https://stackoverflow.com/a/26352765 335 | req.Header["accesstoken"] = []string{os.Getenv("MOSYLE_ACCESS_TOKEN")} 336 | 337 | client := http.Client{ 338 | Timeout: 5 * time.Second, 339 | } 340 | 341 | res, err := client.Do(req) 342 | if err != nil { 343 | return nil, err 344 | } 345 | 346 | fmt.Printf("enumerating machines: %v\n", res) 347 | 348 | body, _ := ioutil.ReadAll(res.Body) 349 | 350 | obj := &DeviceListResponse{} 351 | if err := json.Unmarshal(body, &obj); err != nil { 352 | fmt.Println("error unmarshaling body:", string(body)) 353 | return nil, err 354 | } 355 | 356 | if obj.Response == nil { 357 | return nil, fmt.Errorf("nil Response in Device list") 358 | } 359 | 360 | if len(obj.Response) != 1 { 361 | return nil, fmt.Errorf("Too many Responses in Device list") 362 | } 363 | 364 | if obj.Response[0].Devices == nil { 365 | return nil, fmt.Errorf("Response's Devices list is nil") 366 | } 367 | 368 | devices_response := obj.Response[0] 369 | if devices_response.PageSize == devices_response.Rows { 370 | fmt.Println("Number of devices returend matches the page size! Could be losing devices, since we don't paginate.") 371 | } 372 | 373 | if devices_response.Devices == nil { 374 | return nil, fmt.Errorf("Response's Devices list is nil") 375 | } 376 | 377 | return devices_response.Devices, nil 378 | } 379 | 380 | func sendErase(device Device) error { 381 | data := url.Values{ 382 | "operation": {"wipe_devices"}, 383 | "devices[]": {device.DeviceUDID}, 384 | "options[pin_code]": {"123456"}, 385 | "options[ObliterationBehavior]": {"DoNotObliterate"}, 386 | } 387 | 388 | req, err := http.NewRequest(http.MethodPost, "https://businessapi.mosyle.com/v1/devices", strings.NewReader(data.Encode())) 389 | if err != nil { 390 | return err 391 | } 392 | 393 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 394 | req.Header.Set("Authorization", jwt) 395 | req.Header.Set("accesstoken", os.Getenv("MOSYLE_ACCESS_TOKEN")) 396 | 397 | client := http.Client{ 398 | Timeout: 5 * time.Second, 399 | } 400 | 401 | res, err := client.Do(req) 402 | if err != nil { 403 | return err 404 | } 405 | 406 | log.Printf("sending wipe: %v\n", res) 407 | body, _ := ioutil.ReadAll(res.Body) 408 | log.Printf("reply: %v\n", body) 409 | 410 | return nil 411 | } 412 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in 4 | fetchTarball { 5 | url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; 7 | } 8 | ) 9 | { src = ./.; } 10 | ).shellNix 11 | --------------------------------------------------------------------------------