mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-29 13:46:41 +00:00
Compare commits
4 Commits
relay-serv
...
fix-darwin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d359b08099 | ||
|
|
7eba5dafd8 | ||
|
|
28fe26637b | ||
|
|
407e9d304b |
154
.github/workflows/release.yml
vendored
154
.github/workflows/release.yml
vendored
@@ -115,6 +115,12 @@ jobs:
|
|||||||
|
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest-m
|
runs-on: ubuntu-latest-m
|
||||||
|
outputs:
|
||||||
|
release_artifact_url: ${{ steps.upload_release.outputs.artifact-url }}
|
||||||
|
linux_packages_artifact_url: ${{ steps.upload_linux_packages.outputs.artifact-url }}
|
||||||
|
windows_packages_artifact_url: ${{ steps.upload_windows_packages.outputs.artifact-url }}
|
||||||
|
macos_packages_artifact_url: ${{ steps.upload_macos_packages.outputs.artifact-url }}
|
||||||
|
ghcr_images: ${{ steps.tag_and_push_images.outputs.images_markdown }}
|
||||||
env:
|
env:
|
||||||
flags: ""
|
flags: ""
|
||||||
steps:
|
steps:
|
||||||
@@ -213,10 +219,13 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
run: rm -f /tmp/gpg-rpm-signing-key.asc
|
run: rm -f /tmp/gpg-rpm-signing-key.asc
|
||||||
- name: Tag and push images (amd64 only)
|
- name: Tag and push images (amd64 only)
|
||||||
|
id: tag_and_push_images
|
||||||
if: |
|
if: |
|
||||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||||
(github.event_name == 'push' && github.ref == 'refs/heads/main')
|
(github.event_name == 'push' && github.ref == 'refs/heads/main')
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
resolve_tags() {
|
resolve_tags() {
|
||||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
echo "pr-${{ github.event.pull_request.number }}"
|
echo "pr-${{ github.event.pull_request.number }}"
|
||||||
@@ -225,6 +234,17 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ghcr_package_url() {
|
||||||
|
local image="$1" package encoded_package
|
||||||
|
package="${image#ghcr.io/}"
|
||||||
|
package="${package#*/}"
|
||||||
|
package="${package%%:*}"
|
||||||
|
encoded_package="${package//\//%2F}"
|
||||||
|
echo "https://github.com/orgs/netbirdio/packages/container/package/${encoded_package}"
|
||||||
|
}
|
||||||
|
|
||||||
|
image_refs=()
|
||||||
|
|
||||||
tag_and_push() {
|
tag_and_push() {
|
||||||
local src="$1" img_name tag dst
|
local src="$1" img_name tag dst
|
||||||
img_name="${src%%:*}"
|
img_name="${src%%:*}"
|
||||||
@@ -233,35 +253,56 @@ jobs:
|
|||||||
echo "Tagging ${src} -> ${dst}"
|
echo "Tagging ${src} -> ${dst}"
|
||||||
docker tag "$src" "$dst"
|
docker tag "$src" "$dst"
|
||||||
docker push "$dst"
|
docker push "$dst"
|
||||||
|
image_refs+=("$dst")
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
export -f tag_and_push resolve_tags
|
cat > /tmp/goreleaser-artifacts.json <<'JSON'
|
||||||
|
${{ steps.goreleaser.outputs.artifacts }}
|
||||||
|
JSON
|
||||||
|
|
||||||
echo '${{ steps.goreleaser.outputs.artifacts }}' | \
|
mapfile -t src_images < <(
|
||||||
jq -r '.[] | select(.type == "Docker Image") | select(.goarch == "amd64") | .name' | \
|
jq -r '.[] | select(.type == "Docker Image") | select(.goarch == "amd64") | .name | select(startswith("ghcr.io/"))' /tmp/goreleaser-artifacts.json
|
||||||
grep '^ghcr.io/' | while read -r SRC; do
|
)
|
||||||
tag_and_push "$SRC"
|
|
||||||
|
for src in "${src_images[@]}"; do
|
||||||
|
tag_and_push "$src"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "images_markdown<<EOF"
|
||||||
|
if [[ ${#image_refs[@]} -eq 0 ]]; then
|
||||||
|
echo "_No GHCR images were pushed._"
|
||||||
|
else
|
||||||
|
printf '%s\n' "${image_refs[@]}" | sort -u | while read -r image; do
|
||||||
|
printf -- '- [`%s`](%s)\n' "$image" "$(ghcr_package_url "$image")"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
- name: upload non tags for debug purposes
|
- name: upload non tags for debug purposes
|
||||||
|
id: upload_release
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release
|
name: release
|
||||||
path: dist/
|
path: dist/
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: upload linux packages
|
- name: upload linux packages
|
||||||
|
id: upload_linux_packages
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux-packages
|
name: linux-packages
|
||||||
path: dist/netbird_linux**
|
path: dist/netbird_linux**
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: upload windows packages
|
- name: upload windows packages
|
||||||
|
id: upload_windows_packages
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-packages
|
name: windows-packages
|
||||||
path: dist/netbird_windows**
|
path: dist/netbird_windows**
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: upload macos packages
|
- name: upload macos packages
|
||||||
|
id: upload_macos_packages
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-packages
|
name: macos-packages
|
||||||
@@ -270,6 +311,8 @@ jobs:
|
|||||||
|
|
||||||
release_ui:
|
release_ui:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
release_ui_artifact_url: ${{ steps.upload_release_ui.outputs.artifact-url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Parse semver string
|
- name: Parse semver string
|
||||||
id: semver_parser
|
id: semver_parser
|
||||||
@@ -360,6 +403,7 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
run: rm -f /tmp/gpg-rpm-signing-key.asc
|
run: rm -f /tmp/gpg-rpm-signing-key.asc
|
||||||
- name: upload non tags for debug purposes
|
- name: upload non tags for debug purposes
|
||||||
|
id: upload_release_ui
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release-ui
|
name: release-ui
|
||||||
@@ -368,6 +412,8 @@ jobs:
|
|||||||
|
|
||||||
release_ui_darwin:
|
release_ui_darwin:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
outputs:
|
||||||
|
release_ui_darwin_artifact_url: ${{ steps.upload_release_ui_darwin.outputs.artifact-url }}
|
||||||
steps:
|
steps:
|
||||||
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||||
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
run: echo "flags=--snapshot" >> $GITHUB_ENV
|
||||||
@@ -402,12 +448,110 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: upload non tags for debug purposes
|
- name: upload non tags for debug purposes
|
||||||
|
id: upload_release_ui_darwin
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release-ui-darwin
|
name: release-ui-darwin
|
||||||
path: dist/
|
path: dist/
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
|
||||||
|
comment_release_artifacts:
|
||||||
|
name: Comment release artifacts
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [release, release_ui, release_ui_darwin]
|
||||||
|
if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Create or update PR comment
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
env:
|
||||||
|
RELEASE_RESULT: ${{ needs.release.result }}
|
||||||
|
RELEASE_UI_RESULT: ${{ needs.release_ui.result }}
|
||||||
|
RELEASE_UI_DARWIN_RESULT: ${{ needs.release_ui_darwin.result }}
|
||||||
|
RELEASE_ARTIFACT_URL: ${{ needs.release.outputs.release_artifact_url }}
|
||||||
|
LINUX_PACKAGES_ARTIFACT_URL: ${{ needs.release.outputs.linux_packages_artifact_url }}
|
||||||
|
WINDOWS_PACKAGES_ARTIFACT_URL: ${{ needs.release.outputs.windows_packages_artifact_url }}
|
||||||
|
MACOS_PACKAGES_ARTIFACT_URL: ${{ needs.release.outputs.macos_packages_artifact_url }}
|
||||||
|
RELEASE_UI_ARTIFACT_URL: ${{ needs.release_ui.outputs.release_ui_artifact_url }}
|
||||||
|
RELEASE_UI_DARWIN_ARTIFACT_URL: ${{ needs.release_ui_darwin.outputs.release_ui_darwin_artifact_url }}
|
||||||
|
GHCR_IMAGES_MARKDOWN: ${{ needs.release.outputs.ghcr_images }}
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const marker = '<!-- netbird-release-artifacts -->';
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const issue_number = context.payload.pull_request.number;
|
||||||
|
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
|
||||||
|
const shortSha = context.payload.pull_request.head.sha.slice(0, 7);
|
||||||
|
|
||||||
|
const artifactCell = (url, result) => {
|
||||||
|
if (url) return `[Download](${url})`;
|
||||||
|
return result && result !== 'success' ? `_Not available (${result})_` : '_Not available_';
|
||||||
|
};
|
||||||
|
|
||||||
|
const artifacts = [
|
||||||
|
['All release artifacts', process.env.RELEASE_ARTIFACT_URL, process.env.RELEASE_RESULT],
|
||||||
|
['Linux packages', process.env.LINUX_PACKAGES_ARTIFACT_URL, process.env.RELEASE_RESULT],
|
||||||
|
['Windows packages', process.env.WINDOWS_PACKAGES_ARTIFACT_URL, process.env.RELEASE_RESULT],
|
||||||
|
['macOS packages', process.env.MACOS_PACKAGES_ARTIFACT_URL, process.env.RELEASE_RESULT],
|
||||||
|
['UI artifacts', process.env.RELEASE_UI_ARTIFACT_URL, process.env.RELEASE_UI_RESULT],
|
||||||
|
['UI macOS artifacts', process.env.RELEASE_UI_DARWIN_ARTIFACT_URL, process.env.RELEASE_UI_DARWIN_RESULT],
|
||||||
|
];
|
||||||
|
|
||||||
|
const artifactRows = artifacts
|
||||||
|
.map(([name, url, result]) => `| ${name} | ${artifactCell(url, result)} |`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const ghcrImages = (process.env.GHCR_IMAGES_MARKDOWN || '').trim() || '_No GHCR images were pushed._';
|
||||||
|
|
||||||
|
const body = [
|
||||||
|
marker,
|
||||||
|
'## Release artifacts',
|
||||||
|
'',
|
||||||
|
`Built for PR head \`${shortSha}\` in [workflow run #${process.env.GITHUB_RUN_NUMBER}](${runUrl}).`,
|
||||||
|
'',
|
||||||
|
'| Artifact | Link |',
|
||||||
|
'| --- | --- |',
|
||||||
|
artifactRows,
|
||||||
|
'',
|
||||||
|
'### GHCR images (amd64)',
|
||||||
|
ghcrImages,
|
||||||
|
'',
|
||||||
|
'_This comment is updated by the Release workflow. Artifact links expire according to the workflow retention policy._',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number,
|
||||||
|
per_page: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const previous = comments.find(comment =>
|
||||||
|
comment.user?.type === 'Bot' && comment.body?.includes(marker)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (previous) {
|
||||||
|
await github.rest.issues.updateComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: previous.id,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
core.info(`Updated release artifacts comment ${previous.id}`);
|
||||||
|
} else {
|
||||||
|
const { data } = await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
core.info(`Created release artifacts comment ${data.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
trigger_signer:
|
trigger_signer:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [release, release_ui, release_ui_darwin]
|
needs: [release, release_ui, release_ui_darwin]
|
||||||
|
|||||||
@@ -200,9 +200,17 @@ Pop $0
|
|||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
Function .onInit
|
Function .onInit
|
||||||
SetRegView 64
|
|
||||||
StrCpy $INSTDIR "${INSTALL_DIR}"
|
StrCpy $INSTDIR "${INSTALL_DIR}"
|
||||||
|
|
||||||
|
; Pre-0.70.1 installers ran without SetRegView, so their uninstall keys live
|
||||||
|
; in the 32-bit view. Fall back to it so upgrades still find them.
|
||||||
|
SetRegView 64
|
||||||
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\$(^NAME)" "UninstallString"
|
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\$(^NAME)" "UninstallString"
|
||||||
|
${If} $R0 == ""
|
||||||
|
SetRegView 32
|
||||||
|
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\$(^NAME)" "UninstallString"
|
||||||
|
SetRegView 64
|
||||||
|
${EndIf}
|
||||||
${If} $R0 != ""
|
${If} $R0 != ""
|
||||||
# if silent install jump to uninstall step
|
# if silent install jump to uninstall step
|
||||||
IfSilent uninstall
|
IfSilent uninstall
|
||||||
|
|||||||
@@ -89,9 +89,17 @@ func (r *SysOps) installScopedDefaultFor(unspec netip.Addr) (bool, error) {
|
|||||||
return false, fmt.Errorf("unusable default nexthop for %s (no interface)", unspec)
|
return false, fmt.Errorf("unusable default nexthop for %s (no interface)", unspec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reused := false
|
||||||
if err := r.addScopedDefault(unspec, nexthop); err != nil {
|
if err := r.addScopedDefault(unspec, nexthop); err != nil {
|
||||||
|
if !errors.Is(err, unix.EEXIST) {
|
||||||
return false, fmt.Errorf("add scoped default on %s: %w", nexthop.Intf.Name, err)
|
return false, fmt.Errorf("add scoped default on %s: %w", nexthop.Intf.Name, err)
|
||||||
}
|
}
|
||||||
|
// macOS installs its own RTF_IFSCOPE defaults for primary service
|
||||||
|
// selection on multi-NIC setups, so a route on this ifindex can
|
||||||
|
// already exist before we try. Binding to it via IP[V6]_BOUND_IF
|
||||||
|
// still produces the scoped lookup we need.
|
||||||
|
reused = true
|
||||||
|
}
|
||||||
|
|
||||||
af := unix.AF_INET
|
af := unix.AF_INET
|
||||||
if unspec.Is6() {
|
if unspec.Is6() {
|
||||||
@@ -102,7 +110,11 @@ func (r *SysOps) installScopedDefaultFor(unspec netip.Addr) (bool, error) {
|
|||||||
if nexthop.IP.IsValid() {
|
if nexthop.IP.IsValid() {
|
||||||
via = nexthop.IP.String()
|
via = nexthop.IP.String()
|
||||||
}
|
}
|
||||||
log.Infof("installed scoped default route via %s on %s for %s", via, nexthop.Intf.Name, afOf(unspec))
|
verb := "installed"
|
||||||
|
if reused {
|
||||||
|
verb = "reused existing"
|
||||||
|
}
|
||||||
|
log.Infof("%s scoped default route via %s on %s for %s", verb, via, nexthop.Intf.Name, afOf(unspec))
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,217 +2,358 @@
|
|||||||
|
|
||||||
package sleep
|
package sleep
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo LDFLAGS: -framework IOKit -framework CoreFoundation
|
|
||||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
|
||||||
#include <IOKit/IOMessage.h>
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
|
||||||
|
|
||||||
extern void sleepCallbackBridge();
|
|
||||||
extern void poweredOnCallbackBridge();
|
|
||||||
extern void suspendedCallbackBridge();
|
|
||||||
extern void resumedCallbackBridge();
|
|
||||||
|
|
||||||
|
|
||||||
// C global variables for IOKit state
|
|
||||||
static IONotificationPortRef g_notifyPortRef = NULL;
|
|
||||||
static io_object_t g_notifierObject = 0;
|
|
||||||
static io_object_t g_generalInterestNotifier = 0;
|
|
||||||
static io_connect_t g_rootPort = 0;
|
|
||||||
static CFRunLoopRef g_runLoop = NULL;
|
|
||||||
|
|
||||||
static void sleepCallback(void* refCon, io_service_t service, natural_t messageType, void* messageArgument) {
|
|
||||||
switch (messageType) {
|
|
||||||
case kIOMessageSystemWillSleep:
|
|
||||||
sleepCallbackBridge();
|
|
||||||
IOAllowPowerChange(g_rootPort, (long)messageArgument);
|
|
||||||
break;
|
|
||||||
case kIOMessageSystemHasPoweredOn:
|
|
||||||
poweredOnCallbackBridge();
|
|
||||||
break;
|
|
||||||
case kIOMessageServiceIsSuspended:
|
|
||||||
suspendedCallbackBridge();
|
|
||||||
break;
|
|
||||||
case kIOMessageServiceIsResumed:
|
|
||||||
resumedCallbackBridge();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void registerNotifications() {
|
|
||||||
g_rootPort = IORegisterForSystemPower(
|
|
||||||
NULL,
|
|
||||||
&g_notifyPortRef,
|
|
||||||
(IOServiceInterestCallback)sleepCallback,
|
|
||||||
&g_notifierObject
|
|
||||||
);
|
|
||||||
|
|
||||||
if (g_rootPort == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CFRunLoopAddSource(CFRunLoopGetCurrent(),
|
|
||||||
IONotificationPortGetRunLoopSource(g_notifyPortRef),
|
|
||||||
kCFRunLoopCommonModes);
|
|
||||||
|
|
||||||
g_runLoop = CFRunLoopGetCurrent();
|
|
||||||
CFRunLoopRun();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unregisterNotifications() {
|
|
||||||
CFRunLoopRemoveSource(g_runLoop,
|
|
||||||
IONotificationPortGetRunLoopSource(g_notifyPortRef),
|
|
||||||
kCFRunLoopCommonModes);
|
|
||||||
|
|
||||||
IODeregisterForSystemPower(&g_notifierObject);
|
|
||||||
IOServiceClose(g_rootPort);
|
|
||||||
IONotificationPortDestroy(g_notifyPortRef);
|
|
||||||
CFRunLoopStop(g_runLoop);
|
|
||||||
|
|
||||||
g_notifyPortRef = NULL;
|
|
||||||
g_notifierObject = 0;
|
|
||||||
g_rootPort = 0;
|
|
||||||
g_runLoop = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/ebitengine/purego"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// IOKit message types from IOKit/IOMessage.h.
|
||||||
serviceRegistry = make(map[*Detector]struct{})
|
const (
|
||||||
serviceRegistryMu sync.Mutex
|
kIOMessageCanSystemSleep uintptr = 0xe0000270
|
||||||
|
kIOMessageSystemWillSleep uintptr = 0xe0000280
|
||||||
|
kIOMessageSystemHasPoweredOn uintptr = 0xe0000300
|
||||||
)
|
)
|
||||||
|
|
||||||
//export sleepCallbackBridge
|
var (
|
||||||
func sleepCallbackBridge() {
|
ioKit iokitFuncs
|
||||||
log.Info("sleepCallbackBridge event triggered")
|
cf cfFuncs
|
||||||
|
cfCommonModes uintptr
|
||||||
|
|
||||||
serviceRegistryMu.Lock()
|
libInitOnce sync.Once
|
||||||
defer serviceRegistryMu.Unlock()
|
libInitErr error
|
||||||
|
|
||||||
for svc := range serviceRegistry {
|
// callbackThunk is the single C-callable trampoline registered with IOKit.
|
||||||
svc.triggerCallback(EventTypeSleep)
|
callbackThunk uintptr
|
||||||
}
|
|
||||||
|
serviceRegistry = make(map[*Detector]struct{})
|
||||||
|
serviceRegistryMu sync.Mutex
|
||||||
|
session *runLoopSession
|
||||||
|
|
||||||
|
// lifecycleMu serializes Register/Deregister so a new registration can't
|
||||||
|
// start a second runloop while a previous teardown is still pending.
|
||||||
|
lifecycleMu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// iokitFuncs holds IOKit symbols resolved once at init.
|
||||||
|
type iokitFuncs struct {
|
||||||
|
IORegisterForSystemPower func(refcon uintptr, portRef *uintptr, callback uintptr, notifier *uintptr) uintptr
|
||||||
|
IODeregisterForSystemPower func(notifier *uintptr) int32
|
||||||
|
IOAllowPowerChange func(kernelPort uintptr, notificationID uintptr) int32
|
||||||
|
IOServiceClose func(connect uintptr) int32
|
||||||
|
IONotificationPortGetRunLoopSource func(port uintptr) uintptr
|
||||||
|
IONotificationPortDestroy func(port uintptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export resumedCallbackBridge
|
// cfFuncs holds CoreFoundation symbols resolved once at init.
|
||||||
func resumedCallbackBridge() {
|
type cfFuncs struct {
|
||||||
log.Info("resumedCallbackBridge event triggered")
|
CFRunLoopGetCurrent func() uintptr
|
||||||
|
CFRunLoopRun func()
|
||||||
|
CFRunLoopStop func(rl uintptr)
|
||||||
|
CFRunLoopAddSource func(rl, source, mode uintptr)
|
||||||
|
CFRunLoopRemoveSource func(rl, source, mode uintptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export suspendedCallbackBridge
|
// runLoopSession bundles the handles owned by one CFRunLoop lifetime. A nil
|
||||||
func suspendedCallbackBridge() {
|
// session means no runloop is active and the next Register must start one.
|
||||||
log.Info("suspendedCallbackBridge event triggered")
|
type runLoopSession struct {
|
||||||
|
rl uintptr
|
||||||
|
port uintptr
|
||||||
|
notifier uintptr
|
||||||
|
rp uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
//export poweredOnCallbackBridge
|
// detectorSnapshot pins a detector's callback and done channel so dispatch
|
||||||
func poweredOnCallbackBridge() {
|
// runs with values valid at snapshot time, even if a concurrent
|
||||||
log.Info("poweredOnCallbackBridge event triggered")
|
// Deregister/Register rewrites the detector's fields.
|
||||||
serviceRegistryMu.Lock()
|
type detectorSnapshot struct {
|
||||||
defer serviceRegistryMu.Unlock()
|
detector *Detector
|
||||||
|
callback func(event EventType)
|
||||||
for svc := range serviceRegistry {
|
done <-chan struct{}
|
||||||
svc.triggerCallback(EventTypeWakeUp)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detector delivers sleep and wake events to a registered callback.
|
||||||
type Detector struct {
|
type Detector struct {
|
||||||
callback func(event EventType)
|
callback func(event EventType)
|
||||||
ctx context.Context
|
done chan struct{}
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDetector() (*Detector, error) {
|
|
||||||
return &Detector{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register installs callback for power events. The first registration starts
|
||||||
|
// the CFRunLoop on a dedicated OS-locked thread and blocks until IOKit
|
||||||
|
// registration succeeds or fails; subsequent registrations just add to the
|
||||||
|
// dispatch set.
|
||||||
func (d *Detector) Register(callback func(event EventType)) error {
|
func (d *Detector) Register(callback func(event EventType)) error {
|
||||||
serviceRegistryMu.Lock()
|
lifecycleMu.Lock()
|
||||||
defer serviceRegistryMu.Unlock()
|
defer lifecycleMu.Unlock()
|
||||||
|
|
||||||
|
serviceRegistryMu.Lock()
|
||||||
if _, exists := serviceRegistry[d]; exists {
|
if _, exists := serviceRegistry[d]; exists {
|
||||||
|
serviceRegistryMu.Unlock()
|
||||||
return fmt.Errorf("detector service already registered")
|
return fmt.Errorf("detector service already registered")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.callback = callback
|
d.callback = callback
|
||||||
|
d.done = make(chan struct{})
|
||||||
d.ctx, d.cancel = context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
if len(serviceRegistry) > 0 {
|
|
||||||
serviceRegistry[d] = struct{}{}
|
serviceRegistry[d] = struct{}{}
|
||||||
|
needSetup := session == nil
|
||||||
|
serviceRegistryMu.Unlock()
|
||||||
|
|
||||||
|
if !needSetup {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceRegistry[d] = struct{}{}
|
errCh := make(chan error, 1)
|
||||||
|
go runRunLoop(errCh)
|
||||||
// CFRunLoop must run on a single fixed OS thread
|
if err := <-errCh; err != nil {
|
||||||
go func() {
|
serviceRegistryMu.Lock()
|
||||||
runtime.LockOSThread()
|
delete(serviceRegistry, d)
|
||||||
defer runtime.UnlockOSThread()
|
close(d.done)
|
||||||
|
d.done = nil
|
||||||
C.registerNotifications()
|
serviceRegistryMu.Unlock()
|
||||||
}()
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("sleep detection service started on macOS")
|
log.Info("sleep detection service started on macOS")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deregister removes the detector. When the last detector is removed, IOKit registration is torn down
|
// Deregister removes the detector. When the last detector leaves, IOKit
|
||||||
// and the runloop is stopped and cleaned up.
|
// notifications are torn down and the runloop is stopped.
|
||||||
func (d *Detector) Deregister() error {
|
func (d *Detector) Deregister() error {
|
||||||
|
lifecycleMu.Lock()
|
||||||
|
defer lifecycleMu.Unlock()
|
||||||
|
|
||||||
serviceRegistryMu.Lock()
|
serviceRegistryMu.Lock()
|
||||||
defer serviceRegistryMu.Unlock()
|
if _, exists := serviceRegistry[d]; !exists {
|
||||||
_, exists := serviceRegistry[d]
|
serviceRegistryMu.Unlock()
|
||||||
if !exists {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
close(d.done)
|
||||||
// cancel and remove this detector
|
|
||||||
d.cancel()
|
|
||||||
delete(serviceRegistry, d)
|
delete(serviceRegistry, d)
|
||||||
|
|
||||||
// If other Detectors still exist, leave IOKit running
|
|
||||||
if len(serviceRegistry) > 0 {
|
if len(serviceRegistry) > 0 {
|
||||||
|
serviceRegistryMu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
sess := session
|
||||||
|
serviceRegistryMu.Unlock()
|
||||||
|
|
||||||
log.Info("sleep detection service stopping (deregister)")
|
log.Info("sleep detection service stopping (deregister)")
|
||||||
|
|
||||||
// Deregister IOKit notifications, stop runloop, and free resources
|
if sess == nil {
|
||||||
C.unregisterNotifications()
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sess.rl != 0 && sess.port != 0 {
|
||||||
|
source := ioKit.IONotificationPortGetRunLoopSource(sess.port)
|
||||||
|
cf.CFRunLoopRemoveSource(sess.rl, source, cfCommonModes)
|
||||||
|
}
|
||||||
|
if sess.notifier != 0 {
|
||||||
|
n := sess.notifier
|
||||||
|
ioKit.IODeregisterForSystemPower(&n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear session only after IODeregisterForSystemPower returns so any
|
||||||
|
// in-flight powerCallback can still look up session.rp to ack sleep.
|
||||||
|
serviceRegistryMu.Lock()
|
||||||
|
session = nil
|
||||||
|
serviceRegistryMu.Unlock()
|
||||||
|
|
||||||
|
if sess.rp != 0 {
|
||||||
|
ioKit.IOServiceClose(sess.rp)
|
||||||
|
}
|
||||||
|
if sess.port != 0 {
|
||||||
|
ioKit.IONotificationPortDestroy(sess.port)
|
||||||
|
}
|
||||||
|
if sess.rl != 0 {
|
||||||
|
cf.CFRunLoopStop(sess.rl)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Detector) triggerCallback(event EventType) {
|
func (d *Detector) triggerCallback(event EventType, cb func(event EventType), done <-chan struct{}) {
|
||||||
doneChan := make(chan struct{})
|
if cb == nil || done == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
doneChan := make(chan struct{})
|
||||||
timeout := time.NewTimer(500 * time.Millisecond)
|
timeout := time.NewTimer(500 * time.Millisecond)
|
||||||
defer timeout.Stop()
|
defer timeout.Stop()
|
||||||
|
|
||||||
cb := d.callback
|
go func() {
|
||||||
go func(callback func(event EventType)) {
|
defer close(doneChan)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Errorf("panic in sleep callback: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
log.Info("sleep detection event fired")
|
log.Info("sleep detection event fired")
|
||||||
callback(event)
|
cb(event)
|
||||||
close(doneChan)
|
}()
|
||||||
}(cb)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-doneChan:
|
case <-doneChan:
|
||||||
case <-d.ctx.Done():
|
case <-done:
|
||||||
case <-timeout.C:
|
case <-timeout.C:
|
||||||
log.Warnf("sleep callback timed out")
|
log.Warn("sleep callback timed out")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDetector initializes IOKit/CoreFoundation bindings and returns a Detector.
|
||||||
|
func NewDetector() (*Detector, error) {
|
||||||
|
if err := initLibs(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Detector{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initLibs() error {
|
||||||
|
libInitOnce.Do(func() {
|
||||||
|
iokit, err := purego.Dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||||
|
if err != nil {
|
||||||
|
libInitErr = fmt.Errorf("dlopen IOKit: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfLib, err := purego.Dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||||
|
if err != nil {
|
||||||
|
libInitErr = fmt.Errorf("dlopen CoreFoundation: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
purego.RegisterLibFunc(&ioKit.IORegisterForSystemPower, iokit, "IORegisterForSystemPower")
|
||||||
|
purego.RegisterLibFunc(&ioKit.IODeregisterForSystemPower, iokit, "IODeregisterForSystemPower")
|
||||||
|
purego.RegisterLibFunc(&ioKit.IOAllowPowerChange, iokit, "IOAllowPowerChange")
|
||||||
|
purego.RegisterLibFunc(&ioKit.IOServiceClose, iokit, "IOServiceClose")
|
||||||
|
purego.RegisterLibFunc(&ioKit.IONotificationPortGetRunLoopSource, iokit, "IONotificationPortGetRunLoopSource")
|
||||||
|
purego.RegisterLibFunc(&ioKit.IONotificationPortDestroy, iokit, "IONotificationPortDestroy")
|
||||||
|
|
||||||
|
purego.RegisterLibFunc(&cf.CFRunLoopGetCurrent, cfLib, "CFRunLoopGetCurrent")
|
||||||
|
purego.RegisterLibFunc(&cf.CFRunLoopRun, cfLib, "CFRunLoopRun")
|
||||||
|
purego.RegisterLibFunc(&cf.CFRunLoopStop, cfLib, "CFRunLoopStop")
|
||||||
|
purego.RegisterLibFunc(&cf.CFRunLoopAddSource, cfLib, "CFRunLoopAddSource")
|
||||||
|
purego.RegisterLibFunc(&cf.CFRunLoopRemoveSource, cfLib, "CFRunLoopRemoveSource")
|
||||||
|
|
||||||
|
modeAddr, err := purego.Dlsym(cfLib, "kCFRunLoopCommonModes")
|
||||||
|
if err != nil {
|
||||||
|
libInitErr = fmt.Errorf("dlsym kCFRunLoopCommonModes: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Launder the uintptr-to-pointer conversion through a Go variable so
|
||||||
|
// go vet's unsafeptr analyzer doesn't flag a system-library global.
|
||||||
|
cfCommonModes = **(**uintptr)(unsafe.Pointer(&modeAddr))
|
||||||
|
|
||||||
|
// NewCallback slots are a finite, non-reclaimable resource, so register
|
||||||
|
// a single thunk that dispatches to the current Detector set.
|
||||||
|
callbackThunk = purego.NewCallback(powerCallback)
|
||||||
|
})
|
||||||
|
return libInitErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// powerCallback is the IOServiceInterestCallback trampoline, invoked on the
|
||||||
|
// runloop thread. A Go panic crossing the purego boundary has undefined
|
||||||
|
// behavior, so contain it here.
|
||||||
|
func powerCallback(refcon, service, messageType, messageArgument uintptr) uintptr {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Errorf("panic in sleep powerCallback: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
switch messageType {
|
||||||
|
case kIOMessageCanSystemSleep:
|
||||||
|
// Not acknowledging forces a 30s IOKit timeout before idle sleep.
|
||||||
|
allowPowerChange(messageArgument)
|
||||||
|
case kIOMessageSystemWillSleep:
|
||||||
|
dispatchEvent(EventTypeSleep)
|
||||||
|
allowPowerChange(messageArgument)
|
||||||
|
case kIOMessageSystemHasPoweredOn:
|
||||||
|
dispatchEvent(EventTypeWakeUp)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func allowPowerChange(messageArgument uintptr) {
|
||||||
|
serviceRegistryMu.Lock()
|
||||||
|
var port uintptr
|
||||||
|
if session != nil {
|
||||||
|
port = session.rp
|
||||||
|
}
|
||||||
|
serviceRegistryMu.Unlock()
|
||||||
|
if port != 0 {
|
||||||
|
ioKit.IOAllowPowerChange(port, messageArgument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dispatchEvent(event EventType) {
|
||||||
|
serviceRegistryMu.Lock()
|
||||||
|
snaps := make([]detectorSnapshot, 0, len(serviceRegistry))
|
||||||
|
for d := range serviceRegistry {
|
||||||
|
snaps = append(snaps, detectorSnapshot{
|
||||||
|
detector: d,
|
||||||
|
callback: d.callback,
|
||||||
|
done: d.done,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
serviceRegistryMu.Unlock()
|
||||||
|
|
||||||
|
for _, s := range snaps {
|
||||||
|
s.detector.triggerCallback(event, s.callback, s.done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runRunLoop owns the OS-locked thread that CFRunLoop is pinned to. Setup
|
||||||
|
// result is reported on errCh so Register can surface failures synchronously.
|
||||||
|
func runRunLoop(errCh chan<- error) {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
sess, err := setupSession()
|
||||||
|
if err == nil {
|
||||||
|
serviceRegistryMu.Lock()
|
||||||
|
session = sess
|
||||||
|
serviceRegistryMu.Unlock()
|
||||||
|
}
|
||||||
|
errCh <- err
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Errorf("panic in sleep runloop: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
cf.CFRunLoopRun()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupSession performs the IOKit registration on the current thread. Panics
|
||||||
|
// are converted to errors so runRunLoop never leaves errCh unsent.
|
||||||
|
func setupSession() (s *runLoopSession, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("panic during runloop setup: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var portRef, notifier uintptr
|
||||||
|
rp := ioKit.IORegisterForSystemPower(0, &portRef, callbackThunk, ¬ifier)
|
||||||
|
if rp == 0 {
|
||||||
|
return nil, fmt.Errorf("IORegisterForSystemPower returned zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
rl := cf.CFRunLoopGetCurrent()
|
||||||
|
source := ioKit.IONotificationPortGetRunLoopSource(portRef)
|
||||||
|
cf.CFRunLoopAddSource(rl, source, cfCommonModes)
|
||||||
|
|
||||||
|
return &runLoopSession{rl: rl, port: portRef, notifier: notifier, rp: rp}, nil
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -104,8 +104,6 @@ service DaemonService {
|
|||||||
// StopCPUProfile stops CPU profiling in the daemon
|
// StopCPUProfile stops CPU profiling in the daemon
|
||||||
rpc StopCPUProfile(StopCPUProfileRequest) returns (StopCPUProfileResponse) {}
|
rpc StopCPUProfile(StopCPUProfileRequest) returns (StopCPUProfileResponse) {}
|
||||||
|
|
||||||
rpc NotifyOSLifecycle(OSLifecycleRequest) returns(OSLifecycleResponse) {}
|
|
||||||
|
|
||||||
rpc GetInstallerResult(InstallerResultRequest) returns (InstallerResultResponse) {}
|
rpc GetInstallerResult(InstallerResultRequest) returns (InstallerResultResponse) {}
|
||||||
|
|
||||||
// ExposeService exposes a local port via the NetBird reverse proxy
|
// ExposeService exposes a local port via the NetBird reverse proxy
|
||||||
@@ -114,20 +112,6 @@ service DaemonService {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
message OSLifecycleRequest {
|
|
||||||
// avoid collision with loglevel enum
|
|
||||||
enum CycleType {
|
|
||||||
UNKNOWN = 0;
|
|
||||||
SLEEP = 1;
|
|
||||||
WAKEUP = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
CycleType type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OSLifecycleResponse {}
|
|
||||||
|
|
||||||
|
|
||||||
message LoginRequest {
|
message LoginRequest {
|
||||||
// setupKey netbird setup key.
|
// setupKey netbird setup key.
|
||||||
string setupKey = 1;
|
string setupKey = 1;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -120,6 +120,7 @@ func New(ctx context.Context, logFile string, configFile string, profilesDisable
|
|||||||
}
|
}
|
||||||
agent := &serverAgent{s}
|
agent := &serverAgent{s}
|
||||||
s.sleepHandler = sleephandler.New(agent)
|
s.sleepHandler = sleephandler.New(agent)
|
||||||
|
s.startSleepDetector()
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,18 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/sleep"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const envDisableSleepDetector = "NB_DISABLE_SLEEP_DETECTOR"
|
||||||
|
|
||||||
// serverAgent adapts Server to the handler.Agent and handler.StatusChecker interfaces
|
// serverAgent adapts Server to the handler.Agent and handler.StatusChecker interfaces
|
||||||
type serverAgent struct {
|
type serverAgent struct {
|
||||||
s *Server
|
s *Server
|
||||||
@@ -28,19 +33,61 @@ func (a *serverAgent) Status() (internal.StatusType, error) {
|
|||||||
return internal.CtxGetState(a.s.rootCtx).Status()
|
return internal.CtxGetState(a.s.rootCtx).Status()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyOSLifecycle handles operating system lifecycle events by executing appropriate logic based on the request type.
|
// startSleepDetector starts the OS sleep/wake detector and forwards events to
|
||||||
func (s *Server) NotifyOSLifecycle(callerCtx context.Context, req *proto.OSLifecycleRequest) (*proto.OSLifecycleResponse, error) {
|
// the sleep handler. On platforms without a supported detector the attempt
|
||||||
switch req.GetType() {
|
// logs a warning and returns. Setting NB_DISABLE_SLEEP_DETECTOR=true skips
|
||||||
case proto.OSLifecycleRequest_WAKEUP:
|
// registration entirely.
|
||||||
if err := s.sleepHandler.HandleWakeUp(callerCtx); err != nil {
|
func (s *Server) startSleepDetector() {
|
||||||
return &proto.OSLifecycleResponse{}, err
|
if sleepDetectorDisabled() {
|
||||||
|
log.Info("sleep detection disabled via " + envDisableSleepDetector)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
case proto.OSLifecycleRequest_SLEEP:
|
|
||||||
if err := s.sleepHandler.HandleSleep(callerCtx); err != nil {
|
svc, err := sleep.New()
|
||||||
return &proto.OSLifecycleResponse{}, err
|
if err != nil {
|
||||||
|
log.Warnf("failed to initialize sleep detection: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
log.Errorf("unknown OSLifecycleRequest type: %v", req.GetType())
|
err = svc.Register(func(event sleep.EventType) {
|
||||||
|
switch event {
|
||||||
|
case sleep.EventTypeSleep:
|
||||||
|
log.Info("handling sleep event")
|
||||||
|
if err := s.sleepHandler.HandleSleep(s.rootCtx); err != nil {
|
||||||
|
log.Errorf("failed to handle sleep event: %v", err)
|
||||||
}
|
}
|
||||||
return &proto.OSLifecycleResponse{}, nil
|
case sleep.EventTypeWakeUp:
|
||||||
|
log.Info("handling wakeup event")
|
||||||
|
if err := s.sleepHandler.HandleWakeUp(s.rootCtx); err != nil {
|
||||||
|
log.Errorf("failed to handle wakeup event: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to register sleep detector: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("sleep detection service initialized")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-s.rootCtx.Done()
|
||||||
|
log.Info("stopping sleep event listener")
|
||||||
|
if err := svc.Deregister(); err != nil {
|
||||||
|
log.Errorf("failed to deregister sleep detector: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sleepDetectorDisabled() bool {
|
||||||
|
val := os.Getenv(envDisableSleepDetector)
|
||||||
|
if val == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
disabled, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to parse %s=%q: %v", envDisableSleepDetector, val, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return disabled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||||
"github.com/netbirdio/netbird/client/internal/sleep"
|
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/client/ui/desktop"
|
"github.com/netbirdio/netbird/client/ui/desktop"
|
||||||
"github.com/netbirdio/netbird/client/ui/event"
|
"github.com/netbirdio/netbird/client/ui/event"
|
||||||
@@ -1149,9 +1148,6 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
|
|
||||||
go s.eventManager.Start(s.ctx)
|
go s.eventManager.Start(s.ctx)
|
||||||
go s.eventHandler.listen(s.ctx)
|
go s.eventHandler.listen(s.ctx)
|
||||||
|
|
||||||
// Start sleep detection listener
|
|
||||||
go s.startSleepListener()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) attachOutput(cmd *exec.Cmd) *os.File {
|
func (s *serviceClient) attachOutput(cmd *exec.Cmd) *os.File {
|
||||||
@@ -1212,62 +1208,6 @@ func (s *serviceClient) getSrvClient(timeout time.Duration) (proto.DaemonService
|
|||||||
return s.conn, nil
|
return s.conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// startSleepListener initializes the sleep detection service and listens for sleep events
|
|
||||||
func (s *serviceClient) startSleepListener() {
|
|
||||||
sleepService, err := sleep.New()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("%v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := sleepService.Register(s.handleSleepEvents); err != nil {
|
|
||||||
log.Errorf("failed to start sleep detection: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("sleep detection service initialized")
|
|
||||||
|
|
||||||
// Cleanup on context cancellation
|
|
||||||
go func() {
|
|
||||||
<-s.ctx.Done()
|
|
||||||
log.Info("stopping sleep event listener")
|
|
||||||
if err := sleepService.Deregister(); err != nil {
|
|
||||||
log.Errorf("failed to deregister sleep detection: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleSleepEvents sends a sleep notification to the daemon via gRPC
|
|
||||||
func (s *serviceClient) handleSleepEvents(event sleep.EventType) {
|
|
||||||
conn, err := s.getSrvClient(0)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to get daemon client for sleep notification: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &proto.OSLifecycleRequest{}
|
|
||||||
|
|
||||||
switch event {
|
|
||||||
case sleep.EventTypeWakeUp:
|
|
||||||
log.Infof("handle wakeup event: %v", event)
|
|
||||||
req.Type = proto.OSLifecycleRequest_WAKEUP
|
|
||||||
case sleep.EventTypeSleep:
|
|
||||||
log.Infof("handle sleep event: %v", event)
|
|
||||||
req.Type = proto.OSLifecycleRequest_SLEEP
|
|
||||||
default:
|
|
||||||
log.Infof("unknown event: %v", event)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = conn.NotifyOSLifecycle(s.ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to notify daemon about os lifecycle notification: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("successfully notified daemon about os lifecycle")
|
|
||||||
}
|
|
||||||
|
|
||||||
// setSettingsEnabled enables or disables the settings menu based on the provided state
|
// setSettingsEnabled enables or disables the settings menu based on the provided state
|
||||||
func (s *serviceClient) setSettingsEnabled(enabled bool) {
|
func (s *serviceClient) setSettingsEnabled(enabled bool) {
|
||||||
if s.mSettings != nil {
|
if s.mSettings != nil {
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -47,6 +47,7 @@ require (
|
|||||||
github.com/crowdsecurity/go-cs-bouncer v0.0.21
|
github.com/crowdsecurity/go-cs-bouncer v0.0.21
|
||||||
github.com/dexidp/dex v0.0.0-00010101000000-000000000000
|
github.com/dexidp/dex v0.0.0-00010101000000-000000000000
|
||||||
github.com/dexidp/dex/api/v2 v2.4.0
|
github.com/dexidp/dex/api/v2 v2.4.0
|
||||||
|
github.com/ebitengine/purego v0.8.4
|
||||||
github.com/eko/gocache/lib/v4 v4.2.0
|
github.com/eko/gocache/lib/v4 v4.2.0
|
||||||
github.com/eko/gocache/store/go_cache/v4 v4.2.2
|
github.com/eko/gocache/store/go_cache/v4 v4.2.2
|
||||||
github.com/eko/gocache/store/redis/v4 v4.2.2
|
github.com/eko/gocache/store/redis/v4 v4.2.2
|
||||||
@@ -179,7 +180,6 @@ require (
|
|||||||
github.com/docker/docker v28.0.1+incompatible // indirect
|
github.com/docker/docker v28.0.1+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.6.0 // indirect
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fredbi/uri v1.1.1 // indirect
|
github.com/fredbi/uri v1.1.1 // indirect
|
||||||
github.com/fyne-io/gl-js v0.2.0 // indirect
|
github.com/fyne-io/gl-js v0.2.0 // indirect
|
||||||
|
|||||||
Reference in New Issue
Block a user