From 62755311874a257d69036c87ff69a21c2b88769e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Sch=C3=A4fer?= Date: Mon, 20 Oct 2025 22:11:42 +0200 Subject: [PATCH 1/5] feat(actions): Sync Images from Docker to GHCR --- .github/mirror.yaml | 132 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 .github/mirror.yaml diff --git a/.github/mirror.yaml b/.github/mirror.yaml new file mode 100644 index 0000000..1b6dc91 --- /dev/null +++ b/.github/mirror.yaml @@ -0,0 +1,132 @@ +name: Mirror & Sign (Docker Hub to GHCR) + +on: + workflow_dispatch: {} + +permissions: + contents: read + packages: write + id-token: write # for keyless OIDC + +env: + SOURCE_IMAGE: docker.io/fosrl/olm + DEST_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }} + +jobs: + mirror-and-dual-sign: + runs-on: amd64-runner + steps: + - name: Install skopeo + jq + run: | + sudo apt-get update -y + sudo apt-get install -y skopeo jq + skopeo --version + + - name: Install cosign + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + + - name: Input check + run: | + test -n "${SOURCE_IMAGE}" || (echo "SOURCE_IMAGE is empty" && exit 1) + echo "Source : ${SOURCE_IMAGE}" + echo "Target : ${DEST_IMAGE}" + + # Auth for skopeo (containers-auth) + - name: Skopeo login to GHCR + run: | + skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}" + + # Auth for cosign (docker-config) + - name: Docker login to GHCR (for cosign) + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: List source tags + run: | + set -euo pipefail + skopeo list-tags --retry-times 3 docker://"${SOURCE_IMAGE}" \ + | jq -r '.Tags[]' | sort -u > src-tags.txt + echo "Found source tags: $(wc -l < src-tags.txt)" + head -n 20 src-tags.txt || true + + - name: List destination tags (skip existing) + run: | + set -euo pipefail + if skopeo list-tags --retry-times 3 docker://"${DEST_IMAGE}" >/tmp/dst.json 2>/dev/null; then + jq -r '.Tags[]' /tmp/dst.json | sort -u > dst-tags.txt + else + : > dst-tags.txt + fi + echo "Existing destination tags: $(wc -l < dst-tags.txt)" + + - name: Mirror, dual-sign, and verify + env: + # keyless + COSIGN_YES: "true" + # key-based + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + # verify + COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }} + run: | + set -euo pipefail + copied=0; skipped=0; v_ok=0; errs=0 + + issuer="https://token.actions.githubusercontent.com" + id_regex="^https://github.com/${{ github.repository }}/.+" + + while read -r tag; do + [ -z "$tag" ] && continue + + if grep -Fxq "$tag" dst-tags.txt; then + echo "::notice ::Skip (exists) ${DEST_IMAGE}:${tag}" + skipped=$((skipped+1)) + continue + fi + + echo "==> Copy ${SOURCE_IMAGE}:${tag} → ${DEST_IMAGE}:${tag}" + if ! skopeo copy --all --retry-times 3 \ + docker://"${SOURCE_IMAGE}:${tag}" docker://"${DEST_IMAGE}:${tag}"; then + echo "::warning title=Copy failed::${SOURCE_IMAGE}:${tag}" + errs=$((errs+1)); continue + fi + copied=$((copied+1)) + + digest="$(skopeo inspect --retry-times 3 docker://"${DEST_IMAGE}:${tag}" | jq -r '.Digest')" + ref="${DEST_IMAGE}@${digest}" + + echo "==> cosign sign (keyless) --recursive ${ref}" + if ! cosign sign --recursive "${ref}"; then + echo "::warning title=Keyless sign failed::${ref}" + errs=$((errs+1)) + fi + + echo "==> cosign sign (key) --recursive ${ref}" + if ! cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${ref}"; then + echo "::warning title=Key sign failed::${ref}" + errs=$((errs+1)) + fi + + echo "==> cosign verify (public key) ${ref}" + if ! cosign verify --key env://COSIGN_PUBLIC_KEY "${ref}" -o text; then + echo "::warning title=Verify(pubkey) failed::${ref}" + errs=$((errs+1)) + fi + + echo "==> cosign verify (keyless policy) ${ref}" + if ! cosign verify \ + --certificate-oidc-issuer "${issuer}" \ + --certificate-identity-regexp "${id_regex}" \ + "${ref}" -o text; then + echo "::warning title=Verify(keyless) failed::${ref}" + errs=$((errs+1)) + else + v_ok=$((v_ok+1)) + fi + done < src-tags.txt + + echo "---- Summary ----" + echo "Copied : $copied" + echo "Skipped : $skipped" + echo "Verified OK : $v_ok" + echo "Errors : $errs" From c94dc6af69a4234597f45ca3824f813ce2e71ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Sch=C3=A4fer?= Date: Mon, 20 Oct 2025 22:15:15 +0200 Subject: [PATCH 2/5] fix(actions): Moved mirror action to workflows --- .github/{ => workflows}/mirror.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/mirror.yaml (100%) diff --git a/.github/mirror.yaml b/.github/workflows/mirror.yaml similarity index 100% rename from .github/mirror.yaml rename to .github/workflows/mirror.yaml From ef011f29f7b8b7d3a6778402917302f35ce50c9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 20:36:13 +0000 Subject: [PATCH 3/5] Bump the prod-minor-updates group with 2 updates Bumps the prod-minor-updates group with 2 updates: [golang.org/x/crypto](https://github.com/golang/crypto) and [golang.org/x/sys](https://github.com/golang/sys). Updates `golang.org/x/crypto` from 0.42.0 to 0.43.0 - [Commits](https://github.com/golang/crypto/compare/v0.42.0...v0.43.0) Updates `golang.org/x/sys` from 0.36.0 to 0.37.0 - [Commits](https://github.com/golang/sys/compare/v0.36.0...v0.37.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.43.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: prod-minor-updates - dependency-name: golang.org/x/sys dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: prod-minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 35 +++---------------------- go.sum | 82 +++++----------------------------------------------------- 2 files changed, 9 insertions(+), 108 deletions(-) diff --git a/go.mod b/go.mod index dc7aede..5107cd6 100644 --- a/go.mod +++ b/go.mod @@ -5,47 +5,18 @@ go 1.25 require ( github.com/fosrl/newt v0.0.0-20250929233849-71c5bf7e65f7 github.com/vishvananda/netlink v1.3.1 - golang.org/x/crypto v0.42.0 + golang.org/x/crypto v0.43.0 golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 - golang.org/x/sys v0.36.0 + golang.org/x/sys v0.37.0 golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 ) require ( - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v28.4.0+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gopacket v1.1.19 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/josharian/native v1.1.0 // indirect - github.com/mdlayher/genetlink v1.3.2 // indirect - github.com/mdlayher/netlink v1.7.2 // indirect - github.com/mdlayher/socket v0.5.1 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect - golang.org/x/net v0.44.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/time v0.12.0 // indirect + golang.org/x/net v0.45.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56 // indirect software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index 3202dfc..17ce82d 100644 --- a/go.sum +++ b/go.sum @@ -1,103 +1,33 @@ -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= -github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= -github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fosrl/newt v0.0.0-20250730062419-3ccd755d557a h1:bUGN4piHlcqgfdRLrwqiLZZxgcitzBzNDQS1+CHSmJI= -github.com/fosrl/newt v0.0.0-20250730062419-3ccd755d557a/go.mod h1:PbiPYp1hbL07awrmbqTSTz7lTenieTHN6cIkUVCGD3I= github.com/fosrl/newt v0.0.0-20250929233849-71c5bf7e65f7 h1:6bSU8Efyhx1SR53iSw1Wjk5V8vDfizGAudq/GlE9b+o= github.com/fosrl/newt v0.0.0-20250929233849-71c5bf7e65f7/go.mod h1:Ac0k2FmAMC+hu21rAK+p7EnnEGrqKO/QZuGTVHA/XDM= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= -github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56 h1:H+qymc2ndLKNFR5TcaPmsHGiJnhJMqeofBYSRq4oG3c= gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56/go.mod h1:i8iCZyAdwRnLZYaIi2NUL1gfNtAveqxkKAe0JfAv9Bs= software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= From 555c9dc9f41abea95d0c43dea7728e6588e8eedc Mon Sep 17 00:00:00 2001 From: gk1 Date: Fri, 24 Oct 2025 17:04:48 -0700 Subject: [PATCH 4/5] Updated the olm client to process config vars from cli,env,file in order of precedence and persist them to file --- config.go | 492 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 185 +++++--------------- 2 files changed, 535 insertions(+), 142 deletions(-) create mode 100644 config.go diff --git a/config.go b/config.go new file mode 100644 index 0000000..d45328f --- /dev/null +++ b/config.go @@ -0,0 +1,492 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + "time" +) + +// OlmConfig holds all configuration options for the Olm client +type OlmConfig struct { + // Connection settings + Endpoint string `json:"endpoint"` + ID string `json:"id"` + Secret string `json:"secret"` + + // Network settings + MTU string `json:"mtu"` + DNS string `json:"dns"` + InterfaceName string `json:"interface"` + + // Logging + LogLevel string `json:"logLevel"` + + // HTTP server + EnableHTTP bool `json:"enableHttp"` + HTTPAddr string `json:"httpAddr"` + + // Ping settings + PingInterval string `json:"pingInterval"` + PingTimeout string `json:"pingTimeout"` + + // Advanced + Holepunch bool `json:"holepunch"` + TlsClientCert string `json:"tlsClientCert"` + + // Parsed values (not in JSON) + PingIntervalDuration time.Duration `json:"-"` + PingTimeoutDuration time.Duration `json:"-"` + + // Source tracking (not in JSON) + sources map[string]string `json:"-"` +} + +// ConfigSource tracks where each config value came from +type ConfigSource string + +const ( + SourceDefault ConfigSource = "default" + SourceFile ConfigSource = "file" + SourceEnv ConfigSource = "environment" + SourceCLI ConfigSource = "cli" +) + +// DefaultConfig returns a config with default values +func DefaultConfig() *OlmConfig { + config := &OlmConfig{ + MTU: "1280", + DNS: "8.8.8.8", + LogLevel: "INFO", + InterfaceName: "olm", + EnableHTTP: false, + HTTPAddr: ":9452", + PingInterval: "3s", + PingTimeout: "5s", + Holepunch: false, + sources: make(map[string]string), + } + + // Track default sources + config.sources["mtu"] = string(SourceDefault) + config.sources["dns"] = string(SourceDefault) + config.sources["logLevel"] = string(SourceDefault) + config.sources["interface"] = string(SourceDefault) + config.sources["enableHttp"] = string(SourceDefault) + config.sources["httpAddr"] = string(SourceDefault) + config.sources["pingInterval"] = string(SourceDefault) + config.sources["pingTimeout"] = string(SourceDefault) + config.sources["holepunch"] = string(SourceDefault) + + return config +} + +// getOlmConfigPath returns the path to the olm config file +func getOlmConfigPath() string { + configFile := os.Getenv("CONFIG_FILE") + if configFile != "" { + return configFile + } + + var configDir string + switch runtime.GOOS { + case "darwin": + configDir = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "olm-client") + case "windows": + configDir = filepath.Join(os.Getenv("PROGRAMDATA"), "olm", "olm-client") + default: // linux and others + configDir = filepath.Join(os.Getenv("HOME"), ".config", "olm-client") + } + + if err := os.MkdirAll(configDir, 0755); err != nil { + fmt.Printf("Warning: Failed to create config directory: %v\n", err) + } + + return filepath.Join(configDir, "config.json") +} + +// LoadConfig loads configuration from file, env vars, and CLI args +// Priority: CLI args > Env vars > Config file > Defaults +// Returns: (config, showVersion, showConfig, error) +func LoadConfig(args []string) (*OlmConfig, bool, bool, error) { + // Start with defaults + config := DefaultConfig() + + // Load from config file (if exists) + fileConfig, err := loadConfigFromFile() + if err != nil { + return nil, false, false, fmt.Errorf("failed to load config file: %w", err) + } + if fileConfig != nil { + mergeConfigs(config, fileConfig) + } + + // Override with environment variables + loadConfigFromEnv(config) + + // Override with CLI arguments + showVersion, showConfig, err := loadConfigFromCLI(config, args) + if err != nil { + return nil, false, false, err + } + + // Parse duration strings + if err := config.parseDurations(); err != nil { + return nil, false, false, err + } + + return config, showVersion, showConfig, nil +} + +// loadConfigFromFile loads configuration from the JSON config file +func loadConfigFromFile() (*OlmConfig, error) { + configPath := getOlmConfigPath() + data, err := os.ReadFile(configPath) + if err != nil { + if os.IsNotExist(err) { + return nil, nil // File doesn't exist, not an error + } + return nil, err + } + + var config OlmConfig + if err := json.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("failed to parse config file: %w", err) + } + + return &config, nil +} + +// loadConfigFromEnv loads configuration from environment variables +func loadConfigFromEnv(config *OlmConfig) { + if val := os.Getenv("PANGOLIN_ENDPOINT"); val != "" { + config.Endpoint = val + config.sources["endpoint"] = string(SourceEnv) + } + if val := os.Getenv("OLM_ID"); val != "" { + config.ID = val + config.sources["id"] = string(SourceEnv) + } + if val := os.Getenv("OLM_SECRET"); val != "" { + config.Secret = val + config.sources["secret"] = string(SourceEnv) + } + if val := os.Getenv("MTU"); val != "" { + config.MTU = val + config.sources["mtu"] = string(SourceEnv) + } + if val := os.Getenv("DNS"); val != "" { + config.DNS = val + config.sources["dns"] = string(SourceEnv) + } + if val := os.Getenv("LOG_LEVEL"); val != "" { + config.LogLevel = val + config.sources["logLevel"] = string(SourceEnv) + } + if val := os.Getenv("INTERFACE"); val != "" { + config.InterfaceName = val + config.sources["interface"] = string(SourceEnv) + } + if val := os.Getenv("HTTP_ADDR"); val != "" { + config.HTTPAddr = val + config.sources["httpAddr"] = string(SourceEnv) + } + if val := os.Getenv("PING_INTERVAL"); val != "" { + config.PingInterval = val + config.sources["pingInterval"] = string(SourceEnv) + } + if val := os.Getenv("PING_TIMEOUT"); val != "" { + config.PingTimeout = val + config.sources["pingTimeout"] = string(SourceEnv) + } + if val := os.Getenv("ENABLE_HTTP"); val == "true" { + config.EnableHTTP = true + config.sources["enableHttp"] = string(SourceEnv) + } + if val := os.Getenv("HOLEPUNCH"); val == "true" { + config.Holepunch = true + config.sources["holepunch"] = string(SourceEnv) + } +} + +// loadConfigFromCLI loads configuration from command-line arguments +func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) { + serviceFlags := flag.NewFlagSet("service", flag.ContinueOnError) + + // Store original values to detect changes + origValues := map[string]interface{}{ + "endpoint": config.Endpoint, + "id": config.ID, + "secret": config.Secret, + "mtu": config.MTU, + "dns": config.DNS, + "logLevel": config.LogLevel, + "interface": config.InterfaceName, + "httpAddr": config.HTTPAddr, + "pingInterval": config.PingInterval, + "pingTimeout": config.PingTimeout, + "enableHttp": config.EnableHTTP, + "holepunch": config.Holepunch, + } + + // Define flags + serviceFlags.StringVar(&config.Endpoint, "endpoint", config.Endpoint, "Endpoint of your Pangolin server") + serviceFlags.StringVar(&config.ID, "id", config.ID, "Olm ID") + serviceFlags.StringVar(&config.Secret, "secret", config.Secret, "Olm secret") + serviceFlags.StringVar(&config.MTU, "mtu", config.MTU, "MTU to use") + serviceFlags.StringVar(&config.DNS, "dns", config.DNS, "DNS server to use") + serviceFlags.StringVar(&config.LogLevel, "log-level", config.LogLevel, "Log level (DEBUG, INFO, WARN, ERROR, FATAL)") + serviceFlags.StringVar(&config.InterfaceName, "interface", config.InterfaceName, "Name of the WireGuard interface") + serviceFlags.StringVar(&config.HTTPAddr, "http-addr", config.HTTPAddr, "HTTP server address (e.g., ':9452')") + serviceFlags.StringVar(&config.PingInterval, "ping-interval", config.PingInterval, "Interval for pinging the server") + serviceFlags.StringVar(&config.PingTimeout, "ping-timeout", config.PingTimeout, "Timeout for each ping") + serviceFlags.BoolVar(&config.EnableHTTP, "enable-http", config.EnableHTTP, "Enable HTTP server for receiving connection requests") + serviceFlags.BoolVar(&config.Holepunch, "holepunch", config.Holepunch, "Enable hole punching") + + version := serviceFlags.Bool("version", false, "Print the version") + showConfig := serviceFlags.Bool("show-config", false, "Show configuration sources and exit") + + // Parse the arguments + if err := serviceFlags.Parse(args); err != nil { + return false, false, err + } + + // Track which values were changed by CLI args + if config.Endpoint != origValues["endpoint"].(string) { + config.sources["endpoint"] = string(SourceCLI) + } + if config.ID != origValues["id"].(string) { + config.sources["id"] = string(SourceCLI) + } + if config.Secret != origValues["secret"].(string) { + config.sources["secret"] = string(SourceCLI) + } + if config.MTU != origValues["mtu"].(string) { + config.sources["mtu"] = string(SourceCLI) + } + if config.DNS != origValues["dns"].(string) { + config.sources["dns"] = string(SourceCLI) + } + if config.LogLevel != origValues["logLevel"].(string) { + config.sources["logLevel"] = string(SourceCLI) + } + if config.InterfaceName != origValues["interface"].(string) { + config.sources["interface"] = string(SourceCLI) + } + if config.HTTPAddr != origValues["httpAddr"].(string) { + config.sources["httpAddr"] = string(SourceCLI) + } + if config.PingInterval != origValues["pingInterval"].(string) { + config.sources["pingInterval"] = string(SourceCLI) + } + if config.PingTimeout != origValues["pingTimeout"].(string) { + config.sources["pingTimeout"] = string(SourceCLI) + } + if config.EnableHTTP != origValues["enableHttp"].(bool) { + config.sources["enableHttp"] = string(SourceCLI) + } + if config.Holepunch != origValues["holepunch"].(bool) { + config.sources["holepunch"] = string(SourceCLI) + } + + return *version, *showConfig, nil +} + +// parseDurations parses the duration strings into time.Duration +func (c *OlmConfig) parseDurations() error { + var err error + + // Parse ping interval + if c.PingInterval != "" { + c.PingIntervalDuration, err = time.ParseDuration(c.PingInterval) + if err != nil { + fmt.Printf("Invalid PING_INTERVAL value: %s, using default 3 seconds\n", c.PingInterval) + c.PingIntervalDuration = 3 * time.Second + c.PingInterval = "3s" + } + } else { + c.PingIntervalDuration = 3 * time.Second + c.PingInterval = "3s" + } + + // Parse ping timeout + if c.PingTimeout != "" { + c.PingTimeoutDuration, err = time.ParseDuration(c.PingTimeout) + if err != nil { + fmt.Printf("Invalid PING_TIMEOUT value: %s, using default 5 seconds\n", c.PingTimeout) + c.PingTimeoutDuration = 5 * time.Second + c.PingTimeout = "5s" + } + } else { + c.PingTimeoutDuration = 5 * time.Second + c.PingTimeout = "5s" + } + + return nil +} + +// mergeConfigs merges source config into destination (only non-empty values) +// Also tracks that these values came from a file +func mergeConfigs(dest, src *OlmConfig) { + if src.Endpoint != "" { + dest.Endpoint = src.Endpoint + dest.sources["endpoint"] = string(SourceFile) + } + if src.ID != "" { + dest.ID = src.ID + dest.sources["id"] = string(SourceFile) + } + if src.Secret != "" { + dest.Secret = src.Secret + dest.sources["secret"] = string(SourceFile) + } + if src.MTU != "" && src.MTU != "1280" { + dest.MTU = src.MTU + dest.sources["mtu"] = string(SourceFile) + } + if src.DNS != "" && src.DNS != "8.8.8.8" { + dest.DNS = src.DNS + dest.sources["dns"] = string(SourceFile) + } + if src.LogLevel != "" && src.LogLevel != "INFO" { + dest.LogLevel = src.LogLevel + dest.sources["logLevel"] = string(SourceFile) + } + if src.InterfaceName != "" && src.InterfaceName != "olm" { + dest.InterfaceName = src.InterfaceName + dest.sources["interface"] = string(SourceFile) + } + if src.HTTPAddr != "" && src.HTTPAddr != ":9452" { + dest.HTTPAddr = src.HTTPAddr + dest.sources["httpAddr"] = string(SourceFile) + } + if src.PingInterval != "" && src.PingInterval != "3s" { + dest.PingInterval = src.PingInterval + dest.sources["pingInterval"] = string(SourceFile) + } + if src.PingTimeout != "" && src.PingTimeout != "5s" { + dest.PingTimeout = src.PingTimeout + dest.sources["pingTimeout"] = string(SourceFile) + } + if src.TlsClientCert != "" { + dest.TlsClientCert = src.TlsClientCert + dest.sources["tlsClientCert"] = string(SourceFile) + } + // For booleans, we always take the source value if explicitly set + if src.EnableHTTP { + dest.EnableHTTP = src.EnableHTTP + dest.sources["enableHttp"] = string(SourceFile) + } + if src.Holepunch { + dest.Holepunch = src.Holepunch + dest.sources["holepunch"] = string(SourceFile) + } +} + +// SaveConfig saves the current configuration to the config file +func SaveConfig(config *OlmConfig) error { + configPath := getOlmConfigPath() + data, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + return os.WriteFile(configPath, data, 0644) +} + +// UpdateFromWebsocket updates config with values received from websocket client +func (c *OlmConfig) UpdateFromWebsocket(id, secret, endpoint string) { + if id != "" { + c.ID = id + } + if secret != "" { + c.Secret = secret + } + if endpoint != "" { + c.Endpoint = endpoint + } +} + +// ShowConfig prints the configuration and the source of each value +func (c *OlmConfig) ShowConfig() { + configPath := getOlmConfigPath() + + fmt.Println("\n=== Olm Configuration ===\n") + fmt.Printf("Config File: %s\n", configPath) + + // Check if config file exists + if _, err := os.Stat(configPath); err == nil { + fmt.Printf("Config File Status: ✓ exists\n") + } else { + fmt.Printf("Config File Status: ✗ not found\n") + } + + fmt.Println("\n--- Configuration Values ---") + fmt.Println("(Format: Setting = Value [source])\n") + + // Helper to get source or default + getSource := func(key string) string { + if source, ok := c.sources[key]; ok { + return source + } + return string(SourceDefault) + } + + // Helper to format value (mask secrets) + formatValue := func(key, value string) string { + if key == "secret" && value != "" { + if len(value) > 8 { + return value[:4] + "****" + value[len(value)-4:] + } + return "****" + } + if value == "" { + return "(not set)" + } + return value + } + + // Connection settings + fmt.Println("Connection:") + fmt.Printf(" endpoint = %s [%s]\n", formatValue("endpoint", c.Endpoint), getSource("endpoint")) + fmt.Printf(" id = %s [%s]\n", formatValue("id", c.ID), getSource("id")) + fmt.Printf(" secret = %s [%s]\n", formatValue("secret", c.Secret), getSource("secret")) + + // Network settings + fmt.Println("\nNetwork:") + fmt.Printf(" mtu = %s [%s]\n", c.MTU, getSource("mtu")) + fmt.Printf(" dns = %s [%s]\n", c.DNS, getSource("dns")) + fmt.Printf(" interface = %s [%s]\n", c.InterfaceName, getSource("interface")) + + // Logging + fmt.Println("\nLogging:") + fmt.Printf(" log-level = %s [%s]\n", c.LogLevel, getSource("logLevel")) + + // HTTP server + fmt.Println("\nHTTP Server:") + fmt.Printf(" enable-http = %v [%s]\n", c.EnableHTTP, getSource("enableHttp")) + fmt.Printf(" http-addr = %s [%s]\n", c.HTTPAddr, getSource("httpAddr")) + + // Timing + fmt.Println("\nTiming:") + fmt.Printf(" ping-interval = %s [%s]\n", c.PingInterval, getSource("pingInterval")) + fmt.Printf(" ping-timeout = %s [%s]\n", c.PingTimeout, getSource("pingTimeout")) + + // Advanced + fmt.Println("\nAdvanced:") + fmt.Printf(" holepunch = %v [%s]\n", c.Holepunch, getSource("holepunch")) + if c.TlsClientCert != "" { + fmt.Printf(" tls-cert = %s [%s]\n", c.TlsClientCert, getSource("tlsClientCert")) + } + + // Source legend + fmt.Println("\n--- Source Legend ---") + fmt.Println(" default = Built-in default value") + fmt.Println(" file = Loaded from config file") + fmt.Println(" environment = Set via environment variable") + fmt.Println(" cli = Provided as command-line argument") + fmt.Println("\nPriority: cli > environment > file > default") + fmt.Println() +} diff --git a/main.go b/main.go index 0401fba..05a23f7 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "context" "encoding/json" - "flag" "fmt" "net" "os" @@ -19,7 +18,6 @@ import ( "github.com/fosrl/newt/websocket" "github.com/fosrl/olm/httpserver" "github.com/fosrl/olm/peermonitor" - "github.com/fosrl/olm/wgtester" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun" @@ -204,122 +202,41 @@ func runOlmMain(ctx context.Context) { } func runOlmMainWithArgs(ctx context.Context, args []string) { - // Log that we've entered the main function - // fmt.Printf("runOlmMainWithArgs() called with args: %v\n", args) + // Load configuration from file, env vars, and CLI args + // Priority: CLI args > Env vars > Config file > Defaults + config, showVersion, showConfig, err := LoadConfig(args) + if err != nil { + fmt.Printf("Failed to load configuration: %v\n", err) + return + } - // Create a new FlagSet for parsing service arguments - serviceFlags := flag.NewFlagSet("service", flag.ContinueOnError) + // Handle --show-config flag + if showConfig { + config.ShowConfig() + os.Exit(0) + } + // Extract commonly used values from config for convenience var ( - endpoint string - id string - secret string - mtu string + endpoint = config.Endpoint + id = config.ID + secret = config.Secret + mtu = config.MTU mtuInt int - dns string + logLevel = config.LogLevel + interfaceName = config.InterfaceName + enableHTTP = config.EnableHTTP + httpAddr = config.HTTPAddr + pingInterval = config.PingIntervalDuration + pingTimeout = config.PingTimeoutDuration + doHolepunch = config.Holepunch privateKey wgtypes.Key - err error - logLevel string - interfaceName string - enableHTTP bool - httpAddr string - testMode bool // Add this var for the test flag - testTarget string // Add this var for test target - pingInterval time.Duration - pingTimeout time.Duration - doHolepunch bool connected bool ) stopHolepunch = make(chan struct{}) stopPing = make(chan struct{}) - // if PANGOLIN_ENDPOINT, OLM_ID, and OLM_SECRET are set as environment variables, they will be used as default values - endpoint = os.Getenv("PANGOLIN_ENDPOINT") - id = os.Getenv("OLM_ID") - secret = os.Getenv("OLM_SECRET") - mtu = os.Getenv("MTU") - dns = os.Getenv("DNS") - logLevel = os.Getenv("LOG_LEVEL") - interfaceName = os.Getenv("INTERFACE") - httpAddr = os.Getenv("HTTP_ADDR") - pingIntervalStr := os.Getenv("PING_INTERVAL") - pingTimeoutStr := os.Getenv("PING_TIMEOUT") - enableHTTPEnv := os.Getenv("ENABLE_HTTP") - holepunchEnv := os.Getenv("HOLEPUNCH") - - enableHTTP = enableHTTPEnv == "true" - doHolepunch = holepunchEnv == "true" - - if endpoint == "" { - serviceFlags.StringVar(&endpoint, "endpoint", "", "Endpoint of your Pangolin server") - } - if id == "" { - serviceFlags.StringVar(&id, "id", "", "Olm ID") - } - if secret == "" { - serviceFlags.StringVar(&secret, "secret", "", "Olm secret") - } - if mtu == "" { - serviceFlags.StringVar(&mtu, "mtu", "1280", "MTU to use") - } - if dns == "" { - serviceFlags.StringVar(&dns, "dns", "8.8.8.8", "DNS server to use") - } - if logLevel == "" { - serviceFlags.StringVar(&logLevel, "log-level", "INFO", "Log level (DEBUG, INFO, WARN, ERROR, FATAL)") - } - if interfaceName == "" { - serviceFlags.StringVar(&interfaceName, "interface", "olm", "Name of the WireGuard interface") - } - if httpAddr == "" { - serviceFlags.StringVar(&httpAddr, "http-addr", ":9452", "HTTP server address (e.g., ':9452')") - } - if pingIntervalStr == "" { - serviceFlags.StringVar(&pingIntervalStr, "ping-interval", "3s", "Interval for pinging the server (default 3s)") - } - if pingTimeoutStr == "" { - serviceFlags.StringVar(&pingTimeoutStr, "ping-timeout", "5s", " Timeout for each ping (default 3s)") - } - if enableHTTPEnv == "" { - serviceFlags.BoolVar(&enableHTTP, "enable-http", false, "Enable HTT server for receiving connection requests") - } - if holepunchEnv == "" { - serviceFlags.BoolVar(&doHolepunch, "holepunch", false, "Enable hole punching (default false)") - } - - version := serviceFlags.Bool("version", false, "Print the version") - - // Parse the service arguments - if err := serviceFlags.Parse(args); err != nil { - fmt.Printf("Error parsing service arguments: %v\n", err) - return - } - - // Debug: Print final values after flag parsing - // fmt.Printf("After flag parsing: endpoint='%s', id='%s', secret='%s'\n", endpoint, id, secret) - - // Parse ping intervals - if pingIntervalStr != "" { - pingInterval, err = time.ParseDuration(pingIntervalStr) - if err != nil { - fmt.Printf("Invalid PING_INTERVAL value: %s, using default 3 seconds\n", pingIntervalStr) - pingInterval = 3 * time.Second - } - } else { - pingInterval = 3 * time.Second - } - - if pingTimeoutStr != "" { - pingTimeout, err = time.ParseDuration(pingTimeoutStr) - if err != nil { - fmt.Printf("Invalid PING_TIMEOUT value: %s, using default 5 seconds\n", pingTimeoutStr) - pingTimeout = 5 * time.Second - } - } else { - pingTimeout = 5 * time.Second - } - // Setup Windows event logging if on Windows if runtime.GOOS == "windows" { setupWindowsEventLog() @@ -331,12 +248,11 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { logger.GetLogger().SetLevel(parseLogLevel(logLevel)) olmVersion := "version_replaceme" - if *version { + if showVersion { fmt.Println("Olm version " + olmVersion) os.Exit(0) - } else { - logger.Info("Olm version " + olmVersion) } + logger.Info("Olm version " + olmVersion) if err := updates.CheckForUpdate("fosrl", "olm", olmVersion); err != nil { logger.Debug("Failed to check for updates: %v", err) @@ -351,35 +267,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { logger.Warn("Hole punching is enabled. This is EXPERIMENTAL and may not work in all environments.") } - // Handle test mode - if testMode { - if testTarget == "" { - logger.Fatal("Test mode requires -test-target to be set to a server:port") - } - - logger.Info("Running in test mode, connecting to %s", testTarget) - - // Create a new tester client - tester, err := wgtester.NewClient(testTarget) - if err != nil { - logger.Fatal("Failed to create tester client: %v", err) - } - defer tester.Close() - - // Test connection with a 2-second timeout - connected, rtt := tester.TestConnectionWithTimeout(2 * time.Second) - - if connected { - logger.Info("Connection test successful! RTT: %v", rtt) - fmt.Printf("Connection test successful! RTT: %v\n", rtt) - os.Exit(0) - } else { - logger.Error("Connection test failed - no response received") - fmt.Println("Connection test failed - no response received") - os.Exit(1) - } - } - var httpServer *httpserver.HTTPServer if enableHTTP { httpServer = httpserver.NewHTTPServer(httpAddr) @@ -437,9 +324,15 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { if err != nil { logger.Fatal("Failed to create olm: %v", err) } - endpoint = olm.GetConfig().Endpoint // Update endpoint from config - id = olm.GetConfig().ID // Update ID from config - secret = olm.GetConfig().Secret // Update secret from config + // Update config with values from websocket client (which may have loaded from its config file) + config.UpdateFromWebsocket( + olm.GetConfig().ID, + olm.GetConfig().Secret, + olm.GetConfig().Endpoint, + ) + endpoint = config.Endpoint + id = config.ID + secret = config.Secret // wait until we have a client id and secret and endpoint waitCount := 0 @@ -974,6 +867,14 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { httpServer.SetConnectionStatus(true) } + // CRITICAL: Save our full config AFTER websocket saves its limited config + // This ensures all 13 fields are preserved, not just the 4 that websocket saves + if err := SaveConfig(config); err != nil { + logger.Error("Failed to save full olm config: %v", err) + } else { + logger.Debug("Saved full olm config with all options") + } + if connected { logger.Debug("Already connected, skipping registration") return nil From 228bddcf79179ca05abf7b5601691b6bf4ff5141 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 25 Oct 2025 17:15:25 -0700 Subject: [PATCH 5/5] Treat mtu as int and dont overwrite from websocket --- config.go | 34 +++++++++++++--------------------- main.go | 22 +++------------------- 2 files changed, 16 insertions(+), 40 deletions(-) diff --git a/config.go b/config.go index d45328f..8b3664f 100644 --- a/config.go +++ b/config.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "runtime" + "strconv" "time" ) @@ -18,7 +19,7 @@ type OlmConfig struct { Secret string `json:"secret"` // Network settings - MTU string `json:"mtu"` + MTU int `json:"mtu"` DNS string `json:"dns"` InterfaceName string `json:"interface"` @@ -58,7 +59,7 @@ const ( // DefaultConfig returns a config with default values func DefaultConfig() *OlmConfig { config := &OlmConfig{ - MTU: "1280", + MTU: 1280, DNS: "8.8.8.8", LogLevel: "INFO", InterfaceName: "olm", @@ -175,8 +176,12 @@ func loadConfigFromEnv(config *OlmConfig) { config.sources["secret"] = string(SourceEnv) } if val := os.Getenv("MTU"); val != "" { - config.MTU = val - config.sources["mtu"] = string(SourceEnv) + if mtu, err := strconv.Atoi(val); err == nil { + config.MTU = mtu + config.sources["mtu"] = string(SourceEnv) + } else { + fmt.Printf("Invalid MTU value: %s, keeping current value\n", val) + } } if val := os.Getenv("DNS"); val != "" { config.DNS = val @@ -236,7 +241,7 @@ func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) { serviceFlags.StringVar(&config.Endpoint, "endpoint", config.Endpoint, "Endpoint of your Pangolin server") serviceFlags.StringVar(&config.ID, "id", config.ID, "Olm ID") serviceFlags.StringVar(&config.Secret, "secret", config.Secret, "Olm secret") - serviceFlags.StringVar(&config.MTU, "mtu", config.MTU, "MTU to use") + serviceFlags.IntVar(&config.MTU, "mtu", config.MTU, "MTU to use") serviceFlags.StringVar(&config.DNS, "dns", config.DNS, "DNS server to use") serviceFlags.StringVar(&config.LogLevel, "log-level", config.LogLevel, "Log level (DEBUG, INFO, WARN, ERROR, FATAL)") serviceFlags.StringVar(&config.InterfaceName, "interface", config.InterfaceName, "Name of the WireGuard interface") @@ -264,7 +269,7 @@ func loadConfigFromCLI(config *OlmConfig, args []string) (bool, bool, error) { if config.Secret != origValues["secret"].(string) { config.sources["secret"] = string(SourceCLI) } - if config.MTU != origValues["mtu"].(string) { + if config.MTU != origValues["mtu"].(int) { config.sources["mtu"] = string(SourceCLI) } if config.DNS != origValues["dns"].(string) { @@ -343,7 +348,7 @@ func mergeConfigs(dest, src *OlmConfig) { dest.Secret = src.Secret dest.sources["secret"] = string(SourceFile) } - if src.MTU != "" && src.MTU != "1280" { + if src.MTU != 0 && src.MTU != 1280 { dest.MTU = src.MTU dest.sources["mtu"] = string(SourceFile) } @@ -396,19 +401,6 @@ func SaveConfig(config *OlmConfig) error { return os.WriteFile(configPath, data, 0644) } -// UpdateFromWebsocket updates config with values received from websocket client -func (c *OlmConfig) UpdateFromWebsocket(id, secret, endpoint string) { - if id != "" { - c.ID = id - } - if secret != "" { - c.Secret = secret - } - if endpoint != "" { - c.Endpoint = endpoint - } -} - // ShowConfig prints the configuration and the source of each value func (c *OlmConfig) ShowConfig() { configPath := getOlmConfigPath() @@ -456,7 +448,7 @@ func (c *OlmConfig) ShowConfig() { // Network settings fmt.Println("\nNetwork:") - fmt.Printf(" mtu = %s [%s]\n", c.MTU, getSource("mtu")) + fmt.Printf(" mtu = %d [%s]\n", c.MTU, getSource("mtu")) fmt.Printf(" dns = %s [%s]\n", c.DNS, getSource("dns")) fmt.Printf(" interface = %s [%s]\n", c.InterfaceName, getSource("interface")) diff --git a/main.go b/main.go index 05a23f7..3ef705c 100644 --- a/main.go +++ b/main.go @@ -222,7 +222,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { id = config.ID secret = config.Secret mtu = config.MTU - mtuInt int logLevel = config.LogLevel interfaceName = config.InterfaceName enableHTTP = config.EnableHTTP @@ -324,15 +323,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { if err != nil { logger.Fatal("Failed to create olm: %v", err) } - // Update config with values from websocket client (which may have loaded from its config file) - config.UpdateFromWebsocket( - olm.GetConfig().ID, - olm.GetConfig().Secret, - olm.GetConfig().Endpoint, - ) - endpoint = config.Endpoint - id = config.ID - secret = config.Secret // wait until we have a client id and secret and endpoint waitCount := 0 @@ -360,12 +350,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { } } - // parse the mtu string into an int - mtuInt, err = strconv.Atoi(mtu) - if err != nil { - logger.Fatal("Failed to parse MTU: %v", err) - } - privateKey, err = wgtypes.GeneratePrivateKey() if err != nil { logger.Fatal("Failed to generate private key: %v", err) @@ -486,12 +470,12 @@ func runOlmMainWithArgs(ctx context.Context, args []string) { if err != nil { return nil, err } - return tun.CreateTUN(interfaceName, mtuInt) + return tun.CreateTUN(interfaceName, mtu) } if tunFdStr := os.Getenv(ENV_WG_TUN_FD); tunFdStr != "" { - return createTUNFromFD(tunFdStr, mtuInt) + return createTUNFromFD(tunFdStr, mtu) } - return tun.CreateTUN(interfaceName, mtuInt) + return tun.CreateTUN(interfaceName, mtu) }() if err != nil {