Compare commits

...

8 Commits

Author SHA1 Message Date
henrygd
ae037b278e agent: simplify FreeBSD temperature sensor names 2026-08-16 21:16:12 -04:00
QuantumFlux21
9f1128933f feat: Add support for reading CPU (dev.cpu.*.temperature) and ACPI thermal zone (hw.acpi.thermal.tz*) temperature sensors on FreeBSD systems. (#2227)
Co-authored-by: roib <roib@elsec.us>
2026-08-16 20:58:41 -04:00
henrygd
dbe10e3a8f ci: update vulncheck workflow to run only on pushes to main, not PRs 2026-08-16 20:33:38 -04:00
hank
7e7bcb3b35 chore: release main 2026-08-16 20:28:39 -04:00
henrygd
418e3f0892 fix(helm): update app version to 0.18.7 2026-08-16 20:26:37 -04:00
henrygd
7556a63378 helm: update version to latest and automate chart app version bumps
- Keep chart documentation aligned with the application version
- Update app versions automatically after application releases
- Restore the main application release as latest after chart publishing
2026-08-16 20:23:34 -04:00
hank
260b082c5e chore: release main 2026-08-16 20:04:02 -04:00
henrygd
55054a75ab fix(helm): publish and index chart releases reliably, maybe
- Serialize release runs to prevent GitHub Pages index races
- Publish drafts using the release ID from Release Please
- Keep Helm chart releases out of the latest release channel
- Publish releases before Chart Releaser indexes them
- Match Chart Releaser to v-prefixed release tags
2026-08-16 20:02:20 -04:00
14 changed files with 382 additions and 34 deletions

View File

@@ -18,6 +18,10 @@ env:
name: helm-release
concurrency:
group: helm-release
cancel-in-progress: false
jobs:
release:
name: Release
@@ -73,6 +77,7 @@ jobs:
env:
TAG: ${{ fromJson(needs.release.outputs.releases)[format('{0}--tag_name', matrix.path)] }}
VERSION: ${{ fromJson(needs.release.outputs.releases)[format('{0}--version', matrix.path)] }}
RELEASE_ID: ${{ fromJson(needs.release.outputs.releases)[format('{0}--id', matrix.path)] }}
steps:
- name: Debug
@@ -112,27 +117,36 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "$TAG" .cr-release-packages/*.tgz --clobber
- name: Update index
run: |
owner=$(cut -d '/' -f 1 <<< "$GITHUB_REPOSITORY")
repo=$(cut -d '/' -f 2 <<< "$GITHUB_REPOSITORY")
# Create docs directory and copy chart
mkdir -p docs
cp .cr-release-packages/*.tgz docs/ || true
# Generate index.yaml for GitHub Pages
args=(-o "$owner" -r "$repo" -c "https://henrygd.github.io/beszel" --push -t "${{ secrets.CR_TOKEN }}" --index-path docs/index.yaml)
.tmp/cr index "${args[@]}"
- name: Publish chart release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CHART_PATH: ${{ matrix.path }}
run: |
chart_name="${CHART_PATH##*/}"
release_id=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" --jq .id)
gh api --method PATCH "repos/${GITHUB_REPOSITORY}/releases/${release_id}" \
gh api --method PATCH "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" \
-f "name=Helm chart: ${chart_name} v${VERSION}" \
-F draft=false \
-f make_latest=false
- name: Update index
run: |
owner=$(cut -d '/' -f 1 <<< "$GITHUB_REPOSITORY")
repo=$(cut -d '/' -f 2 <<< "$GITHUB_REPOSITORY")
# Create the local output directory for the generated index
mkdir -p docs
# Generate index.yaml for GitHub Pages
args=(-o "$owner" -r "$repo" --push -t "${{ secrets.CR_TOKEN }}" --index-path docs/index.yaml --release-name-template '{{ .Name }}-v{{ .Version }}')
.tmp/cr index "${args[@]}"
- name: Restore application release as latest
if: ${{ always() }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
release_id=$(gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \
--jq 'map(select(.draft == false and .prerelease == false and (.tag_name | test("^v[0-9]"))))[0].id')
test -n "$release_id"
gh api --method PATCH "repos/${GITHUB_REPOSITORY}/releases/${release_id}" \
-f make_latest=true

View File

@@ -52,3 +52,51 @@ jobs:
GITHUB_TOKEN: ${{ secrets.TOKEN || secrets.GITHUB_TOKEN }}
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
IS_FORK: ${{ github.repository_owner != 'henrygd' }}
update-helm-app-version:
name: Update Helm app version
needs: goreleaser
if: ${{ github.repository_owner == 'henrygd' }}
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v7
with:
ref: main
token: ${{ secrets.CR_TOKEN }}
- name: Update chart app versions
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
version="${RELEASE_TAG#v}"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid release version: $version" >&2
exit 1
fi
for chart in supplemental/helm/beszel-agent supplemental/helm/beszel-hub; do
current_version=$(awk -F '"' '/^appVersion:/ { print $2 }' "$chart/Chart.yaml")
NEW_VERSION="$version" perl -pi -e 's/^appVersion:.*$/appVersion: "$ENV{NEW_VERSION}"/' "$chart/Chart.yaml"
OLD_VERSION="$current_version" NEW_VERSION="$version" \
perl -pi -e 's/\Q$ENV{OLD_VERSION}\E/$ENV{NEW_VERSION}/g' "$chart/README.md"
done
- name: Commit app version update
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
if git diff --quiet -- supplemental/helm; then
echo "Helm charts already use ${RELEASE_TAG#v}"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add supplemental/helm/beszel-agent/Chart.yaml \
supplemental/helm/beszel-agent/README.md \
supplemental/helm/beszel-hub/Chart.yaml \
supplemental/helm/beszel-hub/README.md
git commit -m "fix(helm): update app version to ${RELEASE_TAG#v}"
git push origin HEAD:main

View File

@@ -2,10 +2,6 @@
name: VulnCheck
on:
pull_request:
branches:
- main
push:
branches:
- main

View File

@@ -1,4 +1,4 @@
{
"supplemental/helm/beszel-agent": "0.1.2",
"supplemental/helm/beszel-hub": "0.1.2"
"supplemental/helm/beszel-agent": "0.1.4",
"supplemental/helm/beszel-hub": "0.1.4"
}

View File

@@ -1,4 +1,4 @@
//go:build !windows
//go:build !windows && !freebsd
package agent

14
agent/sensors_freebsd.go Normal file
View File

@@ -0,0 +1,14 @@
//go:build freebsd
package agent
import (
"context"
"github.com/shirou/gopsutil/v4/sensors"
"golang.org/x/sys/unix"
)
var getSensorTemps = func(ctx context.Context) ([]sensors.TemperatureStat, error) {
return getFreeBSDSensorTemps(ctx, unix.SysctlUint32)
}

View File

@@ -0,0 +1,81 @@
//go:build freebsd || testing
package agent
import (
"context"
"fmt"
"github.com/shirou/gopsutil/v4/sensors"
)
const (
freebsdZeroCelsiusDeciKelvin = 2731
freebsdAcpiThermalZoneCount = 16
)
type freebsdSysctlUintReader func(name string) (uint32, error)
func getFreeBSDSensorTemps(ctx context.Context, readSysctl freebsdSysctlUintReader) ([]sensors.TemperatureStat, error) {
cpuCount, err := readSysctl("hw.ncpu")
if err != nil {
return nil, err
}
temps := make([]sensors.TemperatureStat, 0, int(cpuCount)+freebsdAcpiThermalZoneCount)
for cpu := range cpuCount {
select {
case <-ctx.Done():
return temps, ctx.Err()
default:
}
sysctlName := fmt.Sprintf("dev.cpu.%d.temperature", cpu)
value, err := readSysctl(sysctlName)
if err != nil {
continue
}
temp, ok := freebsdDeciKelvinToCelsius(value)
if !ok {
continue
}
temps = append(temps, sensors.TemperatureStat{
SensorKey: fmt.Sprintf("cpu.%d", cpu),
Temperature: temp,
})
}
for zone := 0; zone < freebsdAcpiThermalZoneCount; zone++ {
select {
case <-ctx.Done():
return temps, ctx.Err()
default:
}
sysctlName := fmt.Sprintf("hw.acpi.thermal.tz%d.temperature", zone)
value, err := readSysctl(sysctlName)
if err != nil {
continue
}
temp, ok := freebsdDeciKelvinToCelsius(value)
if !ok {
continue
}
temps = append(temps, sensors.TemperatureStat{
SensorKey: fmt.Sprintf("acpi.thermal.tz%d", zone),
Temperature: temp,
})
}
return temps, nil
}
func freebsdDeciKelvinToCelsius(value uint32) (float64, bool) {
if value <= freebsdZeroCelsiusDeciKelvin {
return 0, false
}
temp := float64(int64(value)-freebsdZeroCelsiusDeciKelvin) / 10
if temp <= 0 || temp >= 200 {
return 0, false
}
return temp, true
}

View File

@@ -0,0 +1,167 @@
//go:build testing
package agent
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var errFakeFreeBSDSysctlNotFound = errors.New("sysctl not found")
type fakeFreeBSDSysctls struct {
values map[string]uint32
errs map[string]error
}
func (f fakeFreeBSDSysctls) read(name string) (uint32, error) {
if err, ok := f.errs[name]; ok {
return 0, err
}
if value, ok := f.values[name]; ok {
return value, nil
}
return 0, errFakeFreeBSDSysctlNotFound
}
func TestFreeBSDDeciKelvinToCelsius(t *testing.T) {
tests := []struct {
name string
value uint32
expected float64
ok bool
}{
{
name: "45 Celsius",
value: 3181,
expected: 45,
ok: true,
},
{
name: "fractional Celsius",
value: 3186,
expected: 45.5,
ok: true,
},
{
name: "zero deci-Kelvin",
value: 0,
ok: false,
},
{
name: "zero Celsius",
value: freebsdZeroCelsiusDeciKelvin,
ok: false,
},
{
name: "below zero Celsius",
value: freebsdZeroCelsiusDeciKelvin - 1,
ok: false,
},
{
name: "invalid signed integer",
value: 1<<32 - 1,
ok: false,
},
{
name: "unreasonably high Celsius",
value: freebsdZeroCelsiusDeciKelvin + 2000,
ok: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, ok := freebsdDeciKelvinToCelsius(tt.value)
assert.Equal(t, tt.ok, ok)
assert.InDelta(t, tt.expected, result, 0.001)
})
}
}
func TestGetFreeBSDSensorTemps(t *testing.T) {
reader := fakeFreeBSDSysctls{
values: map[string]uint32{
"hw.ncpu": 4,
"dev.cpu.0.temperature": 3231,
"dev.cpu.1.temperature": 3242,
"dev.cpu.3.temperature": freebsdZeroCelsiusDeciKelvin,
"hw.acpi.thermal.tz0.temperature": 3101,
"hw.acpi.thermal.tz2.temperature": 3116,
"hw.acpi.thermal.tz3.temperature": freebsdZeroCelsiusDeciKelvin,
"unrelated.sensor.value": 9999,
"dev.cpu.99.temperature": 9999,
"dev.amdtemp.0.core0.foo": 9999,
},
}
temps, err := getFreeBSDSensorTemps(context.Background(), reader.read)
require.NoError(t, err)
require.Len(t, temps, 4)
assert.Equal(t, "cpu.0", temps[0].SensorKey)
assert.InDelta(t, 50.0, temps[0].Temperature, 0.001)
assert.Equal(t, "cpu.1", temps[1].SensorKey)
assert.InDelta(t, 51.1, temps[1].Temperature, 0.001)
assert.Equal(t, "acpi.thermal.tz0", temps[2].SensorKey)
assert.InDelta(t, 37.0, temps[2].Temperature, 0.001)
assert.Equal(t, "acpi.thermal.tz2", temps[3].SensorKey)
assert.InDelta(t, 38.5, temps[3].Temperature, 0.001)
}
func TestGetFreeBSDSensorTempsCpuCountError(t *testing.T) {
reader := fakeFreeBSDSysctls{
errs: map[string]error{
"hw.ncpu": errors.New("permission denied"),
},
}
temps, err := getFreeBSDSensorTemps(context.Background(), reader.read)
assert.Nil(t, temps)
assert.EqualError(t, err, "permission denied")
}
func TestGetFreeBSDSensorTempsNoTemperatureSysctls(t *testing.T) {
reader := fakeFreeBSDSysctls{
values: map[string]uint32{"hw.ncpu": 2},
}
temps, err := getFreeBSDSensorTemps(context.Background(), reader.read)
require.NoError(t, err)
assert.Empty(t, temps)
}
func TestGetFreeBSDSensorTempsAcpiOnly(t *testing.T) {
reader := fakeFreeBSDSysctls{
values: map[string]uint32{
"hw.ncpu": 0,
"hw.acpi.thermal.tz0.temperature": 3081,
},
}
temps, err := getFreeBSDSensorTemps(context.Background(), reader.read)
require.NoError(t, err)
require.Len(t, temps, 1)
assert.Equal(t, "acpi.thermal.tz0", temps[0].SensorKey)
assert.InDelta(t, 35.0, temps[0].Temperature, 0.001)
}
func TestGetFreeBSDSensorTempsContextCancelled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
reader := fakeFreeBSDSysctls{
values: map[string]uint32{"hw.ncpu": 2},
}
temps, err := getFreeBSDSensorTemps(ctx, reader.read)
assert.Empty(t, temps)
assert.ErrorIs(t, err, context.Canceled)
}

View File

@@ -1,5 +1,19 @@
# Changelog
## [0.1.4](https://github.com/henrygd/beszel/compare/beszel-agent-v0.1.3...beszel-agent-v0.1.4) (2026-08-17)
### Bug Fixes
* **helm:** update app version to 0.18.7 ([418e3f0](https://github.com/henrygd/beszel/commit/418e3f0892747a62a3b743b9693c24406439388c))
## [0.1.3](https://github.com/henrygd/beszel/compare/beszel-agent-v0.1.2...beszel-agent-v0.1.3) (2026-08-17)
### Bug Fixes
* **helm:** publish and index chart releases reliably, maybe ([55054a7](https://github.com/henrygd/beszel/commit/55054a75aba20bc2dd4ffd1c92daaa13749d7dd7))
## [0.1.2](https://github.com/henrygd/beszel/compare/beszel-agent-v0.1.1...beszel-agent-v0.1.2) (2026-08-16)

View File

@@ -2,9 +2,9 @@ apiVersion: v1
description: Installs beszel-agent in kubernetes
home: https://github.com/henrygd/beszel/tree/main/supplemental/helm/beszel-agent
name: beszel-agent
appVersion: "0.17.0"
# This version is managed automatically by Release Please.
version: 0.1.2
appVersion: "0.18.7"
# The release workflow manages this chart version; do not edit manually.
version: 0.1.4
sources:
- https://github.com/henrygd/beszel/tree/main/supplemental/helm/beszel-agent
- https://www.beszel.dev/

View File

@@ -87,7 +87,7 @@ Essential parameters to configure:
| `secret.sshKey` | `ssh-key` | Key name in the secret for the SSH public key |
| `secret.tokenKey` | `token` | Key name in the secret for the authentication token |
| `image.repository` | `henrygd/beszel-agent` | Container image |
| `image.tag` | Chart AppVersion (0.17.0) | Image version |
| `image.tag` | Chart AppVersion (0.18.7) | Image version |
| `hostNetwork` | `false` | Use host network for network monitoring |
| `tolerations` | Allows all taints | Tolerations for running on tainted nodes |
@@ -392,7 +392,7 @@ helm upgrade beszel-agent ./beszel-agent \
# Change image version
helm upgrade beszel-agent ./beszel-agent \
--set image.tag="0.17.0"
--set image.tag="0.18.7"
```
### Restart All Agents
@@ -529,7 +529,7 @@ kubectl get secret beszel-agent -o jsonpath='{.data.ssh-key}' | base64 -d
## Chart Information
- **Chart Version**: 0.1.0
- **App Version**: 0.17.0
- **App Version**: 0.18.7
- **Kubernetes Version**: 1.19+
- **Maintainer**: cloudwithdan (nikoloskid@pm.me)

View File

@@ -1,5 +1,19 @@
# Changelog
## [0.1.4](https://github.com/henrygd/beszel/compare/beszel-hub-v0.1.3...beszel-hub-v0.1.4) (2026-08-17)
### Bug Fixes
* **helm:** update app version to 0.18.7 ([418e3f0](https://github.com/henrygd/beszel/commit/418e3f0892747a62a3b743b9693c24406439388c))
## [0.1.3](https://github.com/henrygd/beszel/compare/beszel-hub-v0.1.2...beszel-hub-v0.1.3) (2026-08-17)
### Bug Fixes
* **helm:** publish and index chart releases reliably, maybe ([55054a7](https://github.com/henrygd/beszel/commit/55054a75aba20bc2dd4ffd1c92daaa13749d7dd7))
## [0.1.2](https://github.com/henrygd/beszel/compare/beszel-hub-v0.1.1...beszel-hub-v0.1.2) (2026-08-16)

View File

@@ -2,9 +2,9 @@ apiVersion: v1
description: Installs beszel-hub in kubernetes
home: https://github.com/henrygd/beszel/tree/main/supplemental/helm/beszel-hub
name: beszel-hub
appVersion: "0.17.0"
# This version is managed automatically by Release Please.
version: 0.1.2
appVersion: "0.18.7"
# The release workflow manages this chart version; do not edit manually.
version: 0.1.4
sources:
- https://github.com/henrygd/beszel/tree/main/supplemental/helm/beszel-hub
- https://www.beszel.dev/

View File

@@ -54,7 +54,7 @@ Key configuration options in `values.yaml`:
|-----------|---------|-------------|
| `replicaCount` | `1` | Number of Beszel Hub replicas |
| `image.repository` | `henrygd/beszel` | Container image repository |
| `image.tag` | Chart AppVersion (0.17.0) | Container image tag |
| `image.tag` | Chart AppVersion (0.18.7) | Container image tag |
| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
| `service.port` | `8090` | Service port |
| `persistentVolumeClaim.enabled` | `true` | Enable persistent volume |
@@ -176,7 +176,7 @@ tolerations:
```yaml
replicaCount: 3
image:
tag: "0.17.0"
tag: "0.18.7"
service:
type: LoadBalancer
ingress:
@@ -337,7 +337,7 @@ By default, Beszel Hub uses a PersistentVolumeClaim for data storage. Ensure you
## Chart Information
- **Chart Version**: 0.1.0
- **App Version**: 0.17.0
- **App Version**: 0.18.7
- **Kubernetes Version**: 1.19+
- **Maintainer**: cloudwithdan (nikoloskid@pm.me)