Compare commits
2 Commits
ui-refacto
...
wasm-webso
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e230cf1d96 | ||
|
|
fbd74d3867 |
10
.github/workflows/golang-test-darwin.yml
vendored
@@ -43,13 +43,5 @@ jobs:
|
|||||||
run: git --no-pager diff --exit-code
|
run: git --no-pager diff --exit-code
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
# Exclude client/ui: its main.go uses //go:embed all:frontend/dist,
|
run: NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -tags=devcert -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 $(go list ./... | grep -v -e /management -e /signal -e /relay -e /proxy -e /combined)
|
||||||
# which fails to compile until the frontend has been built. The Wails UI
|
|
||||||
# has no Go-side unit tests, and its release pipeline runs `pnpm build`
|
|
||||||
# before goreleaser.
|
|
||||||
# `go list -e` lets the listing succeed even though the embed fails to
|
|
||||||
# resolve; the grep then drops the broken package by path. Without -e,
|
|
||||||
# go list aborts with empty stdout and `go test` falls back to the repo
|
|
||||||
# root, which has no Go files.
|
|
||||||
run: NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -tags=devcert -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 $(go list -e ./... | grep -v -e /management -e /signal -e /relay -e /proxy -e /combined -e /client/ui)
|
|
||||||
|
|
||||||
|
|||||||
12
.github/workflows/golang-test-linux.yml
vendored
@@ -154,15 +154,7 @@ jobs:
|
|||||||
run: git --no-pager diff --exit-code
|
run: git --no-pager diff --exit-code
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
# Exclude client/ui: its main.go uses //go:embed all:frontend/dist,
|
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} CI=true go test -tags devcert -exec 'sudo' -timeout 10m -p 1 $(go list ./... | grep -v -e /management -e /signal -e /relay -e /proxy -e /combined)
|
||||||
# which fails to compile until the frontend has been built. The Wails UI
|
|
||||||
# has no Go-side unit tests, and its release pipeline runs `pnpm build`
|
|
||||||
# before goreleaser.
|
|
||||||
# `go list -e` lets the listing succeed even though the embed fails to
|
|
||||||
# resolve; the grep then drops the broken package by path. Without -e,
|
|
||||||
# go list aborts with empty stdout and `go test` falls back to the repo
|
|
||||||
# root, which has no Go files.
|
|
||||||
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} CI=true go test -tags devcert -exec 'sudo' -timeout 10m -p 1 $(go list -e ./... | grep -v -e /management -e /signal -e /relay -e /proxy -e /combined -e /client/ui)
|
|
||||||
|
|
||||||
test_client_on_docker:
|
test_client_on_docker:
|
||||||
name: "Client (Docker) / Unit"
|
name: "Client (Docker) / Unit"
|
||||||
@@ -222,7 +214,7 @@ jobs:
|
|||||||
sh -c ' \
|
sh -c ' \
|
||||||
apk update; apk add --no-cache \
|
apk update; apk add --no-cache \
|
||||||
ca-certificates iptables ip6tables dbus dbus-dev libpcap-dev build-base; \
|
ca-certificates iptables ip6tables dbus dbus-dev libpcap-dev build-base; \
|
||||||
go test -buildvcs=false -tags devcert -v -timeout 10m -p 1 $(go list -e -buildvcs=false ./... | grep -v -e /management -e /signal -e /relay -e /proxy -e /combined -e /client/ui -e /upload-server)
|
go test -buildvcs=false -tags devcert -v -timeout 10m -p 1 $(go list -buildvcs=false ./... | grep -v -e /management -e /signal -e /relay -e /proxy -e /combined -e /client/ui -e /upload-server)
|
||||||
'
|
'
|
||||||
|
|
||||||
test_relay:
|
test_relay:
|
||||||
|
|||||||
9
.github/workflows/golang-test-windows.yml
vendored
@@ -64,15 +64,8 @@ jobs:
|
|||||||
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=${{ env.modcache }}
|
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=${{ env.modcache }}
|
||||||
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe mod tidy
|
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe mod tidy
|
||||||
- name: Generate test script
|
- name: Generate test script
|
||||||
# Exclude client/ui: its main.go uses //go:embed all:frontend/dist,
|
|
||||||
# which fails to compile until the frontend has been built. The Wails UI
|
|
||||||
# has no Go-side unit tests, and its release pipeline runs `pnpm build`
|
|
||||||
# before goreleaser.
|
|
||||||
# `go list -e` lets the listing succeed even though the embed fails to
|
|
||||||
# resolve; the Where-Object pipeline then drops the broken package by
|
|
||||||
# path. Without -e, go list aborts with empty stdout.
|
|
||||||
run: |
|
run: |
|
||||||
$packages = go list -e ./... | Where-Object { $_ -notmatch '/management' } | Where-Object { $_ -notmatch '/relay' } | Where-Object { $_ -notmatch '/signal' } | Where-Object { $_ -notmatch '/proxy' } | Where-Object { $_ -notmatch '/combined' } | Where-Object { $_ -notmatch '/client/ui' }
|
$packages = go list ./... | Where-Object { $_ -notmatch '/management' } | Where-Object { $_ -notmatch '/relay' } | Where-Object { $_ -notmatch '/signal' } | Where-Object { $_ -notmatch '/proxy' } | Where-Object { $_ -notmatch '/combined' }
|
||||||
$goExe = "C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe"
|
$goExe = "C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe"
|
||||||
$cmd = "$goExe test -tags=devcert -timeout 10m -p 1 $($packages -join ' ') > test-out.txt 2>&1"
|
$cmd = "$goExe test -tags=devcert -timeout 10m -p 1 $($packages -join ' ') > test-out.txt 2>&1"
|
||||||
Set-Content -Path "${{ github.workspace }}\run-tests.cmd" -Value $cmd
|
Set-Content -Path "${{ github.workspace }}\run-tests.cmd" -Value $cmd
|
||||||
|
|||||||
11
.github/workflows/golangci-lint.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
uses: codespell-project/actions-codespell@v2
|
uses: codespell-project/actions-codespell@v2
|
||||||
with:
|
with:
|
||||||
ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe,cros,ans,deriver,te,userA,ede,additionals
|
ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe,cros,ans,deriver,te,userA,ede,additionals
|
||||||
skip: go.mod,go.sum,**/proxy/web/**,**/pnpm-lock.yaml,**/package-lock.json
|
skip: go.mod,go.sum,**/proxy/web/**
|
||||||
golangci:
|
golangci:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -51,15 +51,6 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev libpcap-dev
|
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev libpcap-dev
|
||||||
- name: Stub Wails frontend bundle
|
|
||||||
# client/ui/main.go has //go:embed all:frontend/dist. The
|
|
||||||
# directory is produced by `pnpm run build` and is gitignored, so
|
|
||||||
# lint-only runs (no frontend toolchain) need a placeholder file
|
|
||||||
# for the embed pattern to match.
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
mkdir -p client/ui/frontend/dist
|
|
||||||
touch client/ui/frontend/dist/.embed-placeholder
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
|
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
28
.github/workflows/release.yml
vendored
@@ -186,9 +186,9 @@ jobs:
|
|||||||
- name: Install goversioninfo
|
- name: Install goversioninfo
|
||||||
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
|
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
|
||||||
- name: Generate windows syso amd64
|
- name: Generate windows syso amd64
|
||||||
run: goversioninfo -icon client/ui/build/windows/icon.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
|
run: goversioninfo -icon client/ui/assets/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso
|
||||||
- name: Generate windows syso arm64
|
- name: Generate windows syso arm64
|
||||||
run: goversioninfo -arm -64 -icon client/ui/build/windows/icon.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm64.syso
|
run: goversioninfo -arm -64 -icon client/ui/assets/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_arm64.syso
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
id: goreleaser
|
id: goreleaser
|
||||||
uses: goreleaser/goreleaser-action@v4
|
uses: goreleaser/goreleaser-action@v4
|
||||||
@@ -349,18 +349,8 @@ jobs:
|
|||||||
- name: check git status
|
- name: check git status
|
||||||
run: git --no-pager diff --exit-code
|
run: git --no-pager diff --exit-code
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
|
|
||||||
- name: Set up pnpm
|
|
||||||
uses: pnpm/action-setup@v3
|
|
||||||
with:
|
|
||||||
version: 9
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libwebkit2gtk-4.1-dev libsoup-3.0-dev libayatana-appindicator3-dev gcc-mingw-w64-x86-64
|
run: sudo apt update && sudo apt install -y -q libappindicator3-dev gir1.2-appindicator3-0.1 libxxf86vm-dev gcc-mingw-w64-x86-64
|
||||||
|
|
||||||
- name: Decode GPG signing key
|
- name: Decode GPG signing key
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
@@ -380,9 +370,9 @@ jobs:
|
|||||||
- name: Install goversioninfo
|
- name: Install goversioninfo
|
||||||
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
|
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
|
||||||
- name: Generate windows syso amd64
|
- name: Generate windows syso amd64
|
||||||
run: goversioninfo -64 -icon client/ui/build/windows/icon.ico -manifest client/ui/build/windows/wails.exe.manifest -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
|
run: goversioninfo -64 -icon client/ui/assets/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso
|
||||||
- name: Generate windows syso arm64
|
- name: Generate windows syso arm64
|
||||||
run: goversioninfo -arm -64 -icon client/ui/build/windows/icon.ico -manifest client/ui/build/windows/wails.exe.manifest -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_arm64.syso
|
run: goversioninfo -arm -64 -icon client/ui/assets/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_arm64.syso
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v4
|
uses: goreleaser/goreleaser-action@v4
|
||||||
@@ -449,14 +439,6 @@ jobs:
|
|||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
- name: check git status
|
- name: check git status
|
||||||
run: git --no-pager diff --exit-code
|
run: git --no-pager diff --exit-code
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
- name: Set up pnpm
|
|
||||||
uses: pnpm/action-setup@v3
|
|
||||||
with:
|
|
||||||
version: 9
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
id: goreleaser
|
id: goreleaser
|
||||||
uses: goreleaser/goreleaser-action@v4
|
uses: goreleaser/goreleaser-action@v4
|
||||||
|
|||||||
@@ -114,16 +114,6 @@ linters:
|
|||||||
- linters:
|
- linters:
|
||||||
- staticcheck
|
- staticcheck
|
||||||
text: "QF1012"
|
text: "QF1012"
|
||||||
# client/ui/main.go uses //go:embed all:frontend/dist; the
|
|
||||||
# directory is populated by `pnpm build` in the release pipeline
|
|
||||||
# and missing at lint time, so the embed parses to "no matching
|
|
||||||
# files found" — surfaced by golangci-lint's typecheck pre-pass.
|
|
||||||
# Suppress just that one diagnostic; the rest of the package
|
|
||||||
# (services/, tray.go, grpc.go, ...) still gets linted normally.
|
|
||||||
- linters:
|
|
||||||
- typecheck
|
|
||||||
path: client/ui/main\.go
|
|
||||||
text: "pattern all:frontend/dist"
|
|
||||||
paths:
|
paths:
|
||||||
- third_party$
|
- third_party$
|
||||||
- builtin$
|
- builtin$
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
project_name: netbird-ui
|
project_name: netbird-ui
|
||||||
|
|
||||||
before:
|
|
||||||
hooks:
|
|
||||||
- sh -c 'cd client/ui/frontend && pnpm install --frozen-lockfile && pnpm build'
|
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: netbird-ui
|
- id: netbird-ui
|
||||||
dir: client/ui
|
dir: client/ui
|
||||||
@@ -75,15 +70,12 @@ nfpms:
|
|||||||
scripts:
|
scripts:
|
||||||
postinstall: "release_files/ui-post-install.sh"
|
postinstall: "release_files/ui-post-install.sh"
|
||||||
contents:
|
contents:
|
||||||
- src: client/ui/build/linux/netbird.desktop
|
- src: client/ui/build/netbird.desktop
|
||||||
dst: /usr/share/applications/netbird.desktop
|
dst: /usr/share/applications/netbird.desktop
|
||||||
- src: client/ui/build/appicon.png
|
- src: client/ui/assets/netbird.png
|
||||||
dst: /usr/share/pixmaps/netbird.png
|
dst: /usr/share/pixmaps/netbird.png
|
||||||
dependencies:
|
dependencies:
|
||||||
- netbird
|
- netbird
|
||||||
- libgtk-3-0
|
|
||||||
- libwebkit2gtk-4.1-0
|
|
||||||
- libayatana-appindicator3-1
|
|
||||||
|
|
||||||
- maintainer: Netbird <dev@netbird.io>
|
- maintainer: Netbird <dev@netbird.io>
|
||||||
description: Netbird client UI.
|
description: Netbird client UI.
|
||||||
@@ -97,15 +89,12 @@ nfpms:
|
|||||||
scripts:
|
scripts:
|
||||||
postinstall: "release_files/ui-post-install.sh"
|
postinstall: "release_files/ui-post-install.sh"
|
||||||
contents:
|
contents:
|
||||||
- src: client/ui/build/linux/netbird.desktop
|
- src: client/ui/build/netbird.desktop
|
||||||
dst: /usr/share/applications/netbird.desktop
|
dst: /usr/share/applications/netbird.desktop
|
||||||
- src: client/ui/build/appicon.png
|
- src: client/ui/assets/netbird.png
|
||||||
dst: /usr/share/pixmaps/netbird.png
|
dst: /usr/share/pixmaps/netbird.png
|
||||||
dependencies:
|
dependencies:
|
||||||
- netbird
|
- netbird
|
||||||
- gtk3
|
|
||||||
- webkit2gtk4.1
|
|
||||||
- libayatana-appindicator-gtk3
|
|
||||||
rpm:
|
rpm:
|
||||||
signature:
|
signature:
|
||||||
key_file: '{{ if index .Env "GPG_RPM_KEY_FILE" }}{{ .Env.GPG_RPM_KEY_FILE }}{{ end }}'
|
key_file: '{{ if index .Env "GPG_RPM_KEY_FILE" }}{{ .Env.GPG_RPM_KEY_FILE }}{{ end }}'
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
project_name: netbird-ui
|
project_name: netbird-ui
|
||||||
|
|
||||||
before:
|
|
||||||
hooks:
|
|
||||||
- sh -c 'cd client/ui/frontend && pnpm install --frozen-lockfile && pnpm build'
|
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: netbird-ui-darwin
|
- id: netbird-ui-darwin
|
||||||
dir: client/ui
|
dir: client/ui
|
||||||
@@ -25,6 +20,8 @@ builds:
|
|||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
- -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
|
||||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||||
|
tags:
|
||||||
|
- load_wgnt_from_rsrc
|
||||||
|
|
||||||
universal_binaries:
|
universal_binaries:
|
||||||
- id: netbird-ui-darwin
|
- id: netbird-ui-darwin
|
||||||
|
|||||||
@@ -280,43 +280,6 @@ CreateShortCut "$SMPROGRAMS\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
|||||||
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${UI_APP_EXE}"
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
# Install the Microsoft Edge WebView2 runtime if it isn't already present.
|
|
||||||
# Macro adapted from Wails3's NSIS template (wails_tools.nsh): a registry
|
|
||||||
# probe followed by a silent install of the embedded evergreen bootstrapper.
|
|
||||||
# The MicrosoftEdgeWebview2Setup.exe payload is staged next to this script
|
|
||||||
# by the sign-pipelines build step (`wails3 generate webview2bootstrapper`).
|
|
||||||
!macro nb.webview2runtime
|
|
||||||
SetRegView 64
|
|
||||||
# Per-machine install marker — populated when the runtime ships with
|
|
||||||
# Edge or has been installed by an admin previously.
|
|
||||||
ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
|
||||||
${If} $0 != ""
|
|
||||||
Goto webview2_ok
|
|
||||||
${EndIf}
|
|
||||||
# Per-user fallback for HKCU installs.
|
|
||||||
ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
|
||||||
${If} $0 != ""
|
|
||||||
Goto webview2_ok
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
SetDetailsPrint both
|
|
||||||
DetailPrint "Installing: WebView2 Runtime"
|
|
||||||
SetDetailsPrint listonly
|
|
||||||
|
|
||||||
InitPluginsDir
|
|
||||||
CreateDirectory "$pluginsdir\webview2bootstrapper"
|
|
||||||
SetOutPath "$pluginsdir\webview2bootstrapper"
|
|
||||||
File "MicrosoftEdgeWebview2Setup.exe"
|
|
||||||
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
|
|
||||||
|
|
||||||
SetDetailsPrint both
|
|
||||||
webview2_ok:
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
Section -WebView2
|
|
||||||
!insertmacro nb.webview2runtime
|
|
||||||
SectionEnd
|
|
||||||
|
|
||||||
Section -Post
|
Section -Post
|
||||||
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service install'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service install'
|
||||||
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service start'
|
ExecWait '"$INSTDIR\${MAIN_APP_EXE}" service start'
|
||||||
@@ -363,9 +326,9 @@ DetailPrint "Deleting application files..."
|
|||||||
Delete "$INSTDIR\${UI_APP_EXE}"
|
Delete "$INSTDIR\${UI_APP_EXE}"
|
||||||
Delete "$INSTDIR\${MAIN_APP_EXE}"
|
Delete "$INSTDIR\${MAIN_APP_EXE}"
|
||||||
Delete "$INSTDIR\wintun.dll"
|
Delete "$INSTDIR\wintun.dll"
|
||||||
# Legacy: pre-Wails installs shipped opengl32.dll (Mesa3D for Fyne); remove
|
!if ${ARCH} == "amd64"
|
||||||
# any leftover copy on uninstall so old upgrades don't leave it behind.
|
|
||||||
Delete "$INSTDIR\opengl32.dll"
|
Delete "$INSTDIR\opengl32.dll"
|
||||||
|
!endif
|
||||||
DetailPrint "Removing application directory..."
|
DetailPrint "Removing application directory..."
|
||||||
RmDir /r "$INSTDIR"
|
RmDir /r "$INSTDIR"
|
||||||
|
|
||||||
|
|||||||
@@ -217,14 +217,6 @@ type Status struct {
|
|||||||
eventStreams map[string]chan *proto.SystemEvent
|
eventStreams map[string]chan *proto.SystemEvent
|
||||||
eventQueue *EventQueue
|
eventQueue *EventQueue
|
||||||
|
|
||||||
// stateChangeStreams fan-out connection-state changes (connected /
|
|
||||||
// disconnected / connecting / address change / peers list change) to
|
|
||||||
// every active SubscribeStatus gRPC stream. Each subscriber gets a
|
|
||||||
// buffered chan; the notifier non-blockingly pings them so a slow
|
|
||||||
// consumer can never stall the daemon.
|
|
||||||
stateChangeMux sync.Mutex
|
|
||||||
stateChangeStreams map[string]chan struct{}
|
|
||||||
|
|
||||||
ingressGwMgr *ingressgw.Manager
|
ingressGwMgr *ingressgw.Manager
|
||||||
|
|
||||||
routeIDLookup routeIDLookup
|
routeIDLookup routeIDLookup
|
||||||
@@ -238,7 +230,6 @@ func NewRecorder(mgmAddress string) *Status {
|
|||||||
changeNotify: make(map[string]map[string]*StatusChangeSubscription),
|
changeNotify: make(map[string]map[string]*StatusChangeSubscription),
|
||||||
eventStreams: make(map[string]chan *proto.SystemEvent),
|
eventStreams: make(map[string]chan *proto.SystemEvent),
|
||||||
eventQueue: NewEventQueue(eventQueueSize),
|
eventQueue: NewEventQueue(eventQueueSize),
|
||||||
stateChangeStreams: make(map[string]chan struct{}),
|
|
||||||
offlinePeers: make([]State, 0),
|
offlinePeers: make([]State, 0),
|
||||||
notifier: newNotifier(),
|
notifier: newNotifier(),
|
||||||
mgmAddress: mgmAddress,
|
mgmAddress: mgmAddress,
|
||||||
@@ -369,7 +360,6 @@ func (d *Status) UpdatePeerState(receivedState State) error {
|
|||||||
if notifyRouter {
|
if notifyRouter {
|
||||||
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
||||||
}
|
}
|
||||||
d.notifyStateChange()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +385,6 @@ func (d *Status) AddPeerStateRoute(peer string, route string, resourceId route.R
|
|||||||
|
|
||||||
// todo: consider to make sense of this notification or not
|
// todo: consider to make sense of this notification or not
|
||||||
d.notifier.peerListChanged(numPeers)
|
d.notifier.peerListChanged(numPeers)
|
||||||
d.notifyStateChange()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +410,6 @@ func (d *Status) RemovePeerStateRoute(peer string, route string) error {
|
|||||||
|
|
||||||
// todo: consider to make sense of this notification or not
|
// todo: consider to make sense of this notification or not
|
||||||
d.notifier.peerListChanged(numPeers)
|
d.notifier.peerListChanged(numPeers)
|
||||||
d.notifyStateChange()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,7 +459,6 @@ func (d *Status) UpdatePeerICEState(receivedState State) error {
|
|||||||
if notifyRouter {
|
if notifyRouter {
|
||||||
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
||||||
}
|
}
|
||||||
d.notifyStateChange()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,7 +495,6 @@ func (d *Status) UpdatePeerRelayedState(receivedState State) error {
|
|||||||
if notifyRouter {
|
if notifyRouter {
|
||||||
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
||||||
}
|
}
|
||||||
d.notifyStateChange()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +530,6 @@ func (d *Status) UpdatePeerRelayedStateToDisconnected(receivedState State) error
|
|||||||
if notifyRouter {
|
if notifyRouter {
|
||||||
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
||||||
}
|
}
|
||||||
d.notifyStateChange()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,7 +568,6 @@ func (d *Status) UpdatePeerICEStateToDisconnected(receivedState State) error {
|
|||||||
if notifyRouter {
|
if notifyRouter {
|
||||||
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
d.dispatchRouterPeers(receivedState.PubKey, routerSnapshot)
|
||||||
}
|
}
|
||||||
d.notifyStateChange()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -677,7 +661,6 @@ func (d *Status) FinishPeerListModifications() {
|
|||||||
for _, rd := range dispatches {
|
for _, rd := range dispatches {
|
||||||
d.dispatchRouterPeers(rd.peerID, rd.snapshot)
|
d.dispatchRouterPeers(rd.peerID, rd.snapshot)
|
||||||
}
|
}
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) SubscribeToPeerStateChanges(ctx context.Context, peerID string) *StatusChangeSubscription {
|
func (d *Status) SubscribeToPeerStateChanges(ctx context.Context, peerID string) *StatusChangeSubscription {
|
||||||
@@ -736,7 +719,6 @@ func (d *Status) UpdateLocalPeerState(localPeerState LocalPeerState) {
|
|||||||
d.mux.Unlock()
|
d.mux.Unlock()
|
||||||
|
|
||||||
d.notifier.localAddressChanged(fqdn, ip)
|
d.notifier.localAddressChanged(fqdn, ip)
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddLocalPeerStateRoute adds a route to the local peer state
|
// AddLocalPeerStateRoute adds a route to the local peer state
|
||||||
@@ -805,7 +787,6 @@ func (d *Status) CleanLocalPeerState() {
|
|||||||
d.mux.Unlock()
|
d.mux.Unlock()
|
||||||
|
|
||||||
d.notifier.localAddressChanged(fqdn, ip)
|
d.notifier.localAddressChanged(fqdn, ip)
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkManagementDisconnected sets ManagementState to disconnected
|
// MarkManagementDisconnected sets ManagementState to disconnected
|
||||||
@@ -818,7 +799,6 @@ func (d *Status) MarkManagementDisconnected(err error) {
|
|||||||
d.mux.Unlock()
|
d.mux.Unlock()
|
||||||
|
|
||||||
d.notifier.updateServerStates(mgm, sig)
|
d.notifier.updateServerStates(mgm, sig)
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkManagementConnected sets ManagementState to connected
|
// MarkManagementConnected sets ManagementState to connected
|
||||||
@@ -831,7 +811,6 @@ func (d *Status) MarkManagementConnected() {
|
|||||||
d.mux.Unlock()
|
d.mux.Unlock()
|
||||||
|
|
||||||
d.notifier.updateServerStates(mgm, sig)
|
d.notifier.updateServerStates(mgm, sig)
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSignalAddress update the address of the signal server
|
// UpdateSignalAddress update the address of the signal server
|
||||||
@@ -872,7 +851,6 @@ func (d *Status) MarkSignalDisconnected(err error) {
|
|||||||
d.mux.Unlock()
|
d.mux.Unlock()
|
||||||
|
|
||||||
d.notifier.updateServerStates(mgm, sig)
|
d.notifier.updateServerStates(mgm, sig)
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkSignalConnected sets SignalState to connected
|
// MarkSignalConnected sets SignalState to connected
|
||||||
@@ -885,7 +863,6 @@ func (d *Status) MarkSignalConnected() {
|
|||||||
d.mux.Unlock()
|
d.mux.Unlock()
|
||||||
|
|
||||||
d.notifier.updateServerStates(mgm, sig)
|
d.notifier.updateServerStates(mgm, sig)
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Status) UpdateRelayStates(relayResults []relay.ProbeResult) {
|
func (d *Status) UpdateRelayStates(relayResults []relay.ProbeResult) {
|
||||||
@@ -1083,19 +1060,16 @@ func (d *Status) GetFullStatus() FullStatus {
|
|||||||
// ClientStart will notify all listeners about the new service state
|
// ClientStart will notify all listeners about the new service state
|
||||||
func (d *Status) ClientStart() {
|
func (d *Status) ClientStart() {
|
||||||
d.notifier.clientStart()
|
d.notifier.clientStart()
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientStop will notify all listeners about the new service state
|
// ClientStop will notify all listeners about the new service state
|
||||||
func (d *Status) ClientStop() {
|
func (d *Status) ClientStop() {
|
||||||
d.notifier.clientStop()
|
d.notifier.clientStop()
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientTeardown will notify all listeners about the service is under teardown
|
// ClientTeardown will notify all listeners about the service is under teardown
|
||||||
func (d *Status) ClientTeardown() {
|
func (d *Status) ClientTeardown() {
|
||||||
d.notifier.clientTearDown()
|
d.notifier.clientTearDown()
|
||||||
d.notifyStateChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetConnectionListener set a listener to the notifier
|
// SetConnectionListener set a listener to the notifier
|
||||||
@@ -1237,50 +1211,6 @@ func (d *Status) GetEventHistory() []*proto.SystemEvent {
|
|||||||
return d.eventQueue.GetAll()
|
return d.eventQueue.GetAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubscribeToStateChanges hands back a channel that receives a tick on
|
|
||||||
// every connection-state change (connected / disconnected / connecting /
|
|
||||||
// address change / peers-list change). The channel is buffered to one
|
|
||||||
// pending tick so a coalesced burst still wakes the consumer exactly
|
|
||||||
// once. Pass the returned id to UnsubscribeFromStateChanges to detach.
|
|
||||||
func (d *Status) SubscribeToStateChanges() (string, <-chan struct{}) {
|
|
||||||
d.stateChangeMux.Lock()
|
|
||||||
defer d.stateChangeMux.Unlock()
|
|
||||||
|
|
||||||
id := uuid.New().String()
|
|
||||||
ch := make(chan struct{}, 1)
|
|
||||||
d.stateChangeStreams[id] = ch
|
|
||||||
return id, ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnsubscribeFromStateChanges releases a SubscribeToStateChanges channel
|
|
||||||
// and closes it so any consumer goroutine selecting on the channel
|
|
||||||
// unblocks cleanly.
|
|
||||||
func (d *Status) UnsubscribeFromStateChanges(id string) {
|
|
||||||
d.stateChangeMux.Lock()
|
|
||||||
defer d.stateChangeMux.Unlock()
|
|
||||||
|
|
||||||
if ch, ok := d.stateChangeStreams[id]; ok {
|
|
||||||
close(ch)
|
|
||||||
delete(d.stateChangeStreams, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifyStateChange wakes every SubscribeToStateChanges subscriber. Drops
|
|
||||||
// the tick if a subscriber's buffer is full — by definition the consumer
|
|
||||||
// is already going to fetch the latest snapshot, so multiple pending ticks
|
|
||||||
// would be redundant.
|
|
||||||
func (d *Status) notifyStateChange() {
|
|
||||||
d.stateChangeMux.Lock()
|
|
||||||
defer d.stateChangeMux.Unlock()
|
|
||||||
|
|
||||||
for _, ch := range d.stateChangeStreams {
|
|
||||||
select {
|
|
||||||
case ch <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Status) SetWgIface(wgInterface WGIfaceStatus) {
|
func (d *Status) SetWgIface(wgInterface WGIfaceStatus) {
|
||||||
d.mux.Lock()
|
d.mux.Lock()
|
||||||
defer d.mux.Unlock()
|
defer d.mux.Unlock()
|
||||||
|
|||||||
@@ -32,6 +32,9 @@
|
|||||||
</File>
|
</File>
|
||||||
<File ProcessorArchitecture="$(var.ProcessorArchitecture)" Source=".\dist\netbird_windows_$(var.ArchSuffix)\wintun.dll" />
|
<File ProcessorArchitecture="$(var.ProcessorArchitecture)" Source=".\dist\netbird_windows_$(var.ArchSuffix)\wintun.dll" />
|
||||||
<File Id="NetbirdToastIcon" Name="netbird.png" Source=".\client\ui\assets\netbird.png" />
|
<File Id="NetbirdToastIcon" Name="netbird.png" Source=".\client\ui\assets\netbird.png" />
|
||||||
|
<?if $(var.ArchSuffix) = "amd64" ?>
|
||||||
|
<File ProcessorArchitecture="$(var.ProcessorArchitecture)" Source=".\dist\netbird_windows_$(var.ArchSuffix)\opengl32.dll" />
|
||||||
|
<?endif ?>
|
||||||
|
|
||||||
<ServiceInstall
|
<ServiceInstall
|
||||||
Id="NetBirdService"
|
Id="NetBirdService"
|
||||||
@@ -59,14 +62,6 @@
|
|||||||
<Component Id="NetbirdAumidRegistry" Guid="*">
|
<Component Id="NetbirdAumidRegistry" Guid="*">
|
||||||
<RegistryKey Root="HKCU" Key="Software\Classes\AppUserModelId\NetBird" ForceDeleteOnUninstall="yes">
|
<RegistryKey Root="HKCU" Key="Software\Classes\AppUserModelId\NetBird" ForceDeleteOnUninstall="yes">
|
||||||
<RegistryValue Name="InstalledByMSI" Type="integer" Value="1" KeyPath="yes" />
|
<RegistryValue Name="InstalledByMSI" Type="integer" Value="1" KeyPath="yes" />
|
||||||
<!-- Pre-seed the CLSID the Wails notifications service reads on
|
|
||||||
first startup (notifications_windows.go:getGUID looks for
|
|
||||||
the CustomActivator value under this key). Without this
|
|
||||||
the service generates a fresh per-install UUID, which
|
|
||||||
diverges from the ToastActivatorCLSID set on the Start
|
|
||||||
Menu / Desktop shortcuts above and the COM activator
|
|
||||||
never fires when a toast is clicked. -->
|
|
||||||
<RegistryValue Name="CustomActivator" Type="string" Value="{0E1B4DE7-E148-432B-9814-544F941826EC}" />
|
|
||||||
</RegistryKey>
|
</RegistryKey>
|
||||||
</Component>
|
</Component>
|
||||||
</StandardDirectory>
|
</StandardDirectory>
|
||||||
@@ -90,37 +85,7 @@
|
|||||||
<util:CloseApplication Id="CloseNetBird" CloseMessage="no" Target="netbird.exe" RebootPrompt="no" />
|
<util:CloseApplication Id="CloseNetBird" CloseMessage="no" Target="netbird.exe" RebootPrompt="no" />
|
||||||
<util:CloseApplication Id="CloseNetBirdUI" CloseMessage="no" Target="netbird-ui.exe" RebootPrompt="no" TerminateProcess="0" />
|
<util:CloseApplication Id="CloseNetBirdUI" CloseMessage="no" Target="netbird-ui.exe" RebootPrompt="no" TerminateProcess="0" />
|
||||||
|
|
||||||
<!-- WebView2 evergreen runtime detection.
|
|
||||||
Probe both the per-machine and per-user EdgeUpdate keys; if either
|
|
||||||
reports a non-empty `pv` value the runtime is already installed
|
|
||||||
and we skip the bootstrapper. -->
|
|
||||||
<Property Id="WEBVIEW2_VERSION_HKLM">
|
|
||||||
<RegistrySearch Id="WV2HKLM" Root="HKLM"
|
|
||||||
Key="SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
|
|
||||||
Name="pv" Type="raw" Bitness="always64" />
|
|
||||||
</Property>
|
|
||||||
<Property Id="WEBVIEW2_VERSION_HKCU">
|
|
||||||
<RegistrySearch Id="WV2HKCU" Root="HKCU"
|
|
||||||
Key="Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
|
|
||||||
Name="pv" Type="raw" />
|
|
||||||
</Property>
|
|
||||||
|
|
||||||
<!-- Embed the bootstrapper payload. Path is relative to the WiX
|
|
||||||
working directory; sign-pipelines stages it next to client/
|
|
||||||
via `wails3 generate webview2bootstrapper`. -->
|
|
||||||
<Binary Id="WebView2Bootstrapper" SourceFile=".\client\MicrosoftEdgeWebview2Setup.exe" />
|
|
||||||
|
|
||||||
<CustomAction Id="InstallWebView2"
|
|
||||||
BinaryRef="WebView2Bootstrapper"
|
|
||||||
ExeCommand="/silent /install"
|
|
||||||
Execute="deferred"
|
|
||||||
Impersonate="no"
|
|
||||||
Return="check" />
|
|
||||||
|
|
||||||
<InstallExecuteSequence>
|
|
||||||
<Custom Action="InstallWebView2" Before="InstallFinalize"
|
|
||||||
Condition="NOT WEBVIEW2_VERSION_HKLM AND NOT WEBVIEW2_VERSION_HKCU AND NOT REMOVE" />
|
|
||||||
</InstallExecuteSequence>
|
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Icons -->
|
||||||
<Icon Id="NetbirdIcon" SourceFile=".\client\ui\assets\netbird.ico" />
|
<Icon Id="NetbirdIcon" SourceFile=".\client\ui\assets\netbird.ico" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.6
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v7.34.1
|
// protoc v6.33.1
|
||||||
// source: daemon.proto
|
// source: daemon.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -6773,13 +6773,12 @@ const file_daemon_proto_rawDesc = "" +
|
|||||||
"\n" +
|
"\n" +
|
||||||
"EXPOSE_UDP\x10\x03\x12\x0e\n" +
|
"EXPOSE_UDP\x10\x03\x12\x0e\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"EXPOSE_TLS\x10\x042\xf5\x17\n" +
|
"EXPOSE_TLS\x10\x042\xaf\x17\n" +
|
||||||
"\rDaemonService\x126\n" +
|
"\rDaemonService\x126\n" +
|
||||||
"\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" +
|
"\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" +
|
||||||
"\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" +
|
"\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" +
|
||||||
"\x02Up\x12\x11.daemon.UpRequest\x1a\x12.daemon.UpResponse\"\x00\x129\n" +
|
"\x02Up\x12\x11.daemon.UpRequest\x1a\x12.daemon.UpResponse\"\x00\x129\n" +
|
||||||
"\x06Status\x12\x15.daemon.StatusRequest\x1a\x16.daemon.StatusResponse\"\x00\x12D\n" +
|
"\x06Status\x12\x15.daemon.StatusRequest\x1a\x16.daemon.StatusResponse\"\x00\x123\n" +
|
||||||
"\x0fSubscribeStatus\x12\x15.daemon.StatusRequest\x1a\x16.daemon.StatusResponse\"\x000\x01\x123\n" +
|
|
||||||
"\x04Down\x12\x13.daemon.DownRequest\x1a\x14.daemon.DownResponse\"\x00\x12B\n" +
|
"\x04Down\x12\x13.daemon.DownRequest\x1a\x14.daemon.DownResponse\"\x00\x12B\n" +
|
||||||
"\tGetConfig\x12\x18.daemon.GetConfigRequest\x1a\x19.daemon.GetConfigResponse\"\x00\x12K\n" +
|
"\tGetConfig\x12\x18.daemon.GetConfigRequest\x1a\x19.daemon.GetConfigResponse\"\x00\x12K\n" +
|
||||||
"\fListNetworks\x12\x1b.daemon.ListNetworksRequest\x1a\x1c.daemon.ListNetworksResponse\"\x00\x12Q\n" +
|
"\fListNetworks\x12\x1b.daemon.ListNetworksRequest\x1a\x1c.daemon.ListNetworksResponse\"\x00\x12Q\n" +
|
||||||
@@ -6980,84 +6979,82 @@ var file_daemon_proto_depIdxs = []int32{
|
|||||||
7, // 38: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
7, // 38: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
||||||
9, // 39: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
9, // 39: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
||||||
11, // 40: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
11, // 40: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
||||||
11, // 41: daemon.DaemonService.SubscribeStatus:input_type -> daemon.StatusRequest
|
13, // 41: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
||||||
13, // 42: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
15, // 42: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
|
||||||
15, // 43: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
|
26, // 43: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest
|
||||||
26, // 44: daemon.DaemonService.ListNetworks:input_type -> daemon.ListNetworksRequest
|
28, // 44: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest
|
||||||
28, // 45: daemon.DaemonService.SelectNetworks:input_type -> daemon.SelectNetworksRequest
|
28, // 45: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest
|
||||||
28, // 46: daemon.DaemonService.DeselectNetworks:input_type -> daemon.SelectNetworksRequest
|
4, // 46: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest
|
||||||
4, // 47: daemon.DaemonService.ForwardingRules:input_type -> daemon.EmptyRequest
|
35, // 47: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest
|
||||||
35, // 48: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest
|
37, // 48: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest
|
||||||
37, // 49: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest
|
39, // 49: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest
|
||||||
39, // 50: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest
|
42, // 50: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest
|
||||||
42, // 51: daemon.DaemonService.ListStates:input_type -> daemon.ListStatesRequest
|
44, // 51: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest
|
||||||
44, // 52: daemon.DaemonService.CleanState:input_type -> daemon.CleanStateRequest
|
46, // 52: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest
|
||||||
46, // 53: daemon.DaemonService.DeleteState:input_type -> daemon.DeleteStateRequest
|
48, // 53: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest
|
||||||
48, // 54: daemon.DaemonService.SetSyncResponsePersistence:input_type -> daemon.SetSyncResponsePersistenceRequest
|
51, // 54: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest
|
||||||
51, // 55: daemon.DaemonService.TracePacket:input_type -> daemon.TracePacketRequest
|
92, // 55: daemon.DaemonService.StartCapture:input_type -> daemon.StartCaptureRequest
|
||||||
92, // 56: daemon.DaemonService.StartCapture:input_type -> daemon.StartCaptureRequest
|
94, // 56: daemon.DaemonService.StartBundleCapture:input_type -> daemon.StartBundleCaptureRequest
|
||||||
94, // 57: daemon.DaemonService.StartBundleCapture:input_type -> daemon.StartBundleCaptureRequest
|
96, // 57: daemon.DaemonService.StopBundleCapture:input_type -> daemon.StopBundleCaptureRequest
|
||||||
96, // 58: daemon.DaemonService.StopBundleCapture:input_type -> daemon.StopBundleCaptureRequest
|
54, // 58: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest
|
||||||
54, // 59: daemon.DaemonService.SubscribeEvents:input_type -> daemon.SubscribeRequest
|
56, // 59: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest
|
||||||
56, // 60: daemon.DaemonService.GetEvents:input_type -> daemon.GetEventsRequest
|
58, // 60: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest
|
||||||
58, // 61: daemon.DaemonService.SwitchProfile:input_type -> daemon.SwitchProfileRequest
|
60, // 61: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest
|
||||||
60, // 62: daemon.DaemonService.SetConfig:input_type -> daemon.SetConfigRequest
|
62, // 62: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest
|
||||||
62, // 63: daemon.DaemonService.AddProfile:input_type -> daemon.AddProfileRequest
|
64, // 63: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest
|
||||||
64, // 64: daemon.DaemonService.RemoveProfile:input_type -> daemon.RemoveProfileRequest
|
66, // 64: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest
|
||||||
66, // 65: daemon.DaemonService.ListProfiles:input_type -> daemon.ListProfilesRequest
|
69, // 65: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest
|
||||||
69, // 66: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest
|
71, // 66: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest
|
||||||
71, // 67: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest
|
73, // 67: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest
|
||||||
73, // 68: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest
|
75, // 68: daemon.DaemonService.TriggerUpdate:input_type -> daemon.TriggerUpdateRequest
|
||||||
75, // 69: daemon.DaemonService.TriggerUpdate:input_type -> daemon.TriggerUpdateRequest
|
77, // 69: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest
|
||||||
77, // 70: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest
|
79, // 70: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest
|
||||||
79, // 71: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest
|
81, // 71: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest
|
||||||
81, // 72: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest
|
83, // 72: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest
|
||||||
83, // 73: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest
|
85, // 73: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
|
||||||
85, // 74: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
|
87, // 74: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
|
||||||
87, // 75: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
|
89, // 75: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest
|
||||||
89, // 76: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest
|
6, // 76: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
||||||
6, // 77: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
8, // 77: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
||||||
8, // 78: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
10, // 78: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
||||||
10, // 79: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
12, // 79: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
||||||
12, // 80: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
14, // 80: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
||||||
12, // 81: daemon.DaemonService.SubscribeStatus:output_type -> daemon.StatusResponse
|
16, // 81: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
||||||
14, // 82: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
27, // 82: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
|
||||||
16, // 83: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
29, // 83: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||||
27, // 84: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
|
29, // 84: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||||
29, // 85: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
|
34, // 85: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
|
||||||
29, // 86: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
|
36, // 86: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
|
||||||
34, // 87: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
|
38, // 87: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
|
||||||
36, // 88: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
|
40, // 88: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
|
||||||
38, // 89: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
|
43, // 89: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
|
||||||
40, // 90: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
|
45, // 90: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
|
||||||
43, // 91: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
|
47, // 91: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
|
||||||
45, // 92: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
|
49, // 92: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
|
||||||
47, // 93: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
|
53, // 93: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
|
||||||
49, // 94: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
|
93, // 94: daemon.DaemonService.StartCapture:output_type -> daemon.CapturePacket
|
||||||
53, // 95: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
|
95, // 95: daemon.DaemonService.StartBundleCapture:output_type -> daemon.StartBundleCaptureResponse
|
||||||
93, // 96: daemon.DaemonService.StartCapture:output_type -> daemon.CapturePacket
|
97, // 96: daemon.DaemonService.StopBundleCapture:output_type -> daemon.StopBundleCaptureResponse
|
||||||
95, // 97: daemon.DaemonService.StartBundleCapture:output_type -> daemon.StartBundleCaptureResponse
|
55, // 97: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
|
||||||
97, // 98: daemon.DaemonService.StopBundleCapture:output_type -> daemon.StopBundleCaptureResponse
|
57, // 98: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
|
||||||
55, // 99: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
|
59, // 99: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
|
||||||
57, // 100: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
|
61, // 100: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
|
||||||
59, // 101: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
|
63, // 101: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
|
||||||
61, // 102: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
|
65, // 102: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
|
||||||
63, // 103: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
|
67, // 103: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
|
||||||
65, // 104: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
|
70, // 104: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
|
||||||
67, // 105: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
|
72, // 105: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
|
||||||
70, // 106: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
|
74, // 106: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
|
||||||
72, // 107: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
|
76, // 107: daemon.DaemonService.TriggerUpdate:output_type -> daemon.TriggerUpdateResponse
|
||||||
74, // 108: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
|
78, // 108: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
|
||||||
76, // 109: daemon.DaemonService.TriggerUpdate:output_type -> daemon.TriggerUpdateResponse
|
80, // 109: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
|
||||||
78, // 110: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
|
82, // 110: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
|
||||||
80, // 111: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
|
84, // 111: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
|
||||||
82, // 112: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
|
86, // 112: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
|
||||||
84, // 113: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
|
88, // 113: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
|
||||||
86, // 114: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
|
90, // 114: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent
|
||||||
88, // 115: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
|
76, // [76:115] is the sub-list for method output_type
|
||||||
90, // 116: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent
|
37, // [37:76] is the sub-list for method input_type
|
||||||
77, // [77:117] is the sub-list for method output_type
|
|
||||||
37, // [37:77] is the sub-list for method input_type
|
|
||||||
37, // [37:37] is the sub-list for extension type_name
|
37, // [37:37] is the sub-list for extension type_name
|
||||||
37, // [37:37] is the sub-list for extension extendee
|
37, // [37:37] is the sub-list for extension extendee
|
||||||
0, // [0:37] is the sub-list for field type_name
|
0, // [0:37] is the sub-list for field type_name
|
||||||
|
|||||||
@@ -24,12 +24,6 @@ service DaemonService {
|
|||||||
// Status of the service.
|
// Status of the service.
|
||||||
rpc Status(StatusRequest) returns (StatusResponse) {}
|
rpc Status(StatusRequest) returns (StatusResponse) {}
|
||||||
|
|
||||||
// SubscribeStatus pushes a fresh StatusResponse on connection state
|
|
||||||
// changes (Connected / Disconnected / Connecting / address change /
|
|
||||||
// peers list change). The first message on the stream is the current
|
|
||||||
// snapshot, so a freshly-subscribed UI doesn't need to also call Status.
|
|
||||||
rpc SubscribeStatus(StatusRequest) returns (stream StatusResponse) {}
|
|
||||||
|
|
||||||
// Down stops engine work in the daemon.
|
// Down stops engine work in the daemon.
|
||||||
rpc Down(DownRequest) returns (DownResponse) {}
|
rpc Down(DownRequest) returns (DownResponse) {}
|
||||||
|
|
||||||
|
|||||||
@@ -258,15 +258,6 @@ func (s *Server) connectWithRetryRuns(ctx context.Context, profileConfig *profil
|
|||||||
runOperation := func() error {
|
runOperation := func() error {
|
||||||
err := s.connect(ctx, profileConfig, statusRecorder, runningChan)
|
err := s.connect(ctx, profileConfig, statusRecorder, runningChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// PermissionDenied means the daemon transitioned to NeedsLogin
|
|
||||||
// inside connect(). Without backoff.Permanent the outer retry
|
|
||||||
// re-enters connect(), which resets the state to Connecting and
|
|
||||||
// makes the tray flicker between NeedsLogin and Connecting until
|
|
||||||
// the user logs in. Stop retrying and let the state stick.
|
|
||||||
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.PermissionDenied {
|
|
||||||
log.Debugf("run client connection exited with PermissionDenied, waiting for login")
|
|
||||||
return backoff.Permanent(err)
|
|
||||||
}
|
|
||||||
log.Debugf("run client connection exited with error: %v. Will retry in the background", err)
|
log.Debugf("run client connection exited with error: %v. Will retry in the background", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -350,7 +341,9 @@ func (s *Server) SetConfig(callerCtx context.Context, msg *proto.SetConfigReques
|
|||||||
}
|
}
|
||||||
|
|
||||||
if msg.OptionalPreSharedKey != nil {
|
if msg.OptionalPreSharedKey != nil {
|
||||||
config.PreSharedKey = msg.OptionalPreSharedKey
|
if *msg.OptionalPreSharedKey != "" {
|
||||||
|
config.PreSharedKey = msg.OptionalPreSharedKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.CleanDNSLabels {
|
if msg.CleanDNSLabels {
|
||||||
@@ -1116,13 +1109,6 @@ func (s *Server) Status(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.buildStatusResponse(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildStatusResponse composes a StatusResponse from the current daemon
|
|
||||||
// state. Shared between the unary Status RPC and the SubscribeStatus
|
|
||||||
// stream so both paths return identical snapshots.
|
|
||||||
func (s *Server) buildStatusResponse(msg *proto.StatusRequest) (*proto.StatusResponse, error) {
|
|
||||||
status, err := internal.CtxGetState(s.rootCtx).Status()
|
status, err := internal.CtxGetState(s.rootCtx).Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SubscribeStatus pushes a fresh StatusResponse on every connection state
|
|
||||||
// change. The first message is the current snapshot, so a re-subscribing
|
|
||||||
// client doesn't need to also call Status. Subsequent messages fire when
|
|
||||||
// the peer recorder reports any of: connected/disconnected/connecting,
|
|
||||||
// management or signal flip, address change, or peers list change.
|
|
||||||
//
|
|
||||||
// The change channel coalesces bursts to a single tick. If the consumer
|
|
||||||
// is slow the daemon drops extras (not blocks), and the next snapshot
|
|
||||||
// the consumer pulls already reflects everything.
|
|
||||||
func (s *Server) SubscribeStatus(req *proto.StatusRequest, stream proto.DaemonService_SubscribeStatusServer) error {
|
|
||||||
subID, ch := s.statusRecorder.SubscribeToStateChanges()
|
|
||||||
defer func() {
|
|
||||||
s.statusRecorder.UnsubscribeFromStateChanges(subID)
|
|
||||||
log.Debug("client unsubscribed from status updates")
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Debug("client subscribed to status updates")
|
|
||||||
|
|
||||||
if err := s.sendStatusSnapshot(req, stream); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case _, ok := <-ch:
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := s.sendStatusSnapshot(req, stream); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-stream.Context().Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) sendStatusSnapshot(req *proto.StatusRequest, stream proto.DaemonService_SubscribeStatusServer) error {
|
|
||||||
resp, err := s.buildStatusResponse(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("build status snapshot for stream: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := stream.Send(resp); err != nil {
|
|
||||||
log.Warnf("send status snapshot to stream: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
8
client/ui/.gitignore
vendored
@@ -1,8 +0,0 @@
|
|||||||
.task
|
|
||||||
bin
|
|
||||||
frontend/dist
|
|
||||||
frontend/node_modules
|
|
||||||
frontend/bindings
|
|
||||||
frontend/.vite
|
|
||||||
build/linux/appimage/build
|
|
||||||
build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
|
|
||||||
BIN
client/ui/Netbird.icns
Normal file
@@ -1,100 +0,0 @@
|
|||||||
# NetBird desktop UI (Wails3 + React)
|
|
||||||
|
|
||||||
Replaces `client/ui` (Fyne). One binary on Windows / macOS / Linux,
|
|
||||||
talks to the NetBird daemon over gRPC, renders a React frontend in a
|
|
||||||
WebView.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Go ≥ 1.25, Node ≥ 20, **pnpm** (`corepack enable && corepack prepare pnpm@latest --activate`)
|
|
||||||
- `wails3` CLI: `go install github.com/wailsapp/wails/v3/cmd/wails3@latest`
|
|
||||||
- `task`: `go install github.com/go-task/task/v3/cmd/task@latest`
|
|
||||||
- A running NetBird daemon (default: `unix:///var/run/netbird.sock`,
|
|
||||||
Windows `tcp://127.0.0.1:41731`)
|
|
||||||
- Linux only: `libwebkit2gtk-4.1-dev`, `libgtk-3-dev`,
|
|
||||||
`libayatana-appindicator3-dev`
|
|
||||||
|
|
||||||
## Develop without rebuilding
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd client/ui
|
|
||||||
task dev
|
|
||||||
```
|
|
||||||
|
|
||||||
`task dev` runs Vite (port 9245) + the Go binary + a `*.go` watcher.
|
|
||||||
Frontend edits hot-reload instantly. Go edits trigger a rebuild and
|
|
||||||
relaunch. Pass daemon flags after `--`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
task dev -- --daemon-addr=tcp://127.0.0.1:41731
|
|
||||||
```
|
|
||||||
|
|
||||||
For pure UI work (no native window, fastest loop):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend && pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Production build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
task build
|
|
||||||
```
|
|
||||||
|
|
||||||
Output in `bin/`. Frontend assets are embedded into the binary.
|
|
||||||
|
|
||||||
### Cross-compile Windows from Linux
|
|
||||||
|
|
||||||
Install the mingw-w64 toolchain once:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install gcc-mingw-w64-x86-64 # Debian/Ubuntu
|
|
||||||
sudo dnf install mingw64-gcc # Fedora
|
|
||||||
sudo pacman -S mingw-w64-gcc # Arch
|
|
||||||
```
|
|
||||||
|
|
||||||
Then:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
CGO_ENABLED=1 task windows:build
|
|
||||||
```
|
|
||||||
|
|
||||||
Produces `bin/netbird-ui.exe`. macOS cross-compile from Linux is not
|
|
||||||
supported (signing and notarization need a real Mac).
|
|
||||||
|
|
||||||
### Windows console build (logs in the terminal)
|
|
||||||
|
|
||||||
Default `windows:build` links the binary as a Windows GUI app, which
|
|
||||||
detaches from the launching console — `logrus` output, `fmt.Println`,
|
|
||||||
and panics go nowhere visible. To debug tray/event/daemon issues:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
CGO_ENABLED=1 task windows:build:console
|
|
||||||
```
|
|
||||||
|
|
||||||
Produces `bin/netbird-ui-console.exe`. Run it from `cmd.exe` /
|
|
||||||
PowerShell / Windows Terminal and stdout/stderr land in that
|
|
||||||
terminal. Same flag works on a native Windows build (drop the
|
|
||||||
`CGO_ENABLED=1` if your toolchain already has it set).
|
|
||||||
|
|
||||||
## Regenerating bindings
|
|
||||||
|
|
||||||
When a Go service signature changes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wails3 generate bindings
|
|
||||||
```
|
|
||||||
|
|
||||||
`task dev` does this automatically on `*.go` save.
|
|
||||||
|
|
||||||
## Tray icons
|
|
||||||
|
|
||||||
Source SVGs live in `assets/svg/` (state.svg + state-macos.svg). After editing
|
|
||||||
any SVG, rasterize to the PNGs the Go side embeds:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
task common:generate:tray:icons
|
|
||||||
```
|
|
||||||
|
|
||||||
Requires Inkscape. Commit the resulting `assets/*.png` files alongside the
|
|
||||||
SVG change so CI doesn't need Inkscape installed.
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
common: ./build/Taskfile.yml
|
|
||||||
windows: ./build/windows/Taskfile.yml
|
|
||||||
darwin: ./build/darwin/Taskfile.yml
|
|
||||||
linux: ./build/linux/Taskfile.yml
|
|
||||||
|
|
||||||
vars:
|
|
||||||
APP_NAME: "netbird-ui"
|
|
||||||
BIN_DIR: "bin"
|
|
||||||
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
summary: Builds the application
|
|
||||||
cmds:
|
|
||||||
- task: "{{OS}}:build"
|
|
||||||
|
|
||||||
package:
|
|
||||||
summary: Packages a production build of the application
|
|
||||||
cmds:
|
|
||||||
- task: "{{OS}}:package"
|
|
||||||
|
|
||||||
run:
|
|
||||||
summary: Runs the application
|
|
||||||
cmds:
|
|
||||||
- task: "{{OS}}:run"
|
|
||||||
|
|
||||||
dev:
|
|
||||||
summary: Runs the application in development mode
|
|
||||||
cmds:
|
|
||||||
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
|
|
||||||
|
|
||||||
setup:docker:
|
|
||||||
summary: Builds Docker image for cross-compilation (~800MB download)
|
|
||||||
cmds:
|
|
||||||
- task: common:setup:docker
|
|
||||||
|
|
||||||
build:server:
|
|
||||||
summary: Builds the application in server mode (no GUI, HTTP server only)
|
|
||||||
cmds:
|
|
||||||
- task: common:build:server
|
|
||||||
|
|
||||||
run:server:
|
|
||||||
summary: Runs the application in server mode
|
|
||||||
cmds:
|
|
||||||
- task: common:run:server
|
|
||||||
|
|
||||||
build:docker:
|
|
||||||
summary: Builds a Docker image for server mode deployment
|
|
||||||
cmds:
|
|
||||||
- task: common:build:docker
|
|
||||||
|
|
||||||
run:docker:
|
|
||||||
summary: Builds and runs the Docker image
|
|
||||||
cmds:
|
|
||||||
- task: common:run:docker
|
|
||||||
BIN
client/ui/assets/connected.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
client/ui/assets/disconnected.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
client/ui/assets/netbird-disconnected.ico
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
client/ui/assets/netbird-disconnected.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
client/ui/assets/netbird-systemtray-connected-dark.ico
Normal file
|
After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.8 KiB |
BIN
client/ui/assets/netbird-systemtray-connected.ico
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
client/ui/assets/netbird-systemtray-connecting-dark.ico
Normal file
|
After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.8 KiB |
BIN
client/ui/assets/netbird-systemtray-connecting.ico
Normal file
|
After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
client/ui/assets/netbird-systemtray-disconnected.ico
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
client/ui/assets/netbird-systemtray-error-dark.ico
Normal file
|
After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
client/ui/assets/netbird-systemtray-error.ico
Normal file
|
After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
BIN
client/ui/assets/netbird-systemtray-update-connected-dark.ico
Normal file
|
After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
client/ui/assets/netbird-systemtray-update-connected.ico
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
client/ui/assets/netbird-systemtray-update-disconnected-dark.ico
Normal file
|
After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
client/ui/assets/netbird-systemtray-update-disconnected.ico
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
client/ui/assets/netbird.ico
Normal file
|
After Width: | Height: | Size: 104 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
|
||||||
<g transform="translate(0.5 4.5) scale(1.0)" opacity="0.7">
|
|
||||||
<path d="M21.4631 0.523438C17.8173 0.857913 16.0028 2.95675 15.3171 4.01871L4.66406 22.4734H17.5163L30.1929 0.523438H21.4631Z" fill="#F68330"/>
|
|
||||||
<path d="M17.5265 22.4737L0 3.88525C0 3.88525 19.8177 -1.44128 21.7493 15.1738L17.5265 22.4737Z" fill="#F68330"/>
|
|
||||||
<path d="M14.9236 4.70563L9.54688 14.0208L17.5158 22.4747L21.7385 15.158C21.0696 9.44682 18.2851 6.32784 14.9236 4.69727" fill="#F05252"/>
|
|
||||||
</g>
|
|
||||||
<circle cx="25" cy="25" r="7" fill="white"/>
|
|
||||||
<path d="M 22.6 24.5 v -1.4 a 2.4 2.4 0 0 1 4.8 0 v 1.4" fill="none" stroke="#D97706" stroke-width="1.5" stroke-linecap="round"/>
|
|
||||||
<rect x="21.6" y="24.5" width="6.8" height="4.9" rx="0.9" fill="none" stroke="#D97706" stroke-width="1.5"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 847 B |
@@ -1,295 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
go:mod:tidy:
|
|
||||||
summary: Runs `go mod tidy`
|
|
||||||
internal: true
|
|
||||||
cmds:
|
|
||||||
- go mod tidy
|
|
||||||
|
|
||||||
install:frontend:deps:
|
|
||||||
summary: Install frontend dependencies
|
|
||||||
dir: frontend
|
|
||||||
sources:
|
|
||||||
- package.json
|
|
||||||
- pnpm-lock.yaml
|
|
||||||
generates:
|
|
||||||
- node_modules
|
|
||||||
preconditions:
|
|
||||||
- sh: pnpm --version
|
|
||||||
msg: "Looks like pnpm isn't installed. Install with: corepack enable && corepack prepare pnpm@latest --activate"
|
|
||||||
cmds:
|
|
||||||
- pnpm install
|
|
||||||
|
|
||||||
build:frontend:
|
|
||||||
label: build:frontend (DEV={{.DEV}})
|
|
||||||
summary: Build the frontend project
|
|
||||||
dir: frontend
|
|
||||||
sources:
|
|
||||||
- "**/*"
|
|
||||||
- exclude: node_modules/**/*
|
|
||||||
generates:
|
|
||||||
- dist/**/*
|
|
||||||
deps:
|
|
||||||
- task: install:frontend:deps
|
|
||||||
- task: generate:bindings
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
cmds:
|
|
||||||
- pnpm run {{.BUILD_COMMAND}}
|
|
||||||
env:
|
|
||||||
PRODUCTION: '{{if eq .DEV "true"}}false{{else}}true{{end}}'
|
|
||||||
vars:
|
|
||||||
BUILD_COMMAND: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}'
|
|
||||||
|
|
||||||
|
|
||||||
frontend:vendor:puppertino:
|
|
||||||
summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling
|
|
||||||
sources:
|
|
||||||
- frontend/public/puppertino/puppertino.css
|
|
||||||
generates:
|
|
||||||
- frontend/public/puppertino/puppertino.css
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
set -euo pipefail
|
|
||||||
mkdir -p frontend/public/puppertino
|
|
||||||
# If bundled Puppertino exists, prefer it. Otherwise, try to fetch, but don't fail build on error.
|
|
||||||
if [ ! -f frontend/public/puppertino/puppertino.css ]; then
|
|
||||||
echo "No bundled Puppertino found. Attempting to fetch from GitHub..."
|
|
||||||
if curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/dist/css/full.css -o frontend/public/puppertino/puppertino.css; then
|
|
||||||
curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/LICENSE -o frontend/public/puppertino/LICENSE || true
|
|
||||||
echo "Puppertino CSS downloaded to frontend/public/puppertino/puppertino.css"
|
|
||||||
else
|
|
||||||
echo "Warning: Could not fetch Puppertino CSS. Proceeding without download since template may bundle it."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Using bundled Puppertino at frontend/public/puppertino/puppertino.css"
|
|
||||||
fi
|
|
||||||
# Ensure index.html includes Puppertino CSS and button classes
|
|
||||||
INDEX_HTML=frontend/index.html
|
|
||||||
if [ -f "$INDEX_HTML" ]; then
|
|
||||||
if ! grep -q 'href="/puppertino/puppertino.css"' "$INDEX_HTML"; then
|
|
||||||
# Insert Puppertino link tag after style.css link
|
|
||||||
awk '
|
|
||||||
/href="\/style.css"\/?/ && !x { print; print " <link rel=\"stylesheet\" href=\"/puppertino/puppertino.css\"/>"; x=1; next }1
|
|
||||||
' "$INDEX_HTML" > "$INDEX_HTML.tmp" && mv "$INDEX_HTML.tmp" "$INDEX_HTML"
|
|
||||||
fi
|
|
||||||
# Replace default .btn with Puppertino primary button classes if present
|
|
||||||
sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
generate:bindings:
|
|
||||||
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
|
|
||||||
summary: Generates bindings for the frontend
|
|
||||||
deps:
|
|
||||||
- task: go:mod:tidy
|
|
||||||
sources:
|
|
||||||
- "**/*.[jt]s"
|
|
||||||
- exclude: frontend/**/*
|
|
||||||
- frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output
|
|
||||||
- "**/*.go"
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
generates:
|
|
||||||
- frontend/bindings/**/*
|
|
||||||
cmds:
|
|
||||||
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts
|
|
||||||
|
|
||||||
generate:icons:
|
|
||||||
summary: Generates Windows `.ico` and Mac `.icns` from an image; on macOS, `-iconcomposerinput appicon.icon -macassetdir darwin` also produces `Assets.car` from a `.icon` file (skipped on other platforms).
|
|
||||||
dir: build
|
|
||||||
sources:
|
|
||||||
- "appicon.png"
|
|
||||||
- "appicon.icon"
|
|
||||||
generates:
|
|
||||||
- "darwin/icons.icns"
|
|
||||||
- "windows/icon.ico"
|
|
||||||
cmds:
|
|
||||||
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico -iconcomposerinput appicon.icon -macassetdir darwin
|
|
||||||
|
|
||||||
generate:tray:icons:
|
|
||||||
summary: Rebuild Windows multi-res .ico files from the per-state PNGs.
|
|
||||||
desc: |
|
|
||||||
The colored tray PNGs (assets/netbird-systemtray-<state>.png) and the
|
|
||||||
macOS template variants are committed to the repo as the canonical
|
|
||||||
source. This task only regenerates the Windows multi-resolution .ico
|
|
||||||
files from those PNGs by downscaling each to 16/24/32/48 px and
|
|
||||||
packing them with icotool, so Shell_NotifyIcon picks the frame
|
|
||||||
matching the user's DPI instead of downscaling a single large PNG.
|
|
||||||
|
|
||||||
Run after replacing any of the colored PNGs (e.g. when copying a new
|
|
||||||
version of the icons from client/ui/assets). The SVG sources in
|
|
||||||
assets/svg/ are kept for reference but are not built by default.
|
|
||||||
dir: assets
|
|
||||||
sources:
|
|
||||||
- "netbird-systemtray-connected.png"
|
|
||||||
- "netbird-systemtray-disconnected.png"
|
|
||||||
- "netbird-systemtray-connecting.png"
|
|
||||||
- "netbird-systemtray-error.png"
|
|
||||||
- "netbird-systemtray-update-connected.png"
|
|
||||||
- "netbird-systemtray-update-disconnected.png"
|
|
||||||
generates:
|
|
||||||
- "netbird-systemtray-*.ico"
|
|
||||||
preconditions:
|
|
||||||
- sh: command -v magick >/dev/null 2>&1 || command -v convert >/dev/null 2>&1
|
|
||||||
msg: "ImageMagick is required to downscale PNGs (apt install imagemagick)"
|
|
||||||
- sh: command -v icotool >/dev/null 2>&1
|
|
||||||
msg: "icotool is required to pack tray .ico files (apt install icoutils)"
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
set -euo pipefail
|
|
||||||
tmp=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$tmp"' EXIT
|
|
||||||
resize=$(command -v magick || echo convert)
|
|
||||||
for state in connected disconnected connecting error update-connected update-disconnected; do
|
|
||||||
for sz in 16 24 32 48; do
|
|
||||||
"$resize" "netbird-systemtray-$state.png" -resize ${sz}x${sz} "$tmp/$state-$sz.png"
|
|
||||||
done
|
|
||||||
icotool -c -o "netbird-systemtray-$state.ico" \
|
|
||||||
"$tmp/$state-16.png" "$tmp/$state-24.png" "$tmp/$state-32.png" "$tmp/$state-48.png"
|
|
||||||
done
|
|
||||||
|
|
||||||
dev:frontend:
|
|
||||||
summary: Runs the frontend in development mode
|
|
||||||
dir: frontend
|
|
||||||
deps:
|
|
||||||
- task: install:frontend:deps
|
|
||||||
cmds:
|
|
||||||
- pnpm exec vite --port {{.VITE_PORT}} --strictPort
|
|
||||||
|
|
||||||
update:build-assets:
|
|
||||||
summary: Updates the build assets
|
|
||||||
dir: build
|
|
||||||
cmds:
|
|
||||||
- wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir .
|
|
||||||
|
|
||||||
build:server:
|
|
||||||
summary: Builds the application in server mode (no GUI, HTTP server only)
|
|
||||||
desc: |
|
|
||||||
Builds the application with the server build tag enabled.
|
|
||||||
Server mode runs as a pure HTTP server without native GUI dependencies.
|
|
||||||
Usage: task build:server
|
|
||||||
deps:
|
|
||||||
- task: build:frontend
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
cmds:
|
|
||||||
- go build -tags server {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}}
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS: "{{.BUILD_FLAGS}}"
|
|
||||||
|
|
||||||
run:server:
|
|
||||||
summary: Builds and runs the application in server mode
|
|
||||||
deps:
|
|
||||||
- task: build:server
|
|
||||||
cmds:
|
|
||||||
- ./{{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}}
|
|
||||||
|
|
||||||
build:docker:
|
|
||||||
summary: Builds a Docker image for server mode deployment
|
|
||||||
desc: |
|
|
||||||
Creates a minimal Docker image containing the server mode binary.
|
|
||||||
The image is based on distroless for security and small size.
|
|
||||||
Usage: task build:docker [TAG=myapp:latest]
|
|
||||||
cmds:
|
|
||||||
- docker build -t {{.TAG | default (printf "%s:latest" .APP_NAME)}} -f build/docker/Dockerfile.server .
|
|
||||||
vars:
|
|
||||||
TAG: "{{.TAG}}"
|
|
||||||
preconditions:
|
|
||||||
- sh: docker info > /dev/null 2>&1
|
|
||||||
msg: "Docker is required. Please install Docker first."
|
|
||||||
- sh: test -f build/docker/Dockerfile.server
|
|
||||||
msg: "Dockerfile.server not found. Run 'wails3 update build-assets' to generate it."
|
|
||||||
|
|
||||||
run:docker:
|
|
||||||
summary: Builds and runs the Docker image
|
|
||||||
desc: |
|
|
||||||
Builds the Docker image and runs it, exposing port 8080.
|
|
||||||
Usage: task run:docker [TAG=myapp:latest] [PORT=8080]
|
|
||||||
Note: The internal container port is always 8080. The PORT variable
|
|
||||||
only changes the host port mapping. Ensure your app uses port 8080
|
|
||||||
or modify the Dockerfile to match your ServerOptions.Port setting.
|
|
||||||
deps:
|
|
||||||
- task: build:docker
|
|
||||||
vars:
|
|
||||||
TAG:
|
|
||||||
ref: .TAG
|
|
||||||
cmds:
|
|
||||||
- docker run --rm -p {{.PORT | default "8080"}}:8080 {{.TAG | default (printf "%s:latest" .APP_NAME)}}
|
|
||||||
vars:
|
|
||||||
TAG: "{{.TAG}}"
|
|
||||||
PORT: "{{.PORT}}"
|
|
||||||
|
|
||||||
setup:docker:
|
|
||||||
summary: Builds Docker image for cross-compilation (~800MB download)
|
|
||||||
desc: |
|
|
||||||
Builds the Docker image needed for cross-compiling to any platform.
|
|
||||||
Run this once to enable cross-platform builds from any OS.
|
|
||||||
cmds:
|
|
||||||
- docker build -t wails-cross -f build/docker/Dockerfile.cross build/docker/
|
|
||||||
preconditions:
|
|
||||||
- sh: docker info > /dev/null 2>&1
|
|
||||||
msg: "Docker is required. Please install Docker first."
|
|
||||||
|
|
||||||
ios:device:list:
|
|
||||||
summary: Lists connected iOS devices (UDIDs)
|
|
||||||
cmds:
|
|
||||||
- xcrun xcdevice list
|
|
||||||
|
|
||||||
ios:run:device:
|
|
||||||
summary: Build, install, and launch on a physical iPhone using Apple tools (xcodebuild/devicectl)
|
|
||||||
vars:
|
|
||||||
PROJECT: '{{.PROJECT}}' # e.g., build/ios/xcode/<YourProject>.xcodeproj
|
|
||||||
SCHEME: '{{.SCHEME}}' # e.g., ios.dev
|
|
||||||
CONFIG: '{{.CONFIG | default "Debug"}}'
|
|
||||||
DERIVED: '{{.DERIVED | default "build/ios/DerivedData"}}'
|
|
||||||
UDID: '{{.UDID}}' # from `task ios:device:list`
|
|
||||||
BUNDLE_ID: '{{.BUNDLE_ID}}' # e.g., com.yourco.wails.ios.dev
|
|
||||||
TEAM_ID: '{{.TEAM_ID}}' # optional, if your project is not already set up for signing
|
|
||||||
preconditions:
|
|
||||||
- sh: xcrun -f xcodebuild
|
|
||||||
msg: "xcodebuild not found. Please install Xcode."
|
|
||||||
- sh: xcrun -f devicectl
|
|
||||||
msg: "devicectl not found. Please update to Xcode 15+ (which includes devicectl)."
|
|
||||||
- sh: test -n '{{.PROJECT}}'
|
|
||||||
msg: "Set PROJECT to your .xcodeproj path (e.g., PROJECT=build/ios/xcode/App.xcodeproj)."
|
|
||||||
- sh: test -n '{{.SCHEME}}'
|
|
||||||
msg: "Set SCHEME to your app scheme (e.g., SCHEME=ios.dev)."
|
|
||||||
- sh: test -n '{{.UDID}}'
|
|
||||||
msg: "Set UDID to your device UDID (see: task ios:device:list)."
|
|
||||||
- sh: test -n '{{.BUNDLE_ID}}'
|
|
||||||
msg: "Set BUNDLE_ID to your app's bundle identifier (e.g., com.yourco.wails.ios.dev)."
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
set -euo pipefail
|
|
||||||
echo "Building for device: UDID={{.UDID}} SCHEME={{.SCHEME}} PROJECT={{.PROJECT}}"
|
|
||||||
XCB_ARGS=(
|
|
||||||
-project "{{.PROJECT}}"
|
|
||||||
-scheme "{{.SCHEME}}"
|
|
||||||
-configuration "{{.CONFIG}}"
|
|
||||||
-destination "id={{.UDID}}"
|
|
||||||
-derivedDataPath "{{.DERIVED}}"
|
|
||||||
-allowProvisioningUpdates
|
|
||||||
-allowProvisioningDeviceRegistration
|
|
||||||
)
|
|
||||||
# Optionally inject signing identifiers if provided
|
|
||||||
if [ -n '{{.TEAM_ID}}' ]; then XCB_ARGS+=(DEVELOPMENT_TEAM={{.TEAM_ID}}); fi
|
|
||||||
if [ -n '{{.BUNDLE_ID}}' ]; then XCB_ARGS+=(PRODUCT_BUNDLE_IDENTIFIER={{.BUNDLE_ID}}); fi
|
|
||||||
xcodebuild "${XCB_ARGS[@]}" build | xcpretty || true
|
|
||||||
# If xcpretty isn't installed, run without it
|
|
||||||
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
|
|
||||||
xcodebuild "${XCB_ARGS[@]}" build
|
|
||||||
fi
|
|
||||||
# Find built .app
|
|
||||||
APP_PATH=$(find "{{.DERIVED}}/Build/Products" -type d -name "*.app" -maxdepth 3 | head -n 1)
|
|
||||||
if [ -z "$APP_PATH" ]; then
|
|
||||||
echo "Could not locate built .app under {{.DERIVED}}/Build/Products" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Installing: $APP_PATH"
|
|
||||||
xcrun devicectl device install app --device "{{.UDID}}" "$APP_PATH"
|
|
||||||
echo "Launching: {{.BUNDLE_ID}}"
|
|
||||||
xcrun devicectl device process launch --device "{{.UDID}}" --stderr console --stdout console "{{.BUNDLE_ID}}"
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!--
|
|
||||||
macOS Icon Composer source. Designed on a 1024x1024 canvas with the bird
|
|
||||||
glyph centered and sized to ~75% of canvas width, leaving padding for
|
|
||||||
the system squircle treatment.
|
|
||||||
-->
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
|
||||||
<g transform="translate(128, 227) scale(24.77)">
|
|
||||||
<path d="M21.4631 0.523438C17.8173 0.857913 16.0028 2.95675 15.3171 4.01871L4.66406 22.4734H17.5163L30.1929 0.523438H21.4631Z" fill="#F68330"/>
|
|
||||||
<path d="M17.5265 22.4737L0 3.88525C0 3.88525 19.8177 -1.44128 21.7493 15.1738L17.5265 22.4737Z" fill="#F68330"/>
|
|
||||||
<path d="M14.9236 4.70563L9.54688 14.0208L17.5158 22.4747L21.7385 15.158C21.0696 9.44682 18.2851 6.32784 14.9236 4.69727" fill="#F05252"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 810 B |
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"fill" : {
|
|
||||||
"solid" : "srgb:1.00000,1.00000,1.00000,1.00000"
|
|
||||||
},
|
|
||||||
"groups" : [
|
|
||||||
{
|
|
||||||
"layers" : [
|
|
||||||
{
|
|
||||||
"image-name" : "wails_icon_vector.svg",
|
|
||||||
"name" : "wails_icon_vector"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"shadow" : {
|
|
||||||
"kind" : "neutral",
|
|
||||||
"opacity" : 0.5
|
|
||||||
},
|
|
||||||
"specular" : true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"supported-platforms" : {
|
|
||||||
"circles" : [
|
|
||||||
"watchOS"
|
|
||||||
],
|
|
||||||
"squares" : "shared"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 32 KiB |
BIN
client/ui/build/banner.bmp
Normal file
|
After Width: | Height: | Size: 26 KiB |
5
client/ui/build/build-ui-linux.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt remove gir1.2-appindicator3-0.1
|
||||||
|
sudo apt install -y libayatana-appindicator3-dev
|
||||||
|
go build
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
# This file contains the configuration for this project.
|
|
||||||
# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets.
|
|
||||||
# Note that this will overwrite any changes you have made to the assets.
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
# This information is used to generate the build assets.
|
|
||||||
info:
|
|
||||||
companyName: "My Company" # The name of the company
|
|
||||||
productName: "My Product" # The name of the application
|
|
||||||
productIdentifier: "com.mycompany.myproduct" # The unique product identifier
|
|
||||||
description: "A program that does X" # The application description
|
|
||||||
copyright: "(c) 2025, My Company" # Copyright text
|
|
||||||
comments: "Some Product Comments" # Comments
|
|
||||||
version: "0.0.1" # The application version
|
|
||||||
# cfBundleIconName: "appicon" # The macOS icon name in Assets.car icon bundles (optional)
|
|
||||||
# # Should match the name of your .icon file without the extension
|
|
||||||
# # If not set and Assets.car exists, defaults to "appicon"
|
|
||||||
|
|
||||||
# iOS build configuration (uncomment to customise iOS project generation)
|
|
||||||
# Note: Keys under `ios` OVERRIDE values under `info` when set.
|
|
||||||
# ios:
|
|
||||||
# # The iOS bundle identifier used in the generated Xcode project (CFBundleIdentifier)
|
|
||||||
# bundleID: "com.mycompany.myproduct"
|
|
||||||
# # The display name shown under the app icon (CFBundleDisplayName/CFBundleName)
|
|
||||||
# displayName: "My Product"
|
|
||||||
# # The app version to embed in Info.plist (CFBundleShortVersionString/CFBundleVersion)
|
|
||||||
# version: "0.0.1"
|
|
||||||
# # The company/organisation name for templates and project settings
|
|
||||||
# company: "My Company"
|
|
||||||
# # Additional comments to embed in Info.plist metadata
|
|
||||||
# comments: "Some Product Comments"
|
|
||||||
|
|
||||||
# Dev mode configuration
|
|
||||||
dev_mode:
|
|
||||||
root_path: .
|
|
||||||
log_level: warn
|
|
||||||
debounce: 1000
|
|
||||||
ignore:
|
|
||||||
dir:
|
|
||||||
- .git
|
|
||||||
- node_modules
|
|
||||||
- frontend
|
|
||||||
- bin
|
|
||||||
file:
|
|
||||||
- .DS_Store
|
|
||||||
- .gitignore
|
|
||||||
- .gitkeep
|
|
||||||
watched_extension:
|
|
||||||
- "*.go"
|
|
||||||
- "*.js" # Watch for changes to JS/TS files included using the //wails:include directive.
|
|
||||||
- "*.ts" # The frontend directory will be excluded entirely by the setting above.
|
|
||||||
git_ignore: true
|
|
||||||
executes:
|
|
||||||
- cmd: wails3 build DEV=true
|
|
||||||
type: blocking
|
|
||||||
- cmd: wails3 task common:dev:frontend
|
|
||||||
type: background
|
|
||||||
- cmd: wails3 task run
|
|
||||||
type: primary
|
|
||||||
|
|
||||||
# File Associations
|
|
||||||
# More information at: https://v3.wails.io/noit/done/yet
|
|
||||||
fileAssociations:
|
|
||||||
# - ext: wails
|
|
||||||
# name: Wails
|
|
||||||
# description: Wails Application File
|
|
||||||
# iconName: wailsFileIcon
|
|
||||||
# role: Editor
|
|
||||||
# - ext: jpg
|
|
||||||
# name: JPEG
|
|
||||||
# description: Image File
|
|
||||||
# iconName: jpegFileIcon
|
|
||||||
# role: Editor
|
|
||||||
# mimeType: image/jpeg # (optional)
|
|
||||||
|
|
||||||
# Other data
|
|
||||||
other:
|
|
||||||
- name: My Other Data
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>NetBird</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>NetBird</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>netbird-ui</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>io.netbird.client</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>0.0.1</string>
|
|
||||||
<key>CFBundleGetInfoString</key>
|
|
||||||
<string>This is a comment</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>0.0.1</string>
|
|
||||||
<key>CFBundleIconFile</key>
|
|
||||||
<string>icons</string>
|
|
||||||
<key>CFBundleIconName</key>
|
|
||||||
<string>appicon</string>
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
|
||||||
<string>10.15.0</string>
|
|
||||||
<key>NSHighResolutionCapable</key>
|
|
||||||
<string>true</string>
|
|
||||||
<key>LSUIElement</key>
|
|
||||||
<string>1</string>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>© 2026, My Company</string>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSAllowsLocalNetworking</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>NetBird</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>NetBird</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>netbird-ui</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>io.netbird.client</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>0.0.1</string>
|
|
||||||
<key>CFBundleGetInfoString</key>
|
|
||||||
<string>This is a comment</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>0.0.1</string>
|
|
||||||
<key>CFBundleIconFile</key>
|
|
||||||
<string>icons</string>
|
|
||||||
<key>CFBundleIconName</key>
|
|
||||||
<string>appicon</string>
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
|
||||||
<string>10.15.0</string>
|
|
||||||
<key>NSHighResolutionCapable</key>
|
|
||||||
<string>true</string>
|
|
||||||
<!-- Accessory mode: tray-only app, no Dock entry, no Cmd-Tab
|
|
||||||
presence. Matches the legacy Fyne client and the sign-pipelines
|
|
||||||
Info.plist used in signed .pkg releases. -->
|
|
||||||
<key>LSUIElement</key>
|
|
||||||
<string>1</string>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>© 2026, My Company</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
common: ../Taskfile.yml
|
|
||||||
|
|
||||||
vars:
|
|
||||||
# Signing configuration - edit these values for your project
|
|
||||||
# SIGN_IDENTITY: "Developer ID Application: Your Company (TEAMID)"
|
|
||||||
# KEYCHAIN_PROFILE: "my-notarize-profile"
|
|
||||||
# ENTITLEMENTS: "build/darwin/entitlements.plist"
|
|
||||||
|
|
||||||
# Docker image for cross-compilation (used when building on non-macOS)
|
|
||||||
CROSS_IMAGE: wails-cross
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
summary: Builds the application
|
|
||||||
cmds:
|
|
||||||
- task: '{{if eq OS "darwin"}}build:native{{else}}build:docker{{end}}'
|
|
||||||
vars:
|
|
||||||
ARCH: '{{.ARCH}}'
|
|
||||||
DEV: '{{.DEV}}'
|
|
||||||
OUTPUT: '{{.OUTPUT}}'
|
|
||||||
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
|
|
||||||
vars:
|
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
|
||||||
|
|
||||||
build:native:
|
|
||||||
summary: Builds the application natively on macOS
|
|
||||||
internal: true
|
|
||||||
deps:
|
|
||||||
- task: common:go:mod:tidy
|
|
||||||
- task: common:build:frontend
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
DEV:
|
|
||||||
ref: .DEV
|
|
||||||
- task: common:generate:icons
|
|
||||||
cmds:
|
|
||||||
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
|
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
|
||||||
env:
|
|
||||||
GOOS: darwin
|
|
||||||
CGO_ENABLED: 1
|
|
||||||
GOARCH: '{{.ARCH | default ARCH}}'
|
|
||||||
CGO_CFLAGS: "-mmacosx-version-min=10.15"
|
|
||||||
CGO_LDFLAGS: "-mmacosx-version-min=10.15"
|
|
||||||
MACOSX_DEPLOYMENT_TARGET: "10.15"
|
|
||||||
|
|
||||||
build:docker:
|
|
||||||
summary: Cross-compiles for macOS using Docker (for Linux/Windows hosts)
|
|
||||||
internal: true
|
|
||||||
deps:
|
|
||||||
- task: common:build:frontend
|
|
||||||
- task: common:generate:icons
|
|
||||||
preconditions:
|
|
||||||
- sh: docker info > /dev/null 2>&1
|
|
||||||
msg: "Docker is required for cross-compilation. Please install Docker."
|
|
||||||
- sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1
|
|
||||||
msg: |
|
|
||||||
Docker image '{{.CROSS_IMAGE}}' not found.
|
|
||||||
Build it first: wails3 task setup:docker
|
|
||||||
cmds:
|
|
||||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{.CROSS_IMAGE}} darwin {{.DOCKER_ARCH}}
|
|
||||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
|
|
||||||
- mkdir -p {{.BIN_DIR}}
|
|
||||||
- mv "bin/{{.APP_NAME}}-darwin-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"
|
|
||||||
vars:
|
|
||||||
DOCKER_ARCH: '{{if eq .ARCH "arm64"}}arm64{{else if eq .ARCH "amd64"}}amd64{{else}}arm64{{end}}'
|
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
|
||||||
# Mount Go module cache for faster builds
|
|
||||||
GO_CACHE_MOUNT:
|
|
||||||
sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"'
|
|
||||||
# Extract replace directives from go.mod and create -v mounts for each
|
|
||||||
# Handles both relative (=> ../) and absolute (=> /) paths
|
|
||||||
REPLACE_MOUNTS:
|
|
||||||
sh: |
|
|
||||||
grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do
|
|
||||||
path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r')
|
|
||||||
# Convert relative paths to absolute
|
|
||||||
if [ "${path#/}" = "$path" ]; then
|
|
||||||
path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")"
|
|
||||||
fi
|
|
||||||
# Only mount if directory exists
|
|
||||||
if [ -d "$path" ]; then
|
|
||||||
echo "-v $path:$path:ro"
|
|
||||||
fi
|
|
||||||
done | tr '\n' ' '
|
|
||||||
|
|
||||||
build:universal:
|
|
||||||
summary: Builds darwin universal binary (arm64 + amd64)
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
vars:
|
|
||||||
ARCH: amd64
|
|
||||||
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64"
|
|
||||||
- task: build
|
|
||||||
vars:
|
|
||||||
ARCH: arm64
|
|
||||||
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
|
|
||||||
cmds:
|
|
||||||
- task: '{{if eq OS "darwin"}}build:universal:lipo:native{{else}}build:universal:lipo:go{{end}}'
|
|
||||||
|
|
||||||
build:universal:lipo:native:
|
|
||||||
summary: Creates universal binary using native lipo (macOS)
|
|
||||||
internal: true
|
|
||||||
cmds:
|
|
||||||
- lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
|
|
||||||
- rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
|
|
||||||
|
|
||||||
build:universal:lipo:go:
|
|
||||||
summary: Creates universal binary using wails3 tool lipo (Linux/Windows)
|
|
||||||
internal: true
|
|
||||||
cmds:
|
|
||||||
- wails3 tool lipo -output "{{.BIN_DIR}}/{{.APP_NAME}}" -input "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" -input "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
|
|
||||||
- rm -f "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
|
|
||||||
|
|
||||||
package:
|
|
||||||
summary: Packages the application into a `.app` bundle
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
cmds:
|
|
||||||
- task: create:app:bundle
|
|
||||||
|
|
||||||
package:universal:
|
|
||||||
summary: Packages darwin universal binary (arm64 + amd64)
|
|
||||||
deps:
|
|
||||||
- task: build:universal
|
|
||||||
cmds:
|
|
||||||
- task: create:app:bundle
|
|
||||||
|
|
||||||
|
|
||||||
create:app:bundle:
|
|
||||||
summary: Creates an `.app` bundle
|
|
||||||
cmds:
|
|
||||||
- mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS"
|
|
||||||
- mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources"
|
|
||||||
- cp build/darwin/icons.icns "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources"
|
|
||||||
- |
|
|
||||||
if [ -f build/darwin/Assets.car ]; then
|
|
||||||
cp build/darwin/Assets.car "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources"
|
|
||||||
fi
|
|
||||||
- cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS"
|
|
||||||
- cp build/darwin/Info.plist "{{.BIN_DIR}}/{{.APP_NAME}}.app/Contents"
|
|
||||||
- task: '{{if eq OS "darwin"}}codesign:adhoc{{else}}codesign:skip{{end}}'
|
|
||||||
|
|
||||||
codesign:adhoc:
|
|
||||||
summary: Ad-hoc signs the app bundle (macOS only)
|
|
||||||
internal: true
|
|
||||||
cmds:
|
|
||||||
- codesign --force --deep --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.app"
|
|
||||||
|
|
||||||
codesign:skip:
|
|
||||||
summary: Skips codesigning when cross-compiling
|
|
||||||
internal: true
|
|
||||||
cmds:
|
|
||||||
- 'echo "Skipping codesign (not available on {{OS}}). Sign the .app on macOS before distribution."'
|
|
||||||
|
|
||||||
run:
|
|
||||||
deps:
|
|
||||||
- task: common:generate:icons
|
|
||||||
cmds:
|
|
||||||
- mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS"
|
|
||||||
- mkdir -p "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources"
|
|
||||||
- cp build/darwin/icons.icns "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources"
|
|
||||||
- |
|
|
||||||
if [ -f build/darwin/Assets.car ]; then
|
|
||||||
cp build/darwin/Assets.car "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources"
|
|
||||||
fi
|
|
||||||
- cp "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS"
|
|
||||||
- cp "build/darwin/Info.dev.plist" "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist"
|
|
||||||
- codesign --force --deep --sign - "{{.BIN_DIR}}/{{.APP_NAME}}.dev.app"
|
|
||||||
- '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}'
|
|
||||||
|
|
||||||
sign:
|
|
||||||
summary: Signs the application bundle with Developer ID
|
|
||||||
desc: |
|
|
||||||
Signs the .app bundle for distribution.
|
|
||||||
Configure SIGN_IDENTITY in the vars section at the top of this file.
|
|
||||||
deps:
|
|
||||||
- task: package
|
|
||||||
cmds:
|
|
||||||
- wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.app" --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}}
|
|
||||||
preconditions:
|
|
||||||
- sh: '[ -n "{{.SIGN_IDENTITY}}" ]'
|
|
||||||
msg: "SIGN_IDENTITY is required. Set it in the vars section at the top of build/darwin/Taskfile.yml"
|
|
||||||
|
|
||||||
sign:notarize:
|
|
||||||
summary: Signs and notarizes the application bundle
|
|
||||||
desc: |
|
|
||||||
Signs the .app bundle and submits it for notarization.
|
|
||||||
Configure SIGN_IDENTITY and KEYCHAIN_PROFILE in the vars section at the top of this file.
|
|
||||||
|
|
||||||
Setup (one-time):
|
|
||||||
wails3 signing credentials --apple-id "you@email.com" --team-id "TEAMID" --password "app-specific-password" --profile "my-profile"
|
|
||||||
deps:
|
|
||||||
- task: package
|
|
||||||
cmds:
|
|
||||||
- wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.app" --identity "{{.SIGN_IDENTITY}}" {{if .ENTITLEMENTS}}--entitlements {{.ENTITLEMENTS}}{{end}} --notarize --keychain-profile {{.KEYCHAIN_PROFILE}}
|
|
||||||
preconditions:
|
|
||||||
- sh: '[ -n "{{.SIGN_IDENTITY}}" ]'
|
|
||||||
msg: "SIGN_IDENTITY is required. Set it in the vars section at the top of build/darwin/Taskfile.yml"
|
|
||||||
- sh: '[ -n "{{.KEYCHAIN_PROFILE}}" ]'
|
|
||||||
msg: "KEYCHAIN_PROFILE is required. Set it in the vars section at the top of build/darwin/Taskfile.yml"
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
# Cross-compile Wails v3 apps to any platform
|
|
||||||
#
|
|
||||||
# Darwin: Zig + macOS SDK
|
|
||||||
# Linux: Native GCC when host matches target, Zig for cross-arch
|
|
||||||
# Windows: Zig + bundled mingw
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# docker build -t wails-cross -f Dockerfile.cross .
|
|
||||||
# docker run --rm -v $(pwd):/app wails-cross darwin arm64
|
|
||||||
# docker run --rm -v $(pwd):/app wails-cross darwin amd64
|
|
||||||
# docker run --rm -v $(pwd):/app wails-cross linux amd64
|
|
||||||
# docker run --rm -v $(pwd):/app wails-cross linux arm64
|
|
||||||
# docker run --rm -v $(pwd):/app wails-cross windows amd64
|
|
||||||
# docker run --rm -v $(pwd):/app wails-cross windows arm64
|
|
||||||
|
|
||||||
FROM golang:1.25-bookworm
|
|
||||||
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
# Install base tools, GCC, and GTK/WebKit dev packages
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
curl xz-utils nodejs npm pkg-config gcc libc6-dev \
|
|
||||||
libgtk-3-dev libwebkit2gtk-4.1-dev \
|
|
||||||
libgtk-4-dev libwebkitgtk-6.0-dev \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install Zig - automatically selects correct binary for host architecture
|
|
||||||
ARG ZIG_VERSION=0.14.0
|
|
||||||
RUN ZIG_ARCH=$(case "${TARGETARCH}" in arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
|
|
||||||
curl -L "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}.tar.xz" \
|
|
||||||
| tar -xJ -C /opt \
|
|
||||||
&& ln -s /opt/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}/zig /usr/local/bin/zig
|
|
||||||
|
|
||||||
# Download macOS SDK (required for darwin targets)
|
|
||||||
ARG MACOS_SDK_VERSION=14.5
|
|
||||||
RUN curl -L "https://github.com/joseluisq/macosx-sdks/releases/download/${MACOS_SDK_VERSION}/MacOSX${MACOS_SDK_VERSION}.sdk.tar.xz" \
|
|
||||||
| tar -xJ -C /opt \
|
|
||||||
&& mv /opt/MacOSX${MACOS_SDK_VERSION}.sdk /opt/macos-sdk
|
|
||||||
|
|
||||||
ENV MACOS_SDK_PATH=/opt/macos-sdk
|
|
||||||
|
|
||||||
# Create Zig CC wrappers for cross-compilation targets
|
|
||||||
# Darwin and Windows use Zig; Linux uses native GCC (run with --platform for cross-arch)
|
|
||||||
|
|
||||||
# Darwin arm64
|
|
||||||
COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-arm64
|
|
||||||
#!/bin/sh
|
|
||||||
ARGS=""
|
|
||||||
SKIP_NEXT=0
|
|
||||||
for arg in "$@"; do
|
|
||||||
if [ $SKIP_NEXT -eq 1 ]; then
|
|
||||||
SKIP_NEXT=0
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
case "$arg" in
|
|
||||||
-target) SKIP_NEXT=1 ;;
|
|
||||||
-mmacosx-version-min=*) ;;
|
|
||||||
*) ARGS="$ARGS $arg" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
exec zig cc -fno-sanitize=all -target aarch64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS
|
|
||||||
ZIGWRAP
|
|
||||||
RUN chmod +x /usr/local/bin/zcc-darwin-arm64
|
|
||||||
|
|
||||||
# Darwin amd64
|
|
||||||
COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-amd64
|
|
||||||
#!/bin/sh
|
|
||||||
ARGS=""
|
|
||||||
SKIP_NEXT=0
|
|
||||||
for arg in "$@"; do
|
|
||||||
if [ $SKIP_NEXT -eq 1 ]; then
|
|
||||||
SKIP_NEXT=0
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
case "$arg" in
|
|
||||||
-target) SKIP_NEXT=1 ;;
|
|
||||||
-mmacosx-version-min=*) ;;
|
|
||||||
*) ARGS="$ARGS $arg" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
exec zig cc -fno-sanitize=all -target x86_64-macos-none -isysroot /opt/macos-sdk -I/opt/macos-sdk/usr/include -L/opt/macos-sdk/usr/lib -F/opt/macos-sdk/System/Library/Frameworks -w $ARGS
|
|
||||||
ZIGWRAP
|
|
||||||
RUN chmod +x /usr/local/bin/zcc-darwin-amd64
|
|
||||||
|
|
||||||
# Windows amd64 - uses Zig's bundled mingw
|
|
||||||
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-amd64
|
|
||||||
#!/bin/sh
|
|
||||||
ARGS=""
|
|
||||||
SKIP_NEXT=0
|
|
||||||
for arg in "$@"; do
|
|
||||||
if [ $SKIP_NEXT -eq 1 ]; then
|
|
||||||
SKIP_NEXT=0
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
case "$arg" in
|
|
||||||
-target) SKIP_NEXT=1 ;;
|
|
||||||
-Wl,*) ;;
|
|
||||||
*) ARGS="$ARGS $arg" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
exec zig cc -target x86_64-windows-gnu $ARGS
|
|
||||||
ZIGWRAP
|
|
||||||
RUN chmod +x /usr/local/bin/zcc-windows-amd64
|
|
||||||
|
|
||||||
# Windows arm64 - uses Zig's bundled mingw
|
|
||||||
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-arm64
|
|
||||||
#!/bin/sh
|
|
||||||
ARGS=""
|
|
||||||
SKIP_NEXT=0
|
|
||||||
for arg in "$@"; do
|
|
||||||
if [ $SKIP_NEXT -eq 1 ]; then
|
|
||||||
SKIP_NEXT=0
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
case "$arg" in
|
|
||||||
-target) SKIP_NEXT=1 ;;
|
|
||||||
-Wl,*) ;;
|
|
||||||
*) ARGS="$ARGS $arg" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
exec zig cc -target aarch64-windows-gnu $ARGS
|
|
||||||
ZIGWRAP
|
|
||||||
RUN chmod +x /usr/local/bin/zcc-windows-arm64
|
|
||||||
|
|
||||||
# Build script
|
|
||||||
COPY <<'SCRIPT' /usr/local/bin/build.sh
|
|
||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
OS=${1:-darwin}
|
|
||||||
ARCH=${2:-arm64}
|
|
||||||
|
|
||||||
case "${OS}-${ARCH}" in
|
|
||||||
darwin-arm64|darwin-aarch64)
|
|
||||||
export CC=zcc-darwin-arm64
|
|
||||||
export GOARCH=arm64
|
|
||||||
export GOOS=darwin
|
|
||||||
;;
|
|
||||||
darwin-amd64|darwin-x86_64)
|
|
||||||
export CC=zcc-darwin-amd64
|
|
||||||
export GOARCH=amd64
|
|
||||||
export GOOS=darwin
|
|
||||||
;;
|
|
||||||
linux-arm64|linux-aarch64)
|
|
||||||
export CC=gcc
|
|
||||||
export GOARCH=arm64
|
|
||||||
export GOOS=linux
|
|
||||||
;;
|
|
||||||
linux-amd64|linux-x86_64)
|
|
||||||
export CC=gcc
|
|
||||||
export GOARCH=amd64
|
|
||||||
export GOOS=linux
|
|
||||||
;;
|
|
||||||
windows-arm64|windows-aarch64)
|
|
||||||
export CC=zcc-windows-arm64
|
|
||||||
export GOARCH=arm64
|
|
||||||
export GOOS=windows
|
|
||||||
;;
|
|
||||||
windows-amd64|windows-x86_64)
|
|
||||||
export CC=zcc-windows-amd64
|
|
||||||
export GOARCH=amd64
|
|
||||||
export GOOS=windows
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: <os> <arch>"
|
|
||||||
echo " os: darwin, linux, windows"
|
|
||||||
echo " arch: amd64, arm64"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
export CGO_ENABLED=1
|
|
||||||
export CGO_CFLAGS="-w"
|
|
||||||
|
|
||||||
# Build frontend if exists and not already built (host may have built it)
|
|
||||||
if [ -d "frontend" ] && [ -f "frontend/package.json" ] && [ ! -d "frontend/dist" ]; then
|
|
||||||
(cd frontend && npm install --silent && npm run build --silent)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build
|
|
||||||
APP=${APP_NAME:-$(basename $(pwd))}
|
|
||||||
mkdir -p bin
|
|
||||||
|
|
||||||
EXT=""
|
|
||||||
LDFLAGS="-s -w"
|
|
||||||
if [ "$GOOS" = "windows" ]; then
|
|
||||||
EXT=".exe"
|
|
||||||
LDFLAGS="-s -w -H windowsgui"
|
|
||||||
fi
|
|
||||||
|
|
||||||
TAGS="production"
|
|
||||||
if [ -n "$EXTRA_TAGS" ]; then
|
|
||||||
TAGS="${TAGS},${EXTRA_TAGS}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
go build -tags "$TAGS" -trimpath -buildvcs=false -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} .
|
|
||||||
echo "Built: bin/${APP}-${GOOS}-${GOARCH}${EXT}"
|
|
||||||
SCRIPT
|
|
||||||
RUN chmod +x /usr/local/bin/build.sh
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
ENTRYPOINT ["/usr/local/bin/build.sh"]
|
|
||||||
CMD ["darwin", "arm64"]
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Wails Server Mode Dockerfile
|
|
||||||
# Multi-stage build for minimal image size
|
|
||||||
|
|
||||||
# Build stage
|
|
||||||
FROM golang:alpine AS builder
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install build dependencies
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Remove local replace directive if present (for production builds)
|
|
||||||
RUN sed -i '/^replace/d' go.mod || true
|
|
||||||
|
|
||||||
# Download dependencies
|
|
||||||
RUN go mod tidy
|
|
||||||
|
|
||||||
# Build the server binary
|
|
||||||
RUN go build -tags server -ldflags="-s -w" -o server .
|
|
||||||
|
|
||||||
# Runtime stage - minimal image
|
|
||||||
FROM gcr.io/distroless/static-debian12
|
|
||||||
|
|
||||||
# Copy the binary
|
|
||||||
COPY --from=builder /app/server /server
|
|
||||||
|
|
||||||
# Copy frontend assets
|
|
||||||
COPY --from=builder /app/frontend/dist /frontend/dist
|
|
||||||
|
|
||||||
# Expose the default port
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
# Bind to all interfaces (required for Docker)
|
|
||||||
# Can be overridden at runtime with -e WAILS_SERVER_HOST=...
|
|
||||||
ENV WAILS_SERVER_HOST=0.0.0.0
|
|
||||||
|
|
||||||
# Run the server
|
|
||||||
ENTRYPOINT ["/server"]
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
common: ../Taskfile.yml
|
|
||||||
|
|
||||||
vars:
|
|
||||||
# Signing configuration - edit these values for your project
|
|
||||||
# PGP_KEY: "path/to/signing-key.asc"
|
|
||||||
# SIGN_ROLE: "builder" # Options: origin, maint, archive, builder
|
|
||||||
#
|
|
||||||
# Password is stored securely in system keychain. Run: wails3 setup signing
|
|
||||||
|
|
||||||
# Docker image for cross-compilation (used when building on non-Linux or no CC available)
|
|
||||||
CROSS_IMAGE: wails-cross
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
summary: Builds the application for Linux
|
|
||||||
cmds:
|
|
||||||
# Linux requires CGO - use Docker when:
|
|
||||||
# 1. Cross-compiling from non-Linux, OR
|
|
||||||
# 2. No C compiler is available, OR
|
|
||||||
# 3. Target architecture differs from host architecture (cross-arch compilation)
|
|
||||||
- task: '{{if and (eq OS "linux") (eq .HAS_CC "true") (eq .TARGET_ARCH ARCH)}}build:native{{else}}build:docker{{end}}'
|
|
||||||
vars:
|
|
||||||
ARCH: '{{.ARCH}}'
|
|
||||||
DEV: '{{.DEV}}'
|
|
||||||
OUTPUT: '{{.OUTPUT}}'
|
|
||||||
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
|
|
||||||
vars:
|
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
|
||||||
# Determine target architecture (defaults to host ARCH if not specified)
|
|
||||||
TARGET_ARCH: '{{.ARCH | default ARCH}}'
|
|
||||||
# Check if a C compiler is available (gcc or clang)
|
|
||||||
HAS_CC:
|
|
||||||
sh: '(command -v gcc >/dev/null 2>&1 || command -v clang >/dev/null 2>&1) && echo "true" || echo "false"'
|
|
||||||
|
|
||||||
build:native:
|
|
||||||
summary: Builds the application natively on Linux
|
|
||||||
internal: true
|
|
||||||
deps:
|
|
||||||
- task: common:go:mod:tidy
|
|
||||||
- task: common:build:frontend
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
DEV:
|
|
||||||
ref: .DEV
|
|
||||||
- task: common:generate:icons
|
|
||||||
- task: generate:dotdesktop
|
|
||||||
cmds:
|
|
||||||
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
|
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
|
||||||
env:
|
|
||||||
GOOS: linux
|
|
||||||
CGO_ENABLED: 1
|
|
||||||
GOARCH: '{{.ARCH | default ARCH}}'
|
|
||||||
|
|
||||||
build:docker:
|
|
||||||
summary: Builds for Linux using Docker (for non-Linux hosts or when no C compiler available)
|
|
||||||
internal: true
|
|
||||||
deps:
|
|
||||||
- task: common:build:frontend
|
|
||||||
- task: common:generate:icons
|
|
||||||
- task: generate:dotdesktop
|
|
||||||
preconditions:
|
|
||||||
- sh: docker info > /dev/null 2>&1
|
|
||||||
msg: "Docker is required for cross-compilation to Linux. Please install Docker."
|
|
||||||
- sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1
|
|
||||||
msg: |
|
|
||||||
Docker image '{{.CROSS_IMAGE}}' not found.
|
|
||||||
Build it first: wails3 task setup:docker
|
|
||||||
cmds:
|
|
||||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}}
|
|
||||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
|
|
||||||
- mkdir -p {{.BIN_DIR}}
|
|
||||||
- mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"
|
|
||||||
vars:
|
|
||||||
DOCKER_ARCH: '{{.ARCH | default "amd64"}}'
|
|
||||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
|
||||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
|
||||||
# Mount Go module cache for faster builds
|
|
||||||
GO_CACHE_MOUNT:
|
|
||||||
sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"'
|
|
||||||
# Extract replace directives from go.mod and create -v mounts for each
|
|
||||||
REPLACE_MOUNTS:
|
|
||||||
sh: |
|
|
||||||
grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do
|
|
||||||
path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r')
|
|
||||||
# Convert relative paths to absolute
|
|
||||||
if [ "${path#/}" = "$path" ]; then
|
|
||||||
path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")"
|
|
||||||
fi
|
|
||||||
# Only mount if directory exists
|
|
||||||
if [ -d "$path" ]; then
|
|
||||||
echo "-v $path:$path:ro"
|
|
||||||
fi
|
|
||||||
done | tr '\n' ' '
|
|
||||||
|
|
||||||
package:
|
|
||||||
summary: Packages the application for Linux
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
cmds:
|
|
||||||
- task: create:appimage
|
|
||||||
- task: create:deb
|
|
||||||
- task: create:rpm
|
|
||||||
- task: create:aur
|
|
||||||
|
|
||||||
create:appimage:
|
|
||||||
summary: Creates an AppImage
|
|
||||||
dir: build/linux/appimage
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
- task: generate:dotdesktop
|
|
||||||
cmds:
|
|
||||||
- cp "{{.APP_BINARY}}" "{{.APP_NAME}}"
|
|
||||||
- cp ../../appicon.png "{{.APP_NAME}}.png"
|
|
||||||
- wails3 generate appimage -binary "{{.APP_NAME}}" -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build
|
|
||||||
vars:
|
|
||||||
APP_NAME: '{{.APP_NAME}}'
|
|
||||||
APP_BINARY: '../../../bin/{{.APP_NAME}}'
|
|
||||||
ICON: '{{.APP_NAME}}.png'
|
|
||||||
DESKTOP_FILE: '../{{.APP_NAME}}.desktop'
|
|
||||||
OUTPUT_DIR: '../../../bin'
|
|
||||||
|
|
||||||
create:deb:
|
|
||||||
summary: Creates a deb package
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
cmds:
|
|
||||||
- task: generate:dotdesktop
|
|
||||||
- task: generate:deb
|
|
||||||
|
|
||||||
create:rpm:
|
|
||||||
summary: Creates a rpm package
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
cmds:
|
|
||||||
- task: generate:dotdesktop
|
|
||||||
- task: generate:rpm
|
|
||||||
|
|
||||||
create:aur:
|
|
||||||
summary: Creates a arch linux packager package
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
cmds:
|
|
||||||
- task: generate:dotdesktop
|
|
||||||
- task: generate:aur
|
|
||||||
|
|
||||||
generate:deb:
|
|
||||||
summary: Creates a deb package
|
|
||||||
cmds:
|
|
||||||
- wails3 tool package -name "{{.APP_NAME}}" -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
|
|
||||||
|
|
||||||
generate:rpm:
|
|
||||||
summary: Creates a rpm package
|
|
||||||
cmds:
|
|
||||||
- wails3 tool package -name "{{.APP_NAME}}" -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
|
|
||||||
|
|
||||||
generate:aur:
|
|
||||||
summary: Creates a arch linux packager package
|
|
||||||
cmds:
|
|
||||||
- wails3 tool package -name "{{.APP_NAME}}" -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
|
|
||||||
|
|
||||||
generate:dotdesktop:
|
|
||||||
summary: Generates a `.desktop` file
|
|
||||||
dir: build
|
|
||||||
cmds:
|
|
||||||
- mkdir -p {{.ROOT_DIR}}/build/linux/appimage
|
|
||||||
- wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile "{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop" -categories "{{.CATEGORIES}}"
|
|
||||||
# Wrap Exec= with `env WEBKIT_DISABLE_DMABUF_RENDERER=1 ...` so launches
|
|
||||||
# from any desktop environment use the working renderer. See build/linux/Taskfile.yml :run for the matching dev-mode env block.
|
|
||||||
- sed -i -E 's|^Exec=([^ ]+)(.*)$|Exec=env WEBKIT_DISABLE_DMABUF_RENDERER=1 \1\2|' {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop
|
|
||||||
vars:
|
|
||||||
APP_NAME: '{{.APP_NAME}}'
|
|
||||||
EXEC: '{{.APP_NAME}}'
|
|
||||||
ICON: '{{.APP_NAME}}'
|
|
||||||
CATEGORIES: 'Development;'
|
|
||||||
OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'
|
|
||||||
|
|
||||||
run:
|
|
||||||
cmds:
|
|
||||||
- '{{.BIN_DIR}}/{{.APP_NAME}}'
|
|
||||||
env:
|
|
||||||
# WebKitGTK 2.50's default DMA-BUF renderer fails on RDP, VirtualBox/QEMU,
|
|
||||||
# and some bare WMs (Fluxbox, dwm) where DRM dumb-buffer access is
|
|
||||||
# restricted. Disabling it falls back to the GLES2/cairo path which works
|
|
||||||
# everywhere. Production launchers must set this too.
|
|
||||||
WEBKIT_DISABLE_DMABUF_RENDERER: "1"
|
|
||||||
|
|
||||||
sign:deb:
|
|
||||||
summary: Signs the DEB package
|
|
||||||
desc: |
|
|
||||||
Signs the .deb package with a PGP key.
|
|
||||||
Configure PGP_KEY in the vars section at the top of this file.
|
|
||||||
Password is retrieved from system keychain (run: wails3 setup signing)
|
|
||||||
deps:
|
|
||||||
- task: create:deb
|
|
||||||
cmds:
|
|
||||||
- wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.deb" --pgp-key {{.PGP_KEY}} {{if .SIGN_ROLE}}--role {{.SIGN_ROLE}}{{end}}
|
|
||||||
preconditions:
|
|
||||||
- sh: '[ -n "{{.PGP_KEY}}" ]'
|
|
||||||
msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml"
|
|
||||||
|
|
||||||
sign:rpm:
|
|
||||||
summary: Signs the RPM package
|
|
||||||
desc: |
|
|
||||||
Signs the .rpm package with a PGP key.
|
|
||||||
Configure PGP_KEY in the vars section at the top of this file.
|
|
||||||
Password is retrieved from system keychain (run: wails3 setup signing)
|
|
||||||
deps:
|
|
||||||
- task: create:rpm
|
|
||||||
cmds:
|
|
||||||
- wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}*.rpm" --pgp-key {{.PGP_KEY}}
|
|
||||||
preconditions:
|
|
||||||
- sh: '[ -n "{{.PGP_KEY}}" ]'
|
|
||||||
msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml"
|
|
||||||
|
|
||||||
sign:packages:
|
|
||||||
summary: Signs all Linux packages (DEB and RPM)
|
|
||||||
desc: |
|
|
||||||
Signs both .deb and .rpm packages with a PGP key.
|
|
||||||
Configure PGP_KEY in the vars section at the top of this file.
|
|
||||||
Password is retrieved from system keychain (run: wails3 setup signing)
|
|
||||||
cmds:
|
|
||||||
- task: sign:deb
|
|
||||||
- task: sign:rpm
|
|
||||||
preconditions:
|
|
||||||
- sh: '[ -n "{{.PGP_KEY}}" ]'
|
|
||||||
msg: "PGP_KEY is required. Set it in the vars section at the top of build/linux/Taskfile.yml"
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Copyright (c) 2018-Present Lea Anthony
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
# Fail script on any error
|
|
||||||
set -euxo pipefail
|
|
||||||
|
|
||||||
# Define variables
|
|
||||||
APP_DIR="${APP_NAME}.AppDir"
|
|
||||||
|
|
||||||
# Create AppDir structure
|
|
||||||
mkdir -p "${APP_DIR}/usr/bin"
|
|
||||||
cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/"
|
|
||||||
cp "${ICON_PATH}" "${APP_DIR}/"
|
|
||||||
cp "${DESKTOP_FILE}" "${APP_DIR}/"
|
|
||||||
|
|
||||||
if [[ $(uname -m) == *x86_64* ]]; then
|
|
||||||
# Download linuxdeploy and make it executable
|
|
||||||
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
|
||||||
chmod +x linuxdeploy-x86_64.AppImage
|
|
||||||
|
|
||||||
# Run linuxdeploy to bundle the application
|
|
||||||
./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage
|
|
||||||
else
|
|
||||||
# Download linuxdeploy and make it executable (arm64)
|
|
||||||
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage
|
|
||||||
chmod +x linuxdeploy-aarch64.AppImage
|
|
||||||
|
|
||||||
# Run linuxdeploy to bundle the application (arm64)
|
|
||||||
./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Rename the generated AppImage
|
|
||||||
mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage"
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Version=1.0
|
|
||||||
Name=NetBird
|
|
||||||
Comment=NetBird desktop client
|
|
||||||
# The Exec line includes %u to pass the URL to the application
|
|
||||||
Exec=/usr/local/bin/netbird-ui %u
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Icon=netbird-ui
|
|
||||||
Categories=Utility;
|
|
||||||
StartupWMClass=netbird-ui
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Type=Application
|
|
||||||
Name=netbird-ui
|
|
||||||
Exec=env WEBKIT_DISABLE_DMABUF_RENDERER=1 netbird-ui
|
|
||||||
Icon=netbird-ui
|
|
||||||
Categories=Development;
|
|
||||||
Terminal=false
|
|
||||||
Keywords=wails
|
|
||||||
Version=1.0
|
|
||||||
StartupNotify=false
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# Feel free to remove those if you don't want/need to use them.
|
|
||||||
# Make sure to check the documentation at https://nfpm.goreleaser.com
|
|
||||||
#
|
|
||||||
# The lines below are called `modelines`. See `:help modeline`
|
|
||||||
|
|
||||||
name: "netbird-ui"
|
|
||||||
arch: ${GOARCH}
|
|
||||||
platform: "linux"
|
|
||||||
version: "0.0.1"
|
|
||||||
section: "default"
|
|
||||||
priority: "extra"
|
|
||||||
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
|
||||||
description: "NetBird desktop client"
|
|
||||||
vendor: "NetBird"
|
|
||||||
homepage: "https://wails.io"
|
|
||||||
license: "MIT"
|
|
||||||
release: "1"
|
|
||||||
|
|
||||||
contents:
|
|
||||||
- src: "./bin/netbird-ui"
|
|
||||||
dst: "/usr/local/bin/netbird-ui"
|
|
||||||
- src: "./build/appicon.png"
|
|
||||||
dst: "/usr/share/icons/hicolor/128x128/apps/netbird-ui.png"
|
|
||||||
- src: "./build/linux/netbird-ui.desktop"
|
|
||||||
dst: "/usr/share/applications/netbird-ui.desktop"
|
|
||||||
|
|
||||||
# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1
|
|
||||||
depends:
|
|
||||||
- libgtk-3-0
|
|
||||||
- libwebkit2gtk-4.1-0
|
|
||||||
|
|
||||||
# Distribution-specific overrides for different package formats and WebKit versions
|
|
||||||
overrides:
|
|
||||||
# RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0)
|
|
||||||
rpm:
|
|
||||||
depends:
|
|
||||||
- gtk3
|
|
||||||
- webkit2gtk4.1
|
|
||||||
|
|
||||||
# Arch Linux packages (WebKit 4.1)
|
|
||||||
archlinux:
|
|
||||||
depends:
|
|
||||||
- gtk3
|
|
||||||
- webkit2gtk-4.1
|
|
||||||
|
|
||||||
# scripts section to ensure desktop database is updated after install
|
|
||||||
scripts:
|
|
||||||
postinstall: "./build/linux/nfpm/scripts/postinstall.sh"
|
|
||||||
# You can also add preremove, postremove if needed
|
|
||||||
# preremove: "./build/linux/nfpm/scripts/preremove.sh"
|
|
||||||
# postremove: "./build/linux/nfpm/scripts/postremove.sh"
|
|
||||||
|
|
||||||
# replaces:
|
|
||||||
# - foobar
|
|
||||||
# provides:
|
|
||||||
# - bar
|
|
||||||
# depends:
|
|
||||||
# - gtk3
|
|
||||||
# - libwebkit2gtk
|
|
||||||
# recommends:
|
|
||||||
# - whatever
|
|
||||||
# suggests:
|
|
||||||
# - something-else
|
|
||||||
# conflicts:
|
|
||||||
# - not-foo
|
|
||||||
# - not-bar
|
|
||||||
# changelog: "changelog.yaml"
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Update desktop database for .desktop file changes
|
|
||||||
# This makes the application appear in application menus and registers its capabilities.
|
|
||||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
|
||||||
echo "Updating desktop database..."
|
|
||||||
update-desktop-database -q /usr/share/applications
|
|
||||||
else
|
|
||||||
echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update MIME database for custom URL schemes (x-scheme-handler)
|
|
||||||
# This ensures the system knows how to handle your custom protocols.
|
|
||||||
if command -v update-mime-database >/dev/null 2>&1; then
|
|
||||||
echo "Updating MIME database..."
|
|
||||||
update-mime-database -n /usr/share/mime
|
|
||||||
else
|
|
||||||
echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=Netbird
|
Name=Netbird
|
||||||
Exec=env WEBKIT_DISABLE_DMABUF_RENDERER=1 /usr/bin/netbird-ui
|
Exec=/usr/bin/netbird-ui
|
||||||
Icon=netbird
|
Icon=netbird
|
||||||
Type=Application
|
Type=Application
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Utility;
|
Categories=Utility;
|
||||||
Keywords=netbird;
|
Keywords=netbird;
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
common: ../Taskfile.yml
|
|
||||||
|
|
||||||
vars:
|
|
||||||
# Signing configuration - edit these values for your project
|
|
||||||
# SIGN_CERTIFICATE: "path/to/certificate.pfx"
|
|
||||||
# SIGN_THUMBPRINT: "certificate-thumbprint" # Alternative to SIGN_CERTIFICATE
|
|
||||||
# TIMESTAMP_SERVER: "http://timestamp.digicert.com"
|
|
||||||
#
|
|
||||||
# Password is stored securely in system keychain. Run: wails3 setup signing
|
|
||||||
|
|
||||||
# Docker image for cross-compilation with CGO (used when CGO_ENABLED=1 on non-Windows)
|
|
||||||
CROSS_IMAGE: wails-cross
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
build:
|
|
||||||
summary: Builds the application for Windows
|
|
||||||
cmds:
|
|
||||||
# CGO Windows builds from Linux use mingw-w64 (lighter than docker).
|
|
||||||
# Docker is only needed if mingw-w64 is unavailable.
|
|
||||||
- task: build:native
|
|
||||||
vars:
|
|
||||||
ARCH: '{{.ARCH}}'
|
|
||||||
DEV: '{{.DEV}}'
|
|
||||||
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
|
|
||||||
vars:
|
|
||||||
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
|
|
||||||
|
|
||||||
build:console:
|
|
||||||
summary: Builds a console-attached Windows binary so logs go to the terminal.
|
|
||||||
desc: |
|
|
||||||
Same as `windows:build` but links against the console PE subsystem
|
|
||||||
instead of windowsgui, so stdout/stderr (logrus, panics) print to the
|
|
||||||
terminal that launched the .exe. Useful for chasing tray, event-stream,
|
|
||||||
or daemon-RPC bugs that have no other feedback channel on Windows.
|
|
||||||
|
|
||||||
Output is bin/netbird-ui-console.exe — kept distinct so the production
|
|
||||||
binary built by `windows:build` isn't shadowed.
|
|
||||||
|
|
||||||
Cross-compile from Linux works the same way:
|
|
||||||
CGO_ENABLED=1 task windows:build:console
|
|
||||||
deps:
|
|
||||||
- task: common:go:mod:tidy
|
|
||||||
- task: common:build:frontend
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
DEV:
|
|
||||||
ref: .DEV
|
|
||||||
- task: common:generate:icons
|
|
||||||
preconditions:
|
|
||||||
- sh: '[ "{{OS}}" = "windows" ] || [ "{{.CGO_ENABLED}}" != "1" ] || command -v {{.CC}}'
|
|
||||||
msg: "{{.CC}} not found. Install with: sudo apt-get install gcc-mingw-w64-x86-64 (Debian/Ubuntu) / sudo dnf install mingw64-gcc (Fedora)"
|
|
||||||
cmds:
|
|
||||||
- task: generate:syso
|
|
||||||
- go build {{.BUILD_FLAGS}} -o "{{.BIN_DIR}}/{{.APP_NAME}}-console.exe"
|
|
||||||
- cmd: powershell Remove-item *.syso
|
|
||||||
platforms: [windows]
|
|
||||||
- cmd: rm -f *.syso
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
vars:
|
|
||||||
# Identical to build:native's flags except no -H windowsgui, so the
|
|
||||||
# binary attaches to the launching console.
|
|
||||||
BUILD_FLAGS: '-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"'
|
|
||||||
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
|
|
||||||
CC: '{{.CC | default "x86_64-w64-mingw32-gcc"}}'
|
|
||||||
env:
|
|
||||||
GOOS: windows
|
|
||||||
CGO_ENABLED: '{{.CGO_ENABLED}}'
|
|
||||||
GOARCH: '{{.ARCH | default ARCH}}'
|
|
||||||
CC: '{{.CC}}'
|
|
||||||
|
|
||||||
build:native:
|
|
||||||
summary: Builds for Windows natively, or cross-compiles from Linux/macOS via mingw-w64.
|
|
||||||
internal: true
|
|
||||||
deps:
|
|
||||||
- task: common:go:mod:tidy
|
|
||||||
- task: common:build:frontend
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS:
|
|
||||||
ref: .BUILD_FLAGS
|
|
||||||
DEV:
|
|
||||||
ref: .DEV
|
|
||||||
- task: common:generate:icons
|
|
||||||
preconditions:
|
|
||||||
# When cross-compiling with CGO from a non-Windows host, the mingw-w64
|
|
||||||
# cross-gcc must be present. Native Windows builds skip this check.
|
|
||||||
- sh: '[ "{{OS}}" = "windows" ] || [ "{{.CGO_ENABLED}}" != "1" ] || command -v {{.CC}}'
|
|
||||||
msg: "{{.CC}} not found. Install with: sudo apt-get install gcc-mingw-w64-x86-64 (Debian/Ubuntu) / sudo dnf install mingw64-gcc (Fedora)"
|
|
||||||
cmds:
|
|
||||||
- task: generate:syso
|
|
||||||
- go build {{.BUILD_FLAGS}} -o "{{.BIN_DIR}}/{{.APP_NAME}}.exe"
|
|
||||||
- cmd: powershell Remove-item *.syso
|
|
||||||
platforms: [windows]
|
|
||||||
- cmd: rm -f *.syso
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
vars:
|
|
||||||
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}'
|
|
||||||
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
|
|
||||||
CC: '{{.CC | default "x86_64-w64-mingw32-gcc"}}'
|
|
||||||
env:
|
|
||||||
GOOS: windows
|
|
||||||
CGO_ENABLED: '{{.CGO_ENABLED}}'
|
|
||||||
GOARCH: '{{.ARCH | default ARCH}}'
|
|
||||||
CC: '{{.CC}}'
|
|
||||||
|
|
||||||
build:docker:
|
|
||||||
summary: Cross-compiles for Windows using Docker with Zig (for CGO builds on non-Windows)
|
|
||||||
internal: true
|
|
||||||
deps:
|
|
||||||
- task: common:build:frontend
|
|
||||||
- task: common:generate:icons
|
|
||||||
preconditions:
|
|
||||||
- sh: docker info > /dev/null 2>&1
|
|
||||||
msg: "Docker is required for CGO cross-compilation. Please install Docker."
|
|
||||||
- sh: docker image inspect {{.CROSS_IMAGE}} > /dev/null 2>&1
|
|
||||||
msg: |
|
|
||||||
Docker image '{{.CROSS_IMAGE}}' not found.
|
|
||||||
Build it first: wails3 task setup:docker
|
|
||||||
cmds:
|
|
||||||
- task: generate:syso
|
|
||||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}}
|
|
||||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
|
|
||||||
- rm -f *.syso
|
|
||||||
vars:
|
|
||||||
DOCKER_ARCH: '{{.ARCH | default "amd64"}}'
|
|
||||||
# Mount Go module cache for faster builds
|
|
||||||
GO_CACHE_MOUNT:
|
|
||||||
sh: 'echo "-v ${GOPATH:-$HOME/go}/pkg/mod:/go/pkg/mod"'
|
|
||||||
# Extract replace directives from go.mod and create -v mounts for each
|
|
||||||
REPLACE_MOUNTS:
|
|
||||||
sh: |
|
|
||||||
grep -E '^replace .* => ' go.mod 2>/dev/null | while read -r line; do
|
|
||||||
path=$(echo "$line" | sed -E 's/^replace .* => //' | tr -d '\r')
|
|
||||||
# Convert relative paths to absolute
|
|
||||||
if [ "${path#/}" = "$path" ]; then
|
|
||||||
path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")"
|
|
||||||
fi
|
|
||||||
# Only mount if directory exists
|
|
||||||
if [ -d "$path" ]; then
|
|
||||||
echo "-v $path:$path:ro"
|
|
||||||
fi
|
|
||||||
done | tr '\n' ' '
|
|
||||||
|
|
||||||
package:
|
|
||||||
summary: Packages the application
|
|
||||||
cmds:
|
|
||||||
- task: '{{if eq (.FORMAT | default "nsis") "msix"}}create:msix:package{{else}}create:nsis:installer{{end}}'
|
|
||||||
vars:
|
|
||||||
FORMAT: '{{.FORMAT | default "nsis"}}'
|
|
||||||
|
|
||||||
generate:syso:
|
|
||||||
summary: Generates Windows `.syso` file
|
|
||||||
dir: build
|
|
||||||
cmds:
|
|
||||||
- wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
|
|
||||||
vars:
|
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
|
||||||
|
|
||||||
create:nsis:installer:
|
|
||||||
summary: Creates an NSIS installer
|
|
||||||
dir: build/windows/nsis
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
cmds:
|
|
||||||
# Create the Microsoft WebView2 bootstrapper if it doesn't exist
|
|
||||||
- wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis"
|
|
||||||
- |
|
|
||||||
{{if eq OS "windows"}}
|
|
||||||
makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}\{{.BIN_DIR}}\{{.APP_NAME}}.exe" project.nsi
|
|
||||||
{{else}}
|
|
||||||
makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi
|
|
||||||
{{end}}
|
|
||||||
vars:
|
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
|
||||||
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
|
|
||||||
|
|
||||||
create:msix:package:
|
|
||||||
summary: Creates an MSIX package
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
cmds:
|
|
||||||
- |-
|
|
||||||
wails3 tool msix \
|
|
||||||
--config "{{.ROOT_DIR}}/wails.json" \
|
|
||||||
--name "{{.APP_NAME}}" \
|
|
||||||
--executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \
|
|
||||||
--arch "{{.ARCH}}" \
|
|
||||||
--out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \
|
|
||||||
{{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \
|
|
||||||
{{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \
|
|
||||||
{{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}}
|
|
||||||
vars:
|
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
|
||||||
CERT_PATH: '{{.CERT_PATH | default ""}}'
|
|
||||||
PUBLISHER: '{{.PUBLISHER | default ""}}'
|
|
||||||
USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}'
|
|
||||||
|
|
||||||
install:msix:tools:
|
|
||||||
summary: Installs tools required for MSIX packaging
|
|
||||||
cmds:
|
|
||||||
- wails3 tool msix-install-tools
|
|
||||||
|
|
||||||
run:
|
|
||||||
cmds:
|
|
||||||
- '{{.BIN_DIR}}/{{.APP_NAME}}.exe'
|
|
||||||
|
|
||||||
sign:
|
|
||||||
summary: Signs the Windows executable
|
|
||||||
desc: |
|
|
||||||
Signs the .exe with an Authenticode certificate.
|
|
||||||
Configure SIGN_CERTIFICATE or SIGN_THUMBPRINT in the vars section at the top of this file.
|
|
||||||
Password is retrieved from system keychain (run: wails3 setup signing)
|
|
||||||
deps:
|
|
||||||
- task: build
|
|
||||||
cmds:
|
|
||||||
- wails3 tool sign --input "{{.BIN_DIR}}/{{.APP_NAME}}.exe" {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}}
|
|
||||||
preconditions:
|
|
||||||
- sh: '[ -n "{{.SIGN_CERTIFICATE}}" ] || [ -n "{{.SIGN_THUMBPRINT}}" ]'
|
|
||||||
msg: "Either SIGN_CERTIFICATE or SIGN_THUMBPRINT is required. Set it in the vars section at the top of build/windows/Taskfile.yml"
|
|
||||||
|
|
||||||
sign:installer:
|
|
||||||
summary: Signs the NSIS installer
|
|
||||||
desc: |
|
|
||||||
Creates and signs the NSIS installer.
|
|
||||||
Configure SIGN_CERTIFICATE or SIGN_THUMBPRINT in the vars section at the top of this file.
|
|
||||||
Password is retrieved from system keychain (run: wails3 setup signing)
|
|
||||||
deps:
|
|
||||||
- task: create:nsis:installer
|
|
||||||
cmds:
|
|
||||||
- wails3 tool sign --input "build/windows/nsis/{{.APP_NAME}}-installer.exe" {{if .SIGN_CERTIFICATE}}--certificate {{.SIGN_CERTIFICATE}}{{end}} {{if .SIGN_THUMBPRINT}}--thumbprint {{.SIGN_THUMBPRINT}}{{end}} {{if .TIMESTAMP_SERVER}}--timestamp {{.TIMESTAMP_SERVER}}{{end}}
|
|
||||||
preconditions:
|
|
||||||
- sh: '[ -n "{{.SIGN_CERTIFICATE}}" ] || [ -n "{{.SIGN_THUMBPRINT}}" ]'
|
|
||||||
msg: "Either SIGN_CERTIFICATE or SIGN_THUMBPRINT is required. Set it in the vars section at the top of build/windows/Taskfile.yml"
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"fixed": {
|
|
||||||
"file_version": "0.0.1"
|
|
||||||
},
|
|
||||||
"info": {
|
|
||||||
"0000": {
|
|
||||||
"ProductVersion": "0.0.1",
|
|
||||||
"CompanyName": "NetBird",
|
|
||||||
"FileDescription": "NetBird desktop client",
|
|
||||||
"LegalCopyright": "© 2026, My Company",
|
|
||||||
"ProductName": "NetBird",
|
|
||||||
"Comments": "This is a comment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Package
|
|
||||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
|
||||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
|
||||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
|
||||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
|
||||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
|
||||||
IgnorableNamespaces="uap3">
|
|
||||||
|
|
||||||
<Identity
|
|
||||||
Name="io.netbird.client"
|
|
||||||
Publisher="CN=NetBird"
|
|
||||||
Version="0.0.1.0"
|
|
||||||
ProcessorArchitecture="x64" />
|
|
||||||
|
|
||||||
<Properties>
|
|
||||||
<DisplayName>NetBird</DisplayName>
|
|
||||||
<PublisherDisplayName>NetBird</PublisherDisplayName>
|
|
||||||
<Description>NetBird desktop client</Description>
|
|
||||||
<Logo>Assets\StoreLogo.png</Logo>
|
|
||||||
</Properties>
|
|
||||||
|
|
||||||
<Dependencies>
|
|
||||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
|
||||||
</Dependencies>
|
|
||||||
|
|
||||||
<Resources>
|
|
||||||
<Resource Language="en-us" />
|
|
||||||
</Resources>
|
|
||||||
|
|
||||||
<Applications>
|
|
||||||
<Application Id="io.netbird.client" Executable="netbird-ui" EntryPoint="Windows.FullTrustApplication">
|
|
||||||
<uap:VisualElements
|
|
||||||
DisplayName="NetBird"
|
|
||||||
Description="NetBird desktop client"
|
|
||||||
BackgroundColor="transparent"
|
|
||||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
|
||||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
|
||||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
|
||||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
|
||||||
</uap:VisualElements>
|
|
||||||
|
|
||||||
<Extensions>
|
|
||||||
<desktop:Extension Category="windows.fullTrustProcess" Executable="netbird-ui" />
|
|
||||||
|
|
||||||
|
|
||||||
</Extensions>
|
|
||||||
</Application>
|
|
||||||
</Applications>
|
|
||||||
|
|
||||||
<Capabilities>
|
|
||||||
<rescap:Capability Name="runFullTrust" />
|
|
||||||
|
|
||||||
</Capabilities>
|
|
||||||
</Package>
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<MsixPackagingToolTemplate
|
|
||||||
xmlns="http://schemas.microsoft.com/msix/packaging/msixpackagingtool/template/2022">
|
|
||||||
<Settings
|
|
||||||
AllowTelemetry="false"
|
|
||||||
ApplyACLsToPackageFiles="true"
|
|
||||||
GenerateCommandLineFile="true"
|
|
||||||
AllowPromptForPassword="false">
|
|
||||||
</Settings>
|
|
||||||
<Installer
|
|
||||||
Path="netbird-ui"
|
|
||||||
Arguments=""
|
|
||||||
InstallLocation="C:\Program Files\NetBird\NetBird">
|
|
||||||
</Installer>
|
|
||||||
<PackageInformation
|
|
||||||
PackageName="NetBird"
|
|
||||||
PackageDisplayName="NetBird"
|
|
||||||
PublisherName="CN=NetBird"
|
|
||||||
PublisherDisplayName="NetBird"
|
|
||||||
Version="0.0.1.0"
|
|
||||||
PackageDescription="NetBird desktop client">
|
|
||||||
<Capabilities>
|
|
||||||
<Capability Name="runFullTrust" />
|
|
||||||
|
|
||||||
</Capabilities>
|
|
||||||
<Applications>
|
|
||||||
<Application
|
|
||||||
Id="io.netbird.client"
|
|
||||||
Description="NetBird desktop client"
|
|
||||||
DisplayName="NetBird"
|
|
||||||
ExecutableName="netbird-ui"
|
|
||||||
EntryPoint="Windows.FullTrustApplication">
|
|
||||||
|
|
||||||
</Application>
|
|
||||||
</Applications>
|
|
||||||
<Resources>
|
|
||||||
<Resource Language="en-us" />
|
|
||||||
</Resources>
|
|
||||||
<Dependencies>
|
|
||||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
|
||||||
</Dependencies>
|
|
||||||
<Properties>
|
|
||||||
<Framework>false</Framework>
|
|
||||||
<DisplayName>NetBird</DisplayName>
|
|
||||||
<PublisherDisplayName>NetBird</PublisherDisplayName>
|
|
||||||
<Description>NetBird desktop client</Description>
|
|
||||||
<Logo>Assets\AppIcon.png</Logo>
|
|
||||||
</Properties>
|
|
||||||
</PackageInformation>
|
|
||||||
<SaveLocation PackagePath="netbird-ui.msix" />
|
|
||||||
<PackageIntegrity>
|
|
||||||
<CertificatePath></CertificatePath>
|
|
||||||
</PackageIntegrity>
|
|
||||||
</MsixPackagingToolTemplate>
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
Unicode true
|
|
||||||
|
|
||||||
####
|
|
||||||
## Please note: Template replacements don't work in this file. They are provided with default defines like
|
|
||||||
## mentioned underneath.
|
|
||||||
## If the keyword is not defined, "wails_tools.nsh" will populate them.
|
|
||||||
## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually
|
|
||||||
## from outside of Wails for debugging and development of the installer.
|
|
||||||
##
|
|
||||||
## For development first make a wails nsis build to populate the "wails_tools.nsh":
|
|
||||||
## > wails build --target windows/amd64 --nsis
|
|
||||||
## Then you can call makensis on this file with specifying the path to your binary:
|
|
||||||
## For a AMD64 only installer:
|
|
||||||
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe
|
|
||||||
## For a ARM64 only installer:
|
|
||||||
## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe
|
|
||||||
## For a installer with both architectures:
|
|
||||||
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
|
|
||||||
####
|
|
||||||
## The following information is taken from the wails_tools.nsh file, but they can be overwritten here.
|
|
||||||
####
|
|
||||||
## !define INFO_PROJECTNAME "my-project" # Default "netbird-ui"
|
|
||||||
## !define INFO_COMPANYNAME "My Company" # Default "NetBird"
|
|
||||||
## !define INFO_PRODUCTNAME "My Product Name" # Default "NetBird"
|
|
||||||
## !define INFO_PRODUCTVERSION "1.0.0" # Default "0.0.1"
|
|
||||||
## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "© 2026, My Company"
|
|
||||||
###
|
|
||||||
## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
|
|
||||||
## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
|
|
||||||
####
|
|
||||||
## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
|
|
||||||
####
|
|
||||||
## Include the wails tools
|
|
||||||
####
|
|
||||||
!include "wails_tools.nsh"
|
|
||||||
|
|
||||||
# The version information for this two must consist of 4 parts
|
|
||||||
VIProductVersion "${INFO_PRODUCTVERSION}.0"
|
|
||||||
VIFileVersion "${INFO_PRODUCTVERSION}.0"
|
|
||||||
|
|
||||||
VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
|
|
||||||
VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
|
|
||||||
VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
|
|
||||||
VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
|
|
||||||
VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
|
|
||||||
VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
|
|
||||||
|
|
||||||
# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware
|
|
||||||
ManifestDPIAware true
|
|
||||||
|
|
||||||
!include "MUI.nsh"
|
|
||||||
|
|
||||||
!define MUI_ICON "..\icon.ico"
|
|
||||||
!define MUI_UNICON "..\icon.ico"
|
|
||||||
# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
|
|
||||||
!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
|
|
||||||
!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
|
|
||||||
|
|
||||||
!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
|
|
||||||
# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer
|
|
||||||
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
|
|
||||||
!insertmacro MUI_PAGE_INSTFILES # Installing page.
|
|
||||||
!insertmacro MUI_PAGE_FINISH # Finished installation page.
|
|
||||||
|
|
||||||
!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page
|
|
||||||
|
|
||||||
!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
|
|
||||||
|
|
||||||
## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
|
|
||||||
#!uninstfinalize 'signtool --file "%1"'
|
|
||||||
#!finalize 'signtool --file "%1"'
|
|
||||||
|
|
||||||
Name "${INFO_PRODUCTNAME}"
|
|
||||||
OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
|
|
||||||
InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
|
|
||||||
ShowInstDetails show # This will always show the installation details.
|
|
||||||
|
|
||||||
Function .onInit
|
|
||||||
!insertmacro wails.checkArchitecture
|
|
||||||
FunctionEnd
|
|
||||||
|
|
||||||
Section
|
|
||||||
!insertmacro wails.setShellContext
|
|
||||||
|
|
||||||
!insertmacro wails.webview2runtime
|
|
||||||
|
|
||||||
SetOutPath $INSTDIR
|
|
||||||
|
|
||||||
!insertmacro wails.files
|
|
||||||
|
|
||||||
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
|
||||||
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
|
||||||
|
|
||||||
!insertmacro wails.associateFiles
|
|
||||||
!insertmacro wails.associateCustomProtocols
|
|
||||||
|
|
||||||
!insertmacro wails.writeUninstaller
|
|
||||||
SectionEnd
|
|
||||||
|
|
||||||
Section "uninstall"
|
|
||||||
!insertmacro wails.setShellContext
|
|
||||||
|
|
||||||
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
|
|
||||||
|
|
||||||
RMDir /r $INSTDIR
|
|
||||||
|
|
||||||
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
|
|
||||||
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
|
|
||||||
|
|
||||||
!insertmacro wails.unassociateFiles
|
|
||||||
!insertmacro wails.unassociateCustomProtocols
|
|
||||||
|
|
||||||
!insertmacro wails.deleteUninstaller
|
|
||||||
SectionEnd
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
# DO NOT EDIT - Generated automatically by `wails build`
|
|
||||||
|
|
||||||
!include "x64.nsh"
|
|
||||||
!include "WinVer.nsh"
|
|
||||||
!include "FileFunc.nsh"
|
|
||||||
|
|
||||||
!ifndef INFO_PROJECTNAME
|
|
||||||
!define INFO_PROJECTNAME "netbird-ui"
|
|
||||||
!endif
|
|
||||||
!ifndef INFO_COMPANYNAME
|
|
||||||
!define INFO_COMPANYNAME "NetBird"
|
|
||||||
!endif
|
|
||||||
!ifndef INFO_PRODUCTNAME
|
|
||||||
!define INFO_PRODUCTNAME "NetBird"
|
|
||||||
!endif
|
|
||||||
!ifndef INFO_PRODUCTVERSION
|
|
||||||
!define INFO_PRODUCTVERSION "0.0.1"
|
|
||||||
!endif
|
|
||||||
!ifndef INFO_COPYRIGHT
|
|
||||||
!define INFO_COPYRIGHT "© 2026, My Company"
|
|
||||||
!endif
|
|
||||||
!ifndef PRODUCT_EXECUTABLE
|
|
||||||
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
|
|
||||||
!endif
|
|
||||||
!ifndef UNINST_KEY_NAME
|
|
||||||
!define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
|
|
||||||
!endif
|
|
||||||
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
|
|
||||||
|
|
||||||
!ifndef REQUEST_EXECUTION_LEVEL
|
|
||||||
!define REQUEST_EXECUTION_LEVEL "admin"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
|
||||||
|
|
||||||
!ifdef ARG_WAILS_AMD64_BINARY
|
|
||||||
!define SUPPORTS_AMD64
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!ifdef ARG_WAILS_ARM64_BINARY
|
|
||||||
!define SUPPORTS_ARM64
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!ifdef SUPPORTS_AMD64
|
|
||||||
!ifdef SUPPORTS_ARM64
|
|
||||||
!define ARCH "amd64_arm64"
|
|
||||||
!else
|
|
||||||
!define ARCH "amd64"
|
|
||||||
!endif
|
|
||||||
!else
|
|
||||||
!ifdef SUPPORTS_ARM64
|
|
||||||
!define ARCH "arm64"
|
|
||||||
!else
|
|
||||||
!error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY"
|
|
||||||
!endif
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!macro wails.checkArchitecture
|
|
||||||
!ifndef WAILS_WIN10_REQUIRED
|
|
||||||
!define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later."
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED
|
|
||||||
!define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
${If} ${AtLeastWin10}
|
|
||||||
!ifdef SUPPORTS_AMD64
|
|
||||||
${if} ${IsNativeAMD64}
|
|
||||||
Goto ok
|
|
||||||
${EndIf}
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!ifdef SUPPORTS_ARM64
|
|
||||||
${if} ${IsNativeARM64}
|
|
||||||
Goto ok
|
|
||||||
${EndIf}
|
|
||||||
!endif
|
|
||||||
|
|
||||||
IfSilent silentArch notSilentArch
|
|
||||||
silentArch:
|
|
||||||
SetErrorLevel 65
|
|
||||||
Abort
|
|
||||||
notSilentArch:
|
|
||||||
MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}"
|
|
||||||
Quit
|
|
||||||
${else}
|
|
||||||
IfSilent silentWin notSilentWin
|
|
||||||
silentWin:
|
|
||||||
SetErrorLevel 64
|
|
||||||
Abort
|
|
||||||
notSilentWin:
|
|
||||||
MessageBox MB_OK "${WAILS_WIN10_REQUIRED}"
|
|
||||||
Quit
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
ok:
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro wails.files
|
|
||||||
!ifdef SUPPORTS_AMD64
|
|
||||||
${if} ${IsNativeAMD64}
|
|
||||||
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}"
|
|
||||||
${EndIf}
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!ifdef SUPPORTS_ARM64
|
|
||||||
${if} ${IsNativeARM64}
|
|
||||||
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}"
|
|
||||||
${EndIf}
|
|
||||||
!endif
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro wails.writeUninstaller
|
|
||||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
|
||||||
|
|
||||||
SetRegView 64
|
|
||||||
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
|
|
||||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
|
|
||||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
|
|
||||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
|
||||||
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
|
||||||
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
|
||||||
|
|
||||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
|
||||||
IntFmt $0 "0x%08X" $0
|
|
||||||
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro wails.deleteUninstaller
|
|
||||||
Delete "$INSTDIR\uninstall.exe"
|
|
||||||
|
|
||||||
SetRegView 64
|
|
||||||
DeleteRegKey HKLM "${UNINST_KEY}"
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro wails.setShellContext
|
|
||||||
${If} ${REQUEST_EXECUTION_LEVEL} == "admin"
|
|
||||||
SetShellVarContext all
|
|
||||||
${else}
|
|
||||||
SetShellVarContext current
|
|
||||||
${EndIf}
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
# Install webview2 by launching the bootstrapper
|
|
||||||
# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment
|
|
||||||
!macro wails.webview2runtime
|
|
||||||
!ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT
|
|
||||||
!define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
SetRegView 64
|
|
||||||
# If the admin key exists and is not empty then webview2 is already installed
|
|
||||||
ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
|
||||||
${If} $0 != ""
|
|
||||||
Goto ok
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
${If} ${REQUEST_EXECUTION_LEVEL} == "user"
|
|
||||||
# If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
|
|
||||||
ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
|
||||||
${If} $0 != ""
|
|
||||||
Goto ok
|
|
||||||
${EndIf}
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
SetDetailsPrint both
|
|
||||||
DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
|
|
||||||
SetDetailsPrint listonly
|
|
||||||
|
|
||||||
InitPluginsDir
|
|
||||||
CreateDirectory "$pluginsdir\webview2bootstrapper"
|
|
||||||
SetOutPath "$pluginsdir\webview2bootstrapper"
|
|
||||||
File "MicrosoftEdgeWebview2Setup.exe"
|
|
||||||
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
|
|
||||||
|
|
||||||
SetDetailsPrint both
|
|
||||||
ok:
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b
|
|
||||||
!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
|
|
||||||
; Backup the previously associated file class
|
|
||||||
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
|
|
||||||
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
|
|
||||||
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro APP_UNASSOCIATE EXT FILECLASS
|
|
||||||
; Backup the previously associated file class
|
|
||||||
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
|
|
||||||
|
|
||||||
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro wails.associateFiles
|
|
||||||
; Create file associations
|
|
||||||
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro wails.unassociateFiles
|
|
||||||
; Delete app associations
|
|
||||||
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND
|
|
||||||
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}"
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" ""
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}"
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" ""
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" ""
|
|
||||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}"
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL
|
|
||||||
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro wails.associateCustomProtocols
|
|
||||||
; Create custom protocols associations
|
|
||||||
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro wails.unassociateCustomProtocols
|
|
||||||
; Delete app custom protocol associations
|
|
||||||
|
|
||||||
!macroend
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<assemblyIdentity type="win32" name="io.netbird.client" version="0.0.1.0" processorArchitecture="*"/>
|
|
||||||
<dependency>
|
|
||||||
<dependentAssembly>
|
|
||||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
|
||||||
</dependentAssembly>
|
|
||||||
</dependency>
|
|
||||||
<asmv3:application>
|
|
||||||
<asmv3:windowsSettings>
|
|
||||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
|
|
||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
|
|
||||||
</asmv3:windowsSettings>
|
|
||||||
</asmv3:application>
|
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<security>
|
|
||||||
<requestedPrivileges>
|
|
||||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
|
||||||
</requestedPrivileges>
|
|
||||||
</security>
|
|
||||||
</trustInfo>
|
|
||||||
</assembly>
|
|
||||||
1781
client/ui/client_ui.go
Normal file
16
client/ui/const.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
const (
|
||||||
|
allowSSHMenuDescr = "Allow SSH connections"
|
||||||
|
autoConnectMenuDescr = "Connect automatically when the service starts"
|
||||||
|
quantumResistanceMenuDescr = "Enable post-quantum security via Rosenpass"
|
||||||
|
lazyConnMenuDescr = "[Experimental] Enable lazy connections"
|
||||||
|
blockInboundMenuDescr = "Block inbound connections to the local machine and routed networks"
|
||||||
|
notificationsMenuDescr = "Enable notifications"
|
||||||
|
advancedSettingsMenuDescr = "Advanced settings of the application"
|
||||||
|
debugBundleMenuDescr = "Create and open debug information bundle"
|
||||||
|
disabledMenuDescr = ""
|
||||||
|
networksMenuDescr = "Open the networks management window"
|
||||||
|
latestVersionMenuDescr = "Download latest version"
|
||||||
|
quitMenuDescr = "Quit the client app"
|
||||||
|
)
|
||||||
727
client/ui/debug.go
Normal file
@@ -0,0 +1,727 @@
|
|||||||
|
//go:build !(linux && 386)
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/skratchdot/open-golang/open"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
uptypes "github.com/netbirdio/netbird/upload-server/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initial state for the debug collection
|
||||||
|
type debugInitialState struct {
|
||||||
|
wasDown bool
|
||||||
|
needsRestoreUp bool
|
||||||
|
logLevel proto.LogLevel
|
||||||
|
isLevelTrace bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug collection parameters
|
||||||
|
type debugCollectionParams struct {
|
||||||
|
duration time.Duration
|
||||||
|
anonymize bool
|
||||||
|
systemInfo bool
|
||||||
|
upload bool
|
||||||
|
uploadURL string
|
||||||
|
enablePersistence bool
|
||||||
|
capture bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI components for progress tracking
|
||||||
|
type progressUI struct {
|
||||||
|
statusLabel *widget.Label
|
||||||
|
progressBar *widget.ProgressBar
|
||||||
|
uiControls []fyne.Disableable
|
||||||
|
window fyne.Window
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) showDebugUI() {
|
||||||
|
w := s.app.NewWindow("NetBird Debug")
|
||||||
|
w.SetOnClosed(s.cancel)
|
||||||
|
w.Resize(fyne.NewSize(600, 500))
|
||||||
|
w.SetFixedSize(true)
|
||||||
|
|
||||||
|
anonymizeCheck := widget.NewCheck("Anonymize sensitive information (public IPs, domains, ...)", nil)
|
||||||
|
systemInfoCheck := widget.NewCheck("Include system information (routes, interfaces, ...)", nil)
|
||||||
|
systemInfoCheck.SetChecked(true)
|
||||||
|
captureCheck := widget.NewCheck("Include packet capture", nil)
|
||||||
|
uploadCheck := widget.NewCheck("Upload bundle automatically after creation", nil)
|
||||||
|
uploadCheck.SetChecked(true)
|
||||||
|
|
||||||
|
uploadURLContainer, uploadURL := s.buildUploadSection(uploadCheck)
|
||||||
|
|
||||||
|
debugModeContainer, runForDurationCheck, durationInput, noteLabel := s.buildDurationSection()
|
||||||
|
|
||||||
|
statusLabel := widget.NewLabel("")
|
||||||
|
statusLabel.Hide()
|
||||||
|
progressBar := widget.NewProgressBar()
|
||||||
|
progressBar.Hide()
|
||||||
|
createButton := widget.NewButton("Create Debug Bundle", nil)
|
||||||
|
|
||||||
|
uiControls := []fyne.Disableable{
|
||||||
|
anonymizeCheck, systemInfoCheck, captureCheck,
|
||||||
|
uploadCheck, uploadURL, runForDurationCheck, durationInput, createButton,
|
||||||
|
}
|
||||||
|
|
||||||
|
createButton.OnTapped = s.getCreateHandler(
|
||||||
|
statusLabel, progressBar, uploadCheck, uploadURL,
|
||||||
|
anonymizeCheck, systemInfoCheck, captureCheck,
|
||||||
|
runForDurationCheck, durationInput, uiControls, w,
|
||||||
|
)
|
||||||
|
|
||||||
|
content := container.NewVBox(
|
||||||
|
widget.NewLabel("Create a debug bundle to help troubleshoot issues with NetBird"),
|
||||||
|
widget.NewLabel(""),
|
||||||
|
anonymizeCheck, systemInfoCheck, captureCheck,
|
||||||
|
uploadCheck, uploadURLContainer,
|
||||||
|
widget.NewLabel(""),
|
||||||
|
debugModeContainer, noteLabel,
|
||||||
|
widget.NewLabel(""),
|
||||||
|
statusLabel, progressBar, createButton,
|
||||||
|
)
|
||||||
|
|
||||||
|
w.SetContent(container.NewPadded(content))
|
||||||
|
w.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) buildUploadSection(uploadCheck *widget.Check) (*fyne.Container, *widget.Entry) {
|
||||||
|
uploadURL := widget.NewEntry()
|
||||||
|
uploadURL.SetText(uptypes.DefaultBundleURL)
|
||||||
|
uploadURL.SetPlaceHolder("Enter upload URL")
|
||||||
|
|
||||||
|
uploadURLContainer := container.NewVBox(widget.NewLabel("Debug upload URL:"), uploadURL)
|
||||||
|
|
||||||
|
uploadCheck.OnChanged = func(checked bool) {
|
||||||
|
if checked {
|
||||||
|
uploadURLContainer.Show()
|
||||||
|
} else {
|
||||||
|
uploadURLContainer.Hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uploadURLContainer, uploadURL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) buildDurationSection() (*fyne.Container, *widget.Check, *widget.Entry, *widget.Label) {
|
||||||
|
runForDurationCheck := widget.NewCheck("Run with trace logs before creating bundle", nil)
|
||||||
|
runForDurationCheck.SetChecked(true)
|
||||||
|
|
||||||
|
forLabel := widget.NewLabel("for")
|
||||||
|
durationInput := widget.NewEntry()
|
||||||
|
durationInput.SetText("1")
|
||||||
|
minutesLabel := widget.NewLabel("minute")
|
||||||
|
durationInput.Validator = func(s string) error {
|
||||||
|
return validateMinute(s, minutesLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
noteLabel := widget.NewLabel("Note: NetBird will be brought up and down during collection")
|
||||||
|
|
||||||
|
runForDurationCheck.OnChanged = func(checked bool) {
|
||||||
|
if checked {
|
||||||
|
forLabel.Show()
|
||||||
|
durationInput.Show()
|
||||||
|
minutesLabel.Show()
|
||||||
|
noteLabel.Show()
|
||||||
|
} else {
|
||||||
|
forLabel.Hide()
|
||||||
|
durationInput.Hide()
|
||||||
|
minutesLabel.Hide()
|
||||||
|
noteLabel.Hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modeContainer := container.NewHBox(runForDurationCheck, forLabel, durationInput, minutesLabel)
|
||||||
|
return modeContainer, runForDurationCheck, durationInput, noteLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateMinute(s string, minutesLabel *widget.Label) error {
|
||||||
|
if val, err := strconv.Atoi(s); err != nil || val < 1 {
|
||||||
|
return fmt.Errorf("must be a number ≥ 1")
|
||||||
|
}
|
||||||
|
if s == "1" {
|
||||||
|
minutesLabel.SetText("minute")
|
||||||
|
} else {
|
||||||
|
minutesLabel.SetText("minutes")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// disableUIControls disables the provided UI controls
|
||||||
|
func disableUIControls(controls []fyne.Disableable) {
|
||||||
|
for _, control := range controls {
|
||||||
|
control.Disable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enableUIControls enables the provided UI controls
|
||||||
|
func enableUIControls(controls []fyne.Disableable) {
|
||||||
|
for _, control := range controls {
|
||||||
|
control.Enable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) getCreateHandler(
|
||||||
|
statusLabel *widget.Label,
|
||||||
|
progressBar *widget.ProgressBar,
|
||||||
|
uploadCheck *widget.Check,
|
||||||
|
uploadURL *widget.Entry,
|
||||||
|
anonymizeCheck *widget.Check,
|
||||||
|
systemInfoCheck *widget.Check,
|
||||||
|
captureCheck *widget.Check,
|
||||||
|
runForDurationCheck *widget.Check,
|
||||||
|
duration *widget.Entry,
|
||||||
|
uiControls []fyne.Disableable,
|
||||||
|
w fyne.Window,
|
||||||
|
) func() {
|
||||||
|
return func() {
|
||||||
|
disableUIControls(uiControls)
|
||||||
|
statusLabel.Show()
|
||||||
|
|
||||||
|
var url string
|
||||||
|
if uploadCheck.Checked {
|
||||||
|
url = uploadURL.Text
|
||||||
|
if url == "" {
|
||||||
|
statusLabel.SetText("Error: Upload URL is required when upload is enabled")
|
||||||
|
enableUIControls(uiControls)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &debugCollectionParams{
|
||||||
|
anonymize: anonymizeCheck.Checked,
|
||||||
|
systemInfo: systemInfoCheck.Checked,
|
||||||
|
capture: captureCheck.Checked,
|
||||||
|
upload: uploadCheck.Checked,
|
||||||
|
uploadURL: url,
|
||||||
|
enablePersistence: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
runForDuration := runForDurationCheck.Checked
|
||||||
|
if runForDuration {
|
||||||
|
minutes, err := time.ParseDuration(duration.Text + "m")
|
||||||
|
if err != nil {
|
||||||
|
statusLabel.SetText(fmt.Sprintf("Error: Invalid duration: %v", err))
|
||||||
|
enableUIControls(uiControls)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params.duration = minutes
|
||||||
|
|
||||||
|
statusLabel.SetText(fmt.Sprintf("Running in debug mode for %d minutes...", int(minutes.Minutes())))
|
||||||
|
progressBar.Show()
|
||||||
|
progressBar.SetValue(0)
|
||||||
|
|
||||||
|
go s.handleRunForDuration(
|
||||||
|
statusLabel,
|
||||||
|
progressBar,
|
||||||
|
uiControls,
|
||||||
|
w,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusLabel.SetText("Creating debug bundle...")
|
||||||
|
go s.handleDebugCreation(
|
||||||
|
params,
|
||||||
|
statusLabel,
|
||||||
|
uiControls,
|
||||||
|
w,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) handleRunForDuration(
|
||||||
|
statusLabel *widget.Label,
|
||||||
|
progressBar *widget.ProgressBar,
|
||||||
|
uiControls []fyne.Disableable,
|
||||||
|
w fyne.Window,
|
||||||
|
params *debugCollectionParams,
|
||||||
|
) {
|
||||||
|
progressUI := &progressUI{
|
||||||
|
statusLabel: statusLabel,
|
||||||
|
progressBar: progressBar,
|
||||||
|
uiControls: uiControls,
|
||||||
|
window: w,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.getSrvClient(failFastTimeout)
|
||||||
|
if err != nil {
|
||||||
|
handleError(progressUI, fmt.Sprintf("Failed to get client for debug: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
initialState, err := s.getInitialState(conn)
|
||||||
|
if err != nil {
|
||||||
|
handleError(progressUI, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer s.restoreServiceState(conn, initialState)
|
||||||
|
|
||||||
|
if err := s.collectDebugData(conn, initialState, params, progressUI); err != nil {
|
||||||
|
handleError(progressUI, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.createDebugBundleFromCollection(conn, params, progressUI); err != nil {
|
||||||
|
handleError(progressUI, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
progressUI.statusLabel.SetText("Bundle created successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get initial state of the service
|
||||||
|
func (s *serviceClient) getInitialState(conn proto.DaemonServiceClient) (*debugInitialState, error) {
|
||||||
|
statusResp, err := conn.Status(s.ctx, &proto.StatusRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(" get status: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevelResp, err := conn.GetLogLevel(s.ctx, &proto.GetLogLevelRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get log level: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wasDown := statusResp.Status != string(internal.StatusConnected) &&
|
||||||
|
statusResp.Status != string(internal.StatusConnecting)
|
||||||
|
|
||||||
|
initialLogLevel := logLevelResp.GetLevel()
|
||||||
|
initialLevelTrace := initialLogLevel >= proto.LogLevel_TRACE
|
||||||
|
|
||||||
|
return &debugInitialState{
|
||||||
|
wasDown: wasDown,
|
||||||
|
logLevel: initialLogLevel,
|
||||||
|
isLevelTrace: initialLevelTrace,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle progress tracking during collection
|
||||||
|
func startProgressTracker(ctx context.Context, wg *sync.WaitGroup, duration time.Duration, progress *progressUI) {
|
||||||
|
progress.progressBar.Show()
|
||||||
|
progress.progressBar.SetValue(0)
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
endTime := startTime.Add(duration)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
remaining := time.Until(endTime)
|
||||||
|
if remaining <= 0 {
|
||||||
|
remaining = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
|
progressVal := float64(elapsed) / float64(duration)
|
||||||
|
if progressVal > 1.0 {
|
||||||
|
progressVal = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.progressBar.SetValue(progressVal)
|
||||||
|
progress.statusLabel.SetText(fmt.Sprintf("Running with trace logs... %s remaining", formatDuration(remaining)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) configureServiceForDebug(
|
||||||
|
conn proto.DaemonServiceClient,
|
||||||
|
state *debugInitialState,
|
||||||
|
params *debugCollectionParams,
|
||||||
|
) {
|
||||||
|
if state.wasDown {
|
||||||
|
if _, err := conn.Up(s.ctx, &proto.UpRequest{}); err != nil {
|
||||||
|
log.Warnf("failed to bring service up: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Service brought up for debug")
|
||||||
|
time.Sleep(time.Second * 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state.isLevelTrace {
|
||||||
|
if _, err := conn.SetLogLevel(s.ctx, &proto.SetLogLevelRequest{Level: proto.LogLevel_TRACE}); err != nil {
|
||||||
|
log.Warnf("failed to set log level to TRACE: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Log level set to TRACE for debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Down(s.ctx, &proto.DownRequest{}); err != nil {
|
||||||
|
log.Warnf("failed to bring service down: %v", err)
|
||||||
|
} else {
|
||||||
|
state.needsRestoreUp = !state.wasDown
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.enablePersistence {
|
||||||
|
if _, err := conn.SetSyncResponsePersistence(s.ctx, &proto.SetSyncResponsePersistenceRequest{
|
||||||
|
Enabled: true,
|
||||||
|
}); err != nil {
|
||||||
|
log.Warnf("failed to enable sync response persistence: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Sync response persistence enabled for debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Up(s.ctx, &proto.UpRequest{}); err != nil {
|
||||||
|
log.Warnf("failed to bring service back up: %v", err)
|
||||||
|
} else {
|
||||||
|
state.needsRestoreUp = false
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.StartCPUProfile(s.ctx, &proto.StartCPUProfileRequest{}); err != nil {
|
||||||
|
log.Warnf("failed to start CPU profiling: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.startBundleCaptureIfEnabled(conn, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) startBundleCaptureIfEnabled(conn proto.DaemonServiceClient, params *debugCollectionParams) {
|
||||||
|
if !params.capture {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxCapture = 10 * time.Minute
|
||||||
|
timeout := params.duration + 30*time.Second
|
||||||
|
if timeout > maxCapture {
|
||||||
|
timeout = maxCapture
|
||||||
|
log.Warnf("packet capture clamped to %s (server maximum)", maxCapture)
|
||||||
|
}
|
||||||
|
if _, err := conn.StartBundleCapture(s.ctx, &proto.StartBundleCaptureRequest{
|
||||||
|
Timeout: durationpb.New(timeout),
|
||||||
|
}); err != nil {
|
||||||
|
log.Warnf("failed to start bundle capture: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) collectDebugData(
|
||||||
|
conn proto.DaemonServiceClient,
|
||||||
|
state *debugInitialState,
|
||||||
|
params *debugCollectionParams,
|
||||||
|
progress *progressUI,
|
||||||
|
) error {
|
||||||
|
ctx, cancel := context.WithTimeout(s.ctx, params.duration)
|
||||||
|
defer cancel()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
startProgressTracker(ctx, &wg, params.duration, progress)
|
||||||
|
|
||||||
|
s.configureServiceForDebug(conn, state, params)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
progress.progressBar.Hide()
|
||||||
|
progress.statusLabel.SetText("Collecting debug data...")
|
||||||
|
|
||||||
|
if _, err := conn.StopCPUProfile(s.ctx, &proto.StopCPUProfileRequest{}); err != nil {
|
||||||
|
log.Warnf("failed to stop CPU profiling: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.capture {
|
||||||
|
stopCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if _, err := conn.StopBundleCapture(stopCtx, &proto.StopBundleCaptureRequest{}); err != nil {
|
||||||
|
log.Warnf("failed to stop bundle capture: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the debug bundle with collected data
|
||||||
|
func (s *serviceClient) createDebugBundleFromCollection(
|
||||||
|
conn proto.DaemonServiceClient,
|
||||||
|
params *debugCollectionParams,
|
||||||
|
progress *progressUI,
|
||||||
|
) error {
|
||||||
|
progress.statusLabel.SetText("Creating debug bundle with collected logs...")
|
||||||
|
|
||||||
|
request := &proto.DebugBundleRequest{
|
||||||
|
Anonymize: params.anonymize,
|
||||||
|
SystemInfo: params.systemInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.upload {
|
||||||
|
request.UploadURL = params.uploadURL
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := conn.DebugBundle(s.ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create debug bundle: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show appropriate dialog based on upload status
|
||||||
|
localPath := resp.GetPath()
|
||||||
|
uploadFailureReason := resp.GetUploadFailureReason()
|
||||||
|
uploadedKey := resp.GetUploadedKey()
|
||||||
|
|
||||||
|
if params.upload {
|
||||||
|
if uploadFailureReason != "" {
|
||||||
|
showUploadFailedDialog(progress.window, localPath, uploadFailureReason)
|
||||||
|
} else {
|
||||||
|
showUploadSuccessDialog(s.app, progress.window, localPath, uploadedKey)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showBundleCreatedDialog(progress.window, localPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
enableUIControls(progress.uiControls)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore service to original state
|
||||||
|
func (s *serviceClient) restoreServiceState(conn proto.DaemonServiceClient, state *debugInitialState) {
|
||||||
|
if state.needsRestoreUp {
|
||||||
|
if _, err := conn.Up(s.ctx, &proto.UpRequest{}); err != nil {
|
||||||
|
log.Warnf("failed to restore up state: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Service state restored to up")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.wasDown {
|
||||||
|
if _, err := conn.Down(s.ctx, &proto.DownRequest{}); err != nil {
|
||||||
|
log.Warnf("failed to restore down state: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Service state restored to down")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state.isLevelTrace {
|
||||||
|
if _, err := conn.SetLogLevel(s.ctx, &proto.SetLogLevelRequest{Level: state.logLevel}); err != nil {
|
||||||
|
log.Warnf("failed to restore log level: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Log level restored to original setting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle errors during debug collection
|
||||||
|
func handleError(progress *progressUI, errMsg string) {
|
||||||
|
log.Errorf("%s", errMsg)
|
||||||
|
progress.statusLabel.SetText(errMsg)
|
||||||
|
progress.progressBar.Hide()
|
||||||
|
enableUIControls(progress.uiControls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) handleDebugCreation(
|
||||||
|
params *debugCollectionParams,
|
||||||
|
statusLabel *widget.Label,
|
||||||
|
uiControls []fyne.Disableable,
|
||||||
|
w fyne.Window,
|
||||||
|
) {
|
||||||
|
conn, err := s.getSrvClient(failFastTimeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get client for debug: %v", err)
|
||||||
|
statusLabel.SetText(fmt.Sprintf("Error: %v", err))
|
||||||
|
enableUIControls(uiControls)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.capture {
|
||||||
|
if _, err := conn.StartBundleCapture(s.ctx, &proto.StartBundleCaptureRequest{
|
||||||
|
Timeout: durationpb.New(30 * time.Second),
|
||||||
|
}); err != nil {
|
||||||
|
log.Warnf("failed to start bundle capture: %v", err)
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
stopCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if _, err := conn.StopBundleCapture(stopCtx, &proto.StopBundleCaptureRequest{}); err != nil {
|
||||||
|
log.Warnf("failed to stop bundle capture: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.createDebugBundle(params.anonymize, params.systemInfo, params.uploadURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create debug bundle: %v", err)
|
||||||
|
statusLabel.SetText(fmt.Sprintf("Error creating bundle: %v", err))
|
||||||
|
enableUIControls(uiControls)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
localPath := resp.GetPath()
|
||||||
|
uploadFailureReason := resp.GetUploadFailureReason()
|
||||||
|
uploadedKey := resp.GetUploadedKey()
|
||||||
|
|
||||||
|
if params.upload {
|
||||||
|
if uploadFailureReason != "" {
|
||||||
|
showUploadFailedDialog(w, localPath, uploadFailureReason)
|
||||||
|
} else {
|
||||||
|
showUploadSuccessDialog(s.app, w, localPath, uploadedKey)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showBundleCreatedDialog(w, localPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
enableUIControls(uiControls)
|
||||||
|
statusLabel.SetText("Bundle created successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) createDebugBundle(anonymize bool, systemInfo bool, uploadURL string) (*proto.DebugBundleResponse, error) {
|
||||||
|
conn, err := s.getSrvClient(failFastTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &proto.DebugBundleRequest{
|
||||||
|
Anonymize: anonymize,
|
||||||
|
SystemInfo: systemInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
if uploadURL != "" {
|
||||||
|
request.UploadURL = uploadURL
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := conn.DebugBundle(s.ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create debug bundle via daemon: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatDuration formats a duration in HH:MM:SS format
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
d = d.Round(time.Second)
|
||||||
|
h := d / time.Hour
|
||||||
|
d %= time.Hour
|
||||||
|
m := d / time.Minute
|
||||||
|
d %= time.Minute
|
||||||
|
s := d / time.Second
|
||||||
|
return fmt.Sprintf("%02d:%02d:%02d", h, m, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createButtonWithAction creates a button with the given label and action
|
||||||
|
func createButtonWithAction(label string, action func()) *widget.Button {
|
||||||
|
button := widget.NewButton(label, action)
|
||||||
|
return button
|
||||||
|
}
|
||||||
|
|
||||||
|
// showUploadFailedDialog displays a dialog when upload fails
|
||||||
|
func showUploadFailedDialog(w fyne.Window, localPath, failureReason string) {
|
||||||
|
content := container.NewVBox(
|
||||||
|
widget.NewLabel(fmt.Sprintf("Bundle upload failed:\n%s\n\n"+
|
||||||
|
"A local copy was saved at:\n%s", failureReason, localPath)),
|
||||||
|
)
|
||||||
|
|
||||||
|
customDialog := dialog.NewCustom("Upload Failed", "Cancel", content, w)
|
||||||
|
|
||||||
|
buttonBox := container.NewHBox(
|
||||||
|
createButtonWithAction("Open file", func() {
|
||||||
|
log.Infof("Attempting to open local file: %s", localPath)
|
||||||
|
if openErr := open.Start(localPath); openErr != nil {
|
||||||
|
log.Errorf("Failed to open local file '%s': %v", localPath, openErr)
|
||||||
|
dialog.ShowError(fmt.Errorf("open the local file:\n%s\n\nError: %v", localPath, openErr), w)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
createButtonWithAction("Open folder", func() {
|
||||||
|
folderPath := filepath.Dir(localPath)
|
||||||
|
log.Infof("Attempting to open local folder: %s", folderPath)
|
||||||
|
if openErr := open.Start(folderPath); openErr != nil {
|
||||||
|
log.Errorf("Failed to open local folder '%s': %v", folderPath, openErr)
|
||||||
|
dialog.ShowError(fmt.Errorf("open the local folder:\n%s\n\nError: %v", folderPath, openErr), w)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
content.Add(buttonBox)
|
||||||
|
customDialog.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// showUploadSuccessDialog displays a dialog when upload succeeds
|
||||||
|
func showUploadSuccessDialog(a fyne.App, w fyne.Window, localPath, uploadedKey string) {
|
||||||
|
log.Infof("Upload key: %s", uploadedKey)
|
||||||
|
keyEntry := widget.NewEntry()
|
||||||
|
keyEntry.SetText(uploadedKey)
|
||||||
|
keyEntry.Disable()
|
||||||
|
|
||||||
|
content := container.NewVBox(
|
||||||
|
widget.NewLabel("Bundle uploaded successfully!"),
|
||||||
|
widget.NewLabel(""),
|
||||||
|
widget.NewLabel("Upload key:"),
|
||||||
|
keyEntry,
|
||||||
|
widget.NewLabel(""),
|
||||||
|
widget.NewLabel(fmt.Sprintf("Local copy saved at:\n%s", localPath)),
|
||||||
|
)
|
||||||
|
|
||||||
|
customDialog := dialog.NewCustom("Upload Successful", "OK", content, w)
|
||||||
|
|
||||||
|
copyBtn := createButtonWithAction("Copy key", func() {
|
||||||
|
a.Clipboard().SetContent(uploadedKey)
|
||||||
|
log.Info("Upload key copied to clipboard")
|
||||||
|
})
|
||||||
|
|
||||||
|
buttonBox := createButtonBox(localPath, w, copyBtn)
|
||||||
|
content.Add(buttonBox)
|
||||||
|
customDialog.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// showBundleCreatedDialog displays a dialog when bundle is created without upload
|
||||||
|
func showBundleCreatedDialog(w fyne.Window, localPath string) {
|
||||||
|
content := container.NewVBox(
|
||||||
|
widget.NewLabel(fmt.Sprintf("Bundle created locally at:\n%s\n\n"+
|
||||||
|
"Administrator privileges may be required to access the file.", localPath)),
|
||||||
|
)
|
||||||
|
|
||||||
|
customDialog := dialog.NewCustom("Debug Bundle Created", "Cancel", content, w)
|
||||||
|
|
||||||
|
buttonBox := createButtonBox(localPath, w, nil)
|
||||||
|
content.Add(buttonBox)
|
||||||
|
customDialog.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createButtonBox(localPath string, w fyne.Window, elems ...fyne.Widget) *fyne.Container {
|
||||||
|
box := container.NewHBox()
|
||||||
|
for _, elem := range elems {
|
||||||
|
box.Add(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileBtn := createButtonWithAction("Open file", func() {
|
||||||
|
log.Infof("Attempting to open local file: %s", localPath)
|
||||||
|
if openErr := open.Start(localPath); openErr != nil {
|
||||||
|
log.Errorf("Failed to open local file '%s': %v", localPath, openErr)
|
||||||
|
dialog.ShowError(fmt.Errorf("open the local file:\n%s\n\nError: %v", localPath, openErr), w)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
folderBtn := createButtonWithAction("Open folder", func() {
|
||||||
|
folderPath := filepath.Dir(localPath)
|
||||||
|
log.Infof("Attempting to open local folder: %s", folderPath)
|
||||||
|
if openErr := open.Start(folderPath); openErr != nil {
|
||||||
|
log.Errorf("Failed to open local folder '%s': %v", folderPath, openErr)
|
||||||
|
dialog.ShowError(fmt.Errorf("open the local folder:\n%s\n\nError: %v", folderPath, openErr), w)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
box.Add(fileBtn)
|
||||||
|
box.Add(folderBtn)
|
||||||
|
|
||||||
|
return box
|
||||||
|
}
|
||||||
184
client/ui/event/event.go
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
"github.com/netbirdio/netbird/client/ui/desktop"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notifier sends desktop notifications. Defined here so the event package
|
||||||
|
// does not depend on fyne or the platform-specific notifier implementation.
|
||||||
|
type Notifier interface {
|
||||||
|
Send(title, body string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handler func(*proto.SystemEvent)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
notifier Notifier
|
||||||
|
addr string
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
enabled bool
|
||||||
|
handlers []Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(notifier Notifier, addr string) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
notifier: notifier,
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) Start(ctx context.Context) {
|
||||||
|
e.mu.Lock()
|
||||||
|
e.ctx, e.cancel = context.WithCancel(ctx)
|
||||||
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
expBackOff := backoff.WithContext(&backoff.ExponentialBackOff{
|
||||||
|
InitialInterval: time.Second,
|
||||||
|
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||||
|
Multiplier: backoff.DefaultMultiplier,
|
||||||
|
MaxInterval: 10 * time.Second,
|
||||||
|
MaxElapsedTime: 0,
|
||||||
|
Stop: backoff.Stop,
|
||||||
|
Clock: backoff.SystemClock,
|
||||||
|
}, ctx)
|
||||||
|
|
||||||
|
if err := backoff.Retry(e.streamEvents, expBackOff); err != nil {
|
||||||
|
log.Errorf("event stream ended: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) streamEvents() error {
|
||||||
|
e.mu.Lock()
|
||||||
|
ctx := e.ctx
|
||||||
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
client, err := getClient(e.addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := client.SubscribeEvents(ctx, &proto.SubscribeRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to subscribe to events: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("subscribed to daemon events")
|
||||||
|
defer func() {
|
||||||
|
log.Info("unsubscribed from daemon events")
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
event, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error receiving event: %w", err)
|
||||||
|
}
|
||||||
|
e.handleEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) Stop() {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
if e.cancel != nil {
|
||||||
|
e.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) SetNotificationsEnabled(enabled bool) {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
e.enabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) handleEvent(event *proto.SystemEvent) {
|
||||||
|
e.mu.Lock()
|
||||||
|
enabled := e.enabled
|
||||||
|
handlers := slices.Clone(e.handlers)
|
||||||
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
if event.UserMessage != "" && (enabled || event.Severity == proto.SystemEvent_CRITICAL) && !isV6DefaultRoutePartner(event) {
|
||||||
|
title := e.getEventTitle(event)
|
||||||
|
body := event.UserMessage
|
||||||
|
id := event.Metadata["id"]
|
||||||
|
if id != "" {
|
||||||
|
body += fmt.Sprintf(" ID: %s", id)
|
||||||
|
}
|
||||||
|
e.notifier.Send(title, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, handler := range handlers {
|
||||||
|
go handler(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) AddHandler(handler Handler) {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
e.handlers = append(e.handlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isV6DefaultRoutePartner reports whether the event is the IPv6 half of a
|
||||||
|
// paired v4/v6 default-route event. Management always pairs ::/0 with 0.0.0.0/0
|
||||||
|
// for exit nodes, so the v4 partner already drives the user-facing toast and
|
||||||
|
// the v6 one is suppressed to avoid a duplicate notification.
|
||||||
|
func isV6DefaultRoutePartner(event *proto.SystemEvent) bool {
|
||||||
|
return event.Category == proto.SystemEvent_NETWORK && event.Metadata["network"] == "::/0"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Manager) getEventTitle(event *proto.SystemEvent) string {
|
||||||
|
var prefix string
|
||||||
|
switch event.Severity {
|
||||||
|
case proto.SystemEvent_CRITICAL:
|
||||||
|
prefix = "Critical"
|
||||||
|
case proto.SystemEvent_ERROR:
|
||||||
|
prefix = "Error"
|
||||||
|
case proto.SystemEvent_WARNING:
|
||||||
|
prefix = "Warning"
|
||||||
|
default:
|
||||||
|
prefix = "Info"
|
||||||
|
}
|
||||||
|
|
||||||
|
var category string
|
||||||
|
switch event.Category {
|
||||||
|
case proto.SystemEvent_DNS:
|
||||||
|
category = "DNS"
|
||||||
|
case proto.SystemEvent_NETWORK:
|
||||||
|
category = "Network"
|
||||||
|
case proto.SystemEvent_AUTHENTICATION:
|
||||||
|
category = "Authentication"
|
||||||
|
case proto.SystemEvent_CONNECTIVITY:
|
||||||
|
category = "Connectivity"
|
||||||
|
default:
|
||||||
|
category = "System"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s: %s", prefix, category)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClient(addr string) (proto.DaemonServiceClient, error) {
|
||||||
|
conn, err := grpc.NewClient(
|
||||||
|
strings.TrimPrefix(addr, "tcp://"),
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithUserAgent(desktop.GetUIUserAgent()),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return proto.NewDaemonServiceClient(conn), nil
|
||||||
|
}
|
||||||
326
client/ui/event_handler.go
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
//go:build !(linux && 386)
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"fyne.io/systray"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
type eventHandler struct {
|
||||||
|
client *serviceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEventHandler(client *serviceClient) *eventHandler {
|
||||||
|
return &eventHandler{
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) listen(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-h.client.mUp.ClickedCh:
|
||||||
|
h.handleConnectClick()
|
||||||
|
case <-h.client.mDown.ClickedCh:
|
||||||
|
h.handleDisconnectClick()
|
||||||
|
case <-h.client.mAllowSSH.ClickedCh:
|
||||||
|
h.handleAllowSSHClick()
|
||||||
|
case <-h.client.mAutoConnect.ClickedCh:
|
||||||
|
h.handleAutoConnectClick()
|
||||||
|
case <-h.client.mEnableRosenpass.ClickedCh:
|
||||||
|
h.handleRosenpassClick()
|
||||||
|
case <-h.client.mLazyConnEnabled.ClickedCh:
|
||||||
|
h.handleLazyConnectionClick()
|
||||||
|
case <-h.client.mBlockInbound.ClickedCh:
|
||||||
|
h.handleBlockInboundClick()
|
||||||
|
case <-h.client.mAdvancedSettings.ClickedCh:
|
||||||
|
h.handleAdvancedSettingsClick()
|
||||||
|
case <-h.client.mCreateDebugBundle.ClickedCh:
|
||||||
|
h.handleCreateDebugBundleClick()
|
||||||
|
case <-h.client.mQuit.ClickedCh:
|
||||||
|
h.handleQuitClick()
|
||||||
|
return
|
||||||
|
case <-h.client.mGitHub.ClickedCh:
|
||||||
|
h.handleGitHubClick()
|
||||||
|
case <-h.client.mUpdate.ClickedCh:
|
||||||
|
h.handleUpdateClick()
|
||||||
|
case <-h.client.mNetworks.ClickedCh:
|
||||||
|
h.handleNetworksClick()
|
||||||
|
case <-h.client.mNotifications.ClickedCh:
|
||||||
|
h.handleNotificationsClick()
|
||||||
|
case <-systray.TrayOpenedCh:
|
||||||
|
h.client.updateExitNodes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleConnectClick() {
|
||||||
|
h.client.mUp.Disable()
|
||||||
|
|
||||||
|
if h.client.connectCancel != nil {
|
||||||
|
h.client.connectCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
connectCtx, connectCancel := context.WithCancel(h.client.ctx)
|
||||||
|
h.client.connectCancel = connectCancel
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer connectCancel()
|
||||||
|
|
||||||
|
if err := h.client.menuUpClick(connectCtx); err != nil {
|
||||||
|
st, ok := status.FromError(err)
|
||||||
|
if errors.Is(err, context.Canceled) || (ok && st.Code() == codes.Canceled) {
|
||||||
|
log.Debugf("connect operation cancelled by user")
|
||||||
|
} else {
|
||||||
|
h.client.notifier.Send("Error", "Failed to connect")
|
||||||
|
log.Errorf("connect failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.client.updateStatus(); err != nil {
|
||||||
|
log.Debugf("failed to update status after connect: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleDisconnectClick() {
|
||||||
|
h.client.mDown.Disable()
|
||||||
|
h.client.cancelExitNodeRetry()
|
||||||
|
|
||||||
|
if h.client.connectCancel != nil {
|
||||||
|
log.Debugf("cancelling ongoing connect operation")
|
||||||
|
h.client.connectCancel()
|
||||||
|
h.client.connectCancel = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := h.client.menuDownClick(); err != nil {
|
||||||
|
st, ok := status.FromError(err)
|
||||||
|
if !errors.Is(err, context.Canceled) && !(ok && st.Code() == codes.Canceled) {
|
||||||
|
h.client.notifier.Send("Error", "Failed to disconnect")
|
||||||
|
log.Errorf("disconnect failed: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Debugf("disconnect cancelled or already disconnecting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.client.updateStatus(); err != nil {
|
||||||
|
log.Debugf("failed to update status after disconnect: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleAllowSSHClick() {
|
||||||
|
h.toggleCheckbox(h.client.mAllowSSH)
|
||||||
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mAllowSSH) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.notifier.Send("Error", "Failed to update SSH settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleAutoConnectClick() {
|
||||||
|
h.toggleCheckbox(h.client.mAutoConnect)
|
||||||
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mAutoConnect) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.notifier.Send("Error", "Failed to update auto-connect settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleRosenpassClick() {
|
||||||
|
h.toggleCheckbox(h.client.mEnableRosenpass)
|
||||||
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mEnableRosenpass) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.notifier.Send("Error", "Failed to update Rosenpass settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleLazyConnectionClick() {
|
||||||
|
h.toggleCheckbox(h.client.mLazyConnEnabled)
|
||||||
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mLazyConnEnabled) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.notifier.Send("Error", "Failed to update lazy connection settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleBlockInboundClick() {
|
||||||
|
h.toggleCheckbox(h.client.mBlockInbound)
|
||||||
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mBlockInbound) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.notifier.Send("Error", "Failed to update block inbound settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleNotificationsClick() {
|
||||||
|
h.toggleCheckbox(h.client.mNotifications)
|
||||||
|
if err := h.updateConfigWithErr(); err != nil {
|
||||||
|
h.toggleCheckbox(h.client.mNotifications) // revert checkbox state on error
|
||||||
|
log.Errorf("failed to update config: %v", err)
|
||||||
|
h.client.notifier.Send("Error", "Failed to update notifications settings")
|
||||||
|
} else if h.client.eventManager != nil {
|
||||||
|
h.client.eventManager.SetNotificationsEnabled(h.client.mNotifications.Checked())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleAdvancedSettingsClick() {
|
||||||
|
h.client.mAdvancedSettings.Disable()
|
||||||
|
go func() {
|
||||||
|
defer h.client.mAdvancedSettings.Enable()
|
||||||
|
defer h.client.getSrvConfig()
|
||||||
|
h.runSelfCommand(h.client.ctx, "settings")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleCreateDebugBundleClick() {
|
||||||
|
h.client.mCreateDebugBundle.Disable()
|
||||||
|
go func() {
|
||||||
|
defer h.client.mCreateDebugBundle.Enable()
|
||||||
|
h.runSelfCommand(h.client.ctx, "debug")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleQuitClick() {
|
||||||
|
systray.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleGitHubClick() {
|
||||||
|
if err := openURL("https://github.com/netbirdio/netbird"); err != nil {
|
||||||
|
log.Errorf("failed to open GitHub URL: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleUpdateClick() {
|
||||||
|
h.client.updateIndicationLock.Lock()
|
||||||
|
enforced := h.client.isEnforcedUpdate
|
||||||
|
h.client.updateIndicationLock.Unlock()
|
||||||
|
|
||||||
|
if !enforced {
|
||||||
|
if err := openURL(version.DownloadUrl()); err != nil {
|
||||||
|
log.Errorf("failed to open download URL: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent blocking against a busy server
|
||||||
|
h.client.mUpdate.Disable()
|
||||||
|
go func() {
|
||||||
|
defer h.client.mUpdate.Enable()
|
||||||
|
conn, err := h.client.getSrvClient(defaultFailTimeout)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get service client for update: %v", err)
|
||||||
|
_ = openURL(version.DownloadUrl())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := conn.TriggerUpdate(h.client.ctx, &proto.TriggerUpdateRequest{})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("TriggerUpdate failed: %v", err)
|
||||||
|
_ = openURL(version.DownloadUrl())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !resp.Success {
|
||||||
|
log.Errorf("TriggerUpdate failed: %s", resp.ErrorMsg)
|
||||||
|
_ = openURL(version.DownloadUrl())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("update triggered via daemon")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) handleNetworksClick() {
|
||||||
|
h.client.mNetworks.Disable()
|
||||||
|
go func() {
|
||||||
|
defer h.client.mNetworks.Enable()
|
||||||
|
h.runSelfCommand(h.client.ctx, "networks")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) toggleCheckbox(item *systray.MenuItem) {
|
||||||
|
if item.Checked() {
|
||||||
|
item.Uncheck()
|
||||||
|
} else {
|
||||||
|
item.Check()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) updateConfigWithErr() error {
|
||||||
|
if err := h.client.updateConfig(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) runSelfCommand(ctx context.Context, command string, args ...string) {
|
||||||
|
proc, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error getting executable path: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the full command arguments
|
||||||
|
cmdArgs := []string{
|
||||||
|
fmt.Sprintf("--%s=true", command),
|
||||||
|
fmt.Sprintf("--daemon-addr=%s", h.client.addr),
|
||||||
|
}
|
||||||
|
cmdArgs = append(cmdArgs, args...)
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, proc, cmdArgs...)
|
||||||
|
|
||||||
|
if out := h.client.attachOutput(cmd); out != nil {
|
||||||
|
defer func() {
|
||||||
|
if err := out.Close(); err != nil {
|
||||||
|
log.Errorf("error closing log file %s: %v", h.client.logFile, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("running command: %s", cmd.String())
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
var exitErr *exec.ExitError
|
||||||
|
if errors.As(err, &exitErr) {
|
||||||
|
log.Printf("command '%s' failed with exit code %d", cmd.String(), exitErr.ExitCode())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("command '%s' completed successfully", cmd.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *eventHandler) logout(ctx context.Context) error {
|
||||||
|
client, err := h.client.getSrvClient(defaultFailTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get service client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.Logout(ctx, &proto.LogoutRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("logout failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.client.getSrvConfig()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
30
client/ui/font_bsd.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *serviceClient) setDefaultFonts() {
|
||||||
|
paths := []string{
|
||||||
|
"/usr/local/share/fonts/TTF/DejaVuSans.ttf",
|
||||||
|
"/usr/local/share/fonts/dejavu/DejaVuSans.ttf",
|
||||||
|
"/usr/local/share/noto/NotoSans-Regular.ttf",
|
||||||
|
"/usr/local/share/fonts/noto/NotoSans-Regular.ttf",
|
||||||
|
"/usr/local/share/fonts/liberation-fonts-ttf/LiberationSans-Regular.ttf",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fontPath := range paths {
|
||||||
|
if _, err := os.Stat(fontPath); err == nil {
|
||||||
|
os.Setenv("FYNE_FONT", fontPath)
|
||||||
|
log.Debugf("Using font: %s", fontPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Errorf("Failed to find any suitable font files for %s", runtime.GOOS)
|
||||||
|
}
|
||||||
18
client/ui/font_darwin.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultFontPath = "/Library/Fonts/Arial Unicode.ttf"
|
||||||
|
|
||||||
|
func (s *serviceClient) setDefaultFonts() {
|
||||||
|
if _, err := os.Stat(defaultFontPath); err != nil {
|
||||||
|
log.Errorf("Failed to find default font file: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("FYNE_FONT", defaultFontPath)
|
||||||
|
}
|
||||||
7
client/ui/font_linux.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !386
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func (s *serviceClient) setDefaultFonts() {
|
||||||
|
//TODO: Linux Multiple Language Support
|
||||||
|
}
|
||||||
90
client/ui/font_windows.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *serviceClient) setDefaultFonts() {
|
||||||
|
defaultFontPath := s.getWindowsFontFilePath()
|
||||||
|
|
||||||
|
if _, err := os.Stat(defaultFontPath); err != nil {
|
||||||
|
log.Errorf("Failed to find default font file: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("FYNE_FONT", defaultFontPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) getWindowsFontFilePath() string {
|
||||||
|
var (
|
||||||
|
fontFolder = "C:/Windows/Fonts"
|
||||||
|
fontMapping = map[string]string{
|
||||||
|
"default": "Segoeui.ttf",
|
||||||
|
"zh-CN": "Segoeui.ttf",
|
||||||
|
"am-ET": "Ebrima.ttf",
|
||||||
|
"nirmala": "Nirmala.ttf",
|
||||||
|
"chr-CHER-US": "Gadugi.ttf",
|
||||||
|
"zh-HK": "Segoeui.ttf",
|
||||||
|
"zh-TW": "Segoeui.ttf",
|
||||||
|
"km-KH": "Leelawui.ttf",
|
||||||
|
"ko-KR": "Malgun.ttf",
|
||||||
|
"th-TH": "Leelawui.ttf",
|
||||||
|
"ti-ET": "Ebrima.ttf",
|
||||||
|
}
|
||||||
|
nirMalaLang = []string{
|
||||||
|
"as-IN",
|
||||||
|
"bn-BD",
|
||||||
|
"bn-IN",
|
||||||
|
"gu-IN",
|
||||||
|
"hi-IN",
|
||||||
|
"kn-IN",
|
||||||
|
"kok-IN",
|
||||||
|
"ml-IN",
|
||||||
|
"mr-IN",
|
||||||
|
"ne-NP",
|
||||||
|
"or-IN",
|
||||||
|
"pa-IN",
|
||||||
|
"si-LK",
|
||||||
|
"ta-IN",
|
||||||
|
"te-IN",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// getUserDefaultLocaleName.Call() panics if the func is not found
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Errorf("Recovered from panic: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
getUserDefaultLocaleName := kernel32.NewProc("GetUserDefaultLocaleName")
|
||||||
|
|
||||||
|
buf := make([]uint16, 85) // LOCALE_NAME_MAX_LENGTH is usually 85
|
||||||
|
r, _, err := getUserDefaultLocaleName.Call(uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
|
||||||
|
// returns 0 on failure, err is always non-nil
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getuserdefaultlocalename
|
||||||
|
if r == 0 {
|
||||||
|
log.Errorf("GetUserDefaultLocaleName call failed: %v", err)
|
||||||
|
return path.Join(fontFolder, fontMapping["default"])
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultLanguage := windows.UTF16ToString(buf)
|
||||||
|
|
||||||
|
for _, lang := range nirMalaLang {
|
||||||
|
if defaultLanguage == lang {
|
||||||
|
return path.Join(fontFolder, fontMapping["nirmala"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if font, ok := fontMapping[defaultLanguage]; ok {
|
||||||
|
return path.Join(fontFolder, font)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(fontFolder, fontMapping["default"])
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
dist
|
|
||||||
build
|
|
||||||
node_modules
|
|
||||||
pnpm-lock.yaml
|
|
||||||
wailsjs
|
|
||||||
*.min.js
|
|
||||||
*.min.css
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"tabWidth": 4,
|
|
||||||
"useTabs": false,
|
|
||||||
"semi": true,
|
|
||||||
"singleQuote": false,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"printWidth": 100,
|
|
||||||
"arrowParens": "always",
|
|
||||||
"endOfLine": "lf"
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connection groups the daemon RPCs that drive login / connect / disconnect.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as $models from "./models.js";
|
|
||||||
|
|
||||||
export function Down(): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(70044537);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Login(p: $models.LoginParams): $CancellablePromise<$models.LoginResult> {
|
|
||||||
return $Call.ByID(252661358, p).then(($result: any) => {
|
|
||||||
return $$createType0($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Logout(p: $models.LogoutParams): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(3824847887, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OpenURL launches the user's preferred browser to display url. Mirrors the
|
|
||||||
* Fyne client's openURL helper so the SSO flow can pop the verification page
|
|
||||||
* the same way as the legacy UI — WebKitGTK's window.open is blocked by the
|
|
||||||
* embedded webview, and asking the user to copy/paste defeats the point of
|
|
||||||
* SSO. Honors $BROWSER first, then falls back to the platform default.
|
|
||||||
*/
|
|
||||||
export function OpenURL(url: string): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(3786555598, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Up(p: $models.UpParams): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(3381092588, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WaitSSOLogin(p: $models.WaitSSOParams): $CancellablePromise<string> {
|
|
||||||
return $Call.ByID(1751351500, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = $models.LoginResult.createFrom;
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Debug groups debug / log-level / packet-trace RPCs.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as $models from "./models.js";
|
|
||||||
|
|
||||||
export function Bundle(p: $models.DebugBundleParams): $CancellablePromise<$models.DebugBundleResult> {
|
|
||||||
return $Call.ByID(617551238, p).then(($result: any) => {
|
|
||||||
return $$createType0($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetLogLevel(): $CancellablePromise<$models.LogLevel> {
|
|
||||||
return $Call.ByID(3832950014).then(($result: any) => {
|
|
||||||
return $$createType1($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RevealFile opens the OS file manager focused on the given path. Wails'
|
|
||||||
* Browser.OpenURL refuses non-http(s) schemes, so the UI calls this binding
|
|
||||||
* instead of constructing a file:// URL.
|
|
||||||
*/
|
|
||||||
export function RevealFile(path: string): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(2620662837, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SetLogLevel(lvl: $models.LogLevel): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(4122411498, lvl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = $models.DebugBundleResult.createFrom;
|
|
||||||
const $$createType1 = $models.LogLevel.createFrom;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forwarding groups the daemon RPCs that surface exposed/forwarded services.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as $models from "./models.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List returns the current set of forwarding rules from the daemon's
|
|
||||||
* reverse proxy. The frontend renders these as the "exposed services" list.
|
|
||||||
*/
|
|
||||||
export function List(): $CancellablePromise<$models.ForwardingRule[]> {
|
|
||||||
return $Call.ByID(3831092172).then(($result: any) => {
|
|
||||||
return $$createType1($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = $models.ForwardingRule.createFrom;
|
|
||||||
const $$createType1 = $Create.Array($$createType0);
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
import * as Connection from "./connection.js";
|
|
||||||
import * as Debug from "./debug.js";
|
|
||||||
import * as Forwarding from "./forwarding.js";
|
|
||||||
import * as Networks from "./networks.js";
|
|
||||||
import * as Peers from "./peers.js";
|
|
||||||
import * as Profiles from "./profiles.js";
|
|
||||||
import * as Settings from "./settings.js";
|
|
||||||
import * as Update from "./update.js";
|
|
||||||
export {
|
|
||||||
Connection,
|
|
||||||
Debug,
|
|
||||||
Forwarding,
|
|
||||||
Networks,
|
|
||||||
Peers,
|
|
||||||
Profiles,
|
|
||||||
Settings,
|
|
||||||
Update
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
ActiveProfile,
|
|
||||||
Config,
|
|
||||||
ConfigParams,
|
|
||||||
DebugBundleParams,
|
|
||||||
DebugBundleResult,
|
|
||||||
Features,
|
|
||||||
ForwardingRule,
|
|
||||||
LocalPeer,
|
|
||||||
LogLevel,
|
|
||||||
LoginParams,
|
|
||||||
LoginResult,
|
|
||||||
LogoutParams,
|
|
||||||
Network,
|
|
||||||
PeerLink,
|
|
||||||
PeerStatus,
|
|
||||||
PortInfo,
|
|
||||||
PortRange,
|
|
||||||
Profile,
|
|
||||||
ProfileRef,
|
|
||||||
SelectNetworksParams,
|
|
||||||
SetConfigParams,
|
|
||||||
Status,
|
|
||||||
SystemEvent,
|
|
||||||
UpParams,
|
|
||||||
UpdateAvailable,
|
|
||||||
UpdateProgress,
|
|
||||||
UpdateResult,
|
|
||||||
WaitSSOParams
|
|
||||||
} from "./models.js";
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Networks groups the daemon RPCs that read and toggle routed networks.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as $models from "./models.js";
|
|
||||||
|
|
||||||
export function Deselect(p: $models.SelectNetworksParams): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(3382210947, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function List(): $CancellablePromise<$models.Network[]> {
|
|
||||||
return $Call.ByID(1550842096).then(($result: any) => {
|
|
||||||
return $$createType1($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Select(p: $models.SelectNetworksParams): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(1339338400, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = $models.Network.createFrom;
|
|
||||||
const $$createType1 = $Create.Array($$createType0);
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Peers serves the dashboard data: one polled Status RPC and a long-running
|
|
||||||
* SubscribeEvents stream that re-emits every event over the Wails event bus.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as $models from "./models.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get returns the current daemon status snapshot.
|
|
||||||
*/
|
|
||||||
export function Get(): $CancellablePromise<$models.Status> {
|
|
||||||
return $Call.ByID(3266051360).then(($result: any) => {
|
|
||||||
return $$createType0($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch starts the background loops that feed the frontend:
|
|
||||||
* - statusStreamLoop: push-driven snapshots on connection-state change
|
|
||||||
* (Connected/Disconnected/Connecting, peer list, address). Drives the
|
|
||||||
* tray icon, Status page, and Peers page.
|
|
||||||
* - toastStreamLoop: DNS / network / auth / connectivity / update
|
|
||||||
* SystemEvent stream. Drives OS notifications, the Recent Events
|
|
||||||
* list, and the update-overlay flag. The daemon-side RPC is named
|
|
||||||
* SubscribeEvents — only the loop's local alias differs to keep the
|
|
||||||
* two streams distinguishable in this file.
|
|
||||||
*
|
|
||||||
* Safe to call once at boot; both loops self-restart on stream errors
|
|
||||||
* via exponential backoff.
|
|
||||||
*/
|
|
||||||
export function Watch(): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(2799871735);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = $models.Status.createFrom;
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Profiles groups the daemon RPCs that manage named profiles.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as $models from "./models.js";
|
|
||||||
|
|
||||||
export function Add(p: $models.ProfileRef): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(722930578, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetActive(): $CancellablePromise<$models.ActiveProfile> {
|
|
||||||
return $Call.ByID(3458449443).then(($result: any) => {
|
|
||||||
return $$createType0($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function List(username: string): $CancellablePromise<$models.Profile[]> {
|
|
||||||
return $Call.ByID(3702185167, username).then(($result: any) => {
|
|
||||||
return $$createType2($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Remove(p: $models.ProfileRef): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(2365690315, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Switch(p: $models.ProfileRef): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(3209858855, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Username returns the OS username the daemon expects for profile lookups.
|
|
||||||
* The frontend calls this once at boot and reuses the result.
|
|
||||||
*/
|
|
||||||
export function Username(): $CancellablePromise<string> {
|
|
||||||
return $Call.ByID(262345647);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = $models.ActiveProfile.createFrom;
|
|
||||||
const $$createType1 = $models.Profile.createFrom;
|
|
||||||
const $$createType2 = $Create.Array($$createType1);
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Settings groups the daemon RPCs that read and write the daemon config.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as $models from "./models.js";
|
|
||||||
|
|
||||||
export function GetConfig(p: $models.ConfigParams): $CancellablePromise<$models.Config> {
|
|
||||||
return $Call.ByID(59246988, p).then(($result: any) => {
|
|
||||||
return $$createType0($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetFeatures(): $CancellablePromise<$models.Features> {
|
|
||||||
return $Call.ByID(2056724965).then(($result: any) => {
|
|
||||||
return $$createType1($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SetConfig(p: $models.SetConfigParams): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(26939944, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = $models.Config.createFrom;
|
|
||||||
const $$createType1 = $models.Features.createFrom;
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update groups the RPCs that drive the enforced-update install flow.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as $models from "./models.js";
|
|
||||||
|
|
||||||
export function GetInstallerResult(): $CancellablePromise<$models.UpdateResult> {
|
|
||||||
return $Call.ByID(2533624807).then(($result: any) => {
|
|
||||||
return $$createType0($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quit asks the host application to exit. The /update page calls this once
|
|
||||||
* the daemon-side installer has reported success, mirroring the legacy
|
|
||||||
* Fyne UI's app.Quit() in showInstallerResult. Schedules the actual exit
|
|
||||||
* off the calling goroutine so the JS-side caller's response can return
|
|
||||||
* before the runtime tears down.
|
|
||||||
*/
|
|
||||||
export function Quit(): $CancellablePromise<void> {
|
|
||||||
return $Call.ByID(409602657);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Trigger(): $CancellablePromise<$models.UpdateResult> {
|
|
||||||
return $Call.ByID(166270378).then(($result: any) => {
|
|
||||||
return $$createType0($result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = $models.UpdateResult.createFrom;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
//@ts-check
|
|
||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import { Create as $Create } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import * as services$0 from "../../../../netbirdio/netbird/client/ui/services/models.js";
|
|
||||||
|
|
||||||
function configure() {
|
|
||||||
Object.freeze(Object.assign($Create.Events, {
|
|
||||||
"netbird:event": $$createType0,
|
|
||||||
"netbird:status": $$createType1,
|
|
||||||
"netbird:update:available": $$createType2,
|
|
||||||
"netbird:update:progress": $$createType3,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private type creation functions
|
|
||||||
const $$createType0 = services$0.SystemEvent.createFrom;
|
|
||||||
const $$createType1 = services$0.Status.createFrom;
|
|
||||||
const $$createType2 = services$0.UpdateAvailable.createFrom;
|
|
||||||
const $$createType3 = services$0.UpdateProgress.createFrom;
|
|
||||||
|
|
||||||
configure();
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
||||||
// This file is automatically generated. DO NOT EDIT
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import type { Events } from "@wailsio/runtime";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore: Unused imports
|
|
||||||
import type * as services$0 from "../../../../netbirdio/netbird/client/ui/services/models.js";
|
|
||||||
|
|
||||||
declare module "@wailsio/runtime" {
|
|
||||||
namespace Events {
|
|
||||||
interface CustomEvents {
|
|
||||||
"netbird:event": services$0.SystemEvent;
|
|
||||||
"netbird:status": services$0.Status;
|
|
||||||
"netbird:update:available": services$0.UpdateAvailable;
|
|
||||||
"netbird:update:progress": services$0.UpdateProgress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||