mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-23 14:06:18 +01:00
Compare commits
5 Commits
updater-up
...
v0.12.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09cd8d0db9 | ||
|
|
36f1a0c53b | ||
|
|
0b0e94e045 | ||
|
|
20ca6edf81 | ||
|
|
1990f8c6df |
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestSessionCache_GetSet(t *testing.T) {
|
func TestSessionCache_GetSet(t *testing.T) {
|
||||||
synctest.Run(func() {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
cache := NewSessionCache(69 * time.Second)
|
cache := NewSessionCache(69 * time.Second)
|
||||||
|
|
||||||
testData := &system.CombinedData{
|
testData := &system.CombinedData{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import "github.com/distatus/battery"
|
|||||||
// getBatteryStats returns the current battery percent and charge state
|
// getBatteryStats returns the current battery percent and charge state
|
||||||
func getBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
func getBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
||||||
batteries, err := battery.GetAll()
|
batteries, err := battery.GetAll()
|
||||||
if err != nil {
|
if err != nil || len(batteries) == 0 {
|
||||||
return batteryPercent, batteryState, err
|
return batteryPercent, batteryState, err
|
||||||
}
|
}
|
||||||
totalCapacity := float64(0)
|
totalCapacity := float64(0)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func TestHealth(t *testing.T) {
|
|||||||
// This test uses synctest to simulate time passing.
|
// This test uses synctest to simulate time passing.
|
||||||
// NOTE: This test requires GOEXPERIMENT=synctest to run.
|
// NOTE: This test requires GOEXPERIMENT=synctest to run.
|
||||||
t.Run("check with simulated time", func(t *testing.T) {
|
t.Run("check with simulated time", func(t *testing.T) {
|
||||||
synctest.Run(func() {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
// Update the file to set the initial timestamp.
|
// Update the file to set the initial timestamp.
|
||||||
require.NoError(t, Update(), "Update() failed inside synctest")
|
require.NoError(t, Update(), "Update() failed inside synctest")
|
||||||
|
|
||||||
|
|||||||
140
beszel/internal/ghupdate/extract.go
Normal file
140
beszel/internal/ghupdate/extract.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package ghupdate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// extract extracts an archive file to the destination directory.
|
||||||
|
// Supports .zip and .tar.gz files based on the file extension.
|
||||||
|
func extract(srcPath, destDir string) error {
|
||||||
|
if strings.HasSuffix(srcPath, ".tar.gz") {
|
||||||
|
return extractTarGz(srcPath, destDir)
|
||||||
|
}
|
||||||
|
// Default to zip extraction
|
||||||
|
return extractZip(srcPath, destDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractTarGz extracts a tar.gz archive to the destination directory.
|
||||||
|
func extractTarGz(srcPath, destDir string) error {
|
||||||
|
src, err := os.Open(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
gz, err := gzip.NewReader(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer gz.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(gz)
|
||||||
|
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.Typeflag == tar.TypeDir {
|
||||||
|
if err := os.MkdirAll(filepath.Join(destDir, header.Name), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(filepath.Join(destDir, header.Name)), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
outFile, err := os.Create(filepath.Join(destDir, header.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(outFile, tr); err != nil {
|
||||||
|
outFile.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractZip extracts the zip archive at "src" to "dest".
|
||||||
|
//
|
||||||
|
// Note that only dirs and regular files will be extracted.
|
||||||
|
// Symbolic links, named pipes, sockets, or any other irregular files
|
||||||
|
// are skipped because they come with too many edge cases and ambiguities.
|
||||||
|
func extractZip(src, dest string) error {
|
||||||
|
zr, err := zip.OpenReader(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer zr.Close()
|
||||||
|
|
||||||
|
// normalize dest path to check later for Zip Slip
|
||||||
|
dest = filepath.Clean(dest) + string(os.PathSeparator)
|
||||||
|
|
||||||
|
for _, f := range zr.File {
|
||||||
|
err := extractFile(f, dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractFile extracts the provided zipFile into "basePath/zipFileName" path,
|
||||||
|
// creating all the necessary path directories.
|
||||||
|
func extractFile(zipFile *zip.File, basePath string) error {
|
||||||
|
path := filepath.Join(basePath, zipFile.Name)
|
||||||
|
|
||||||
|
// check for Zip Slip
|
||||||
|
if !strings.HasPrefix(path, basePath) {
|
||||||
|
return fmt.Errorf("invalid file path: %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := zipFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
// allow only dirs or regular files
|
||||||
|
if zipFile.FileInfo().IsDir() {
|
||||||
|
if err := os.MkdirAll(path, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if zipFile.FileInfo().Mode().IsRegular() {
|
||||||
|
// ensure that the file path directories are created
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, zipFile.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(f, r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -4,9 +4,7 @@
|
|||||||
package ghupdate
|
package ghupdate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
|
||||||
"beszel"
|
"beszel"
|
||||||
"compress/gzip"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -19,8 +17,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/archive"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Minimal color functions using ANSI escape codes
|
// Minimal color functions using ANSI escape codes
|
||||||
@@ -108,6 +104,8 @@ func (p *plugin) update() (updated bool, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var latest *release
|
var latest *release
|
||||||
|
var useMirror bool
|
||||||
|
|
||||||
latest, err = fetchLatestRelease(
|
latest, err = fetchLatestRelease(
|
||||||
p.config.Context,
|
p.config.Context,
|
||||||
p.config.HttpClient,
|
p.config.HttpClient,
|
||||||
@@ -116,6 +114,7 @@ func (p *plugin) update() (updated bool, err error) {
|
|||||||
// if the first fetch fails, try the beszel.dev API (fallback for China)
|
// if the first fetch fails, try the beszel.dev API (fallback for China)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ColorPrint(ColorYellow, "Failed to fetch release. Trying beszel.dev mirror...")
|
ColorPrint(ColorYellow, "Failed to fetch release. Trying beszel.dev mirror...")
|
||||||
|
useMirror = true
|
||||||
latest, err = fetchLatestRelease(
|
latest, err = fetchLatestRelease(
|
||||||
p.config.Context,
|
p.config.Context,
|
||||||
p.config.HttpClient,
|
p.config.HttpClient,
|
||||||
@@ -140,14 +139,14 @@ func (p *plugin) update() (updated bool, err error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseDir := filepath.Join(p.config.DataDir, core.LocalTempDirName)
|
releaseDir := filepath.Join(p.config.DataDir, ".beszel_update")
|
||||||
defer os.RemoveAll(releaseDir)
|
defer os.RemoveAll(releaseDir)
|
||||||
|
|
||||||
ColorPrintf(ColorYellow, "Downloading %s...", asset.Name)
|
ColorPrintf(ColorYellow, "Downloading %s...", asset.Name)
|
||||||
|
|
||||||
// download the release asset
|
// download the release asset
|
||||||
assetPath := filepath.Join(releaseDir, asset.Name)
|
assetPath := filepath.Join(releaseDir, asset.Name)
|
||||||
if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetPath); err != nil {
|
if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetPath, useMirror); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,15 +155,9 @@ func (p *plugin) update() (updated bool, err error) {
|
|||||||
extractDir := filepath.Join(releaseDir, "extracted_"+asset.Name)
|
extractDir := filepath.Join(releaseDir, "extracted_"+asset.Name)
|
||||||
defer os.RemoveAll(extractDir)
|
defer os.RemoveAll(extractDir)
|
||||||
|
|
||||||
// Extract based on file extension
|
// Extract the archive (automatically detects format)
|
||||||
if strings.HasSuffix(asset.Name, ".tar.gz") {
|
if err := extract(assetPath, extractDir); err != nil {
|
||||||
if err := extractTarGz(assetPath, extractDir); err != nil {
|
return false, err
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := archive.Extract(assetPath, extractDir); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorPrint(ColorYellow, "Replacing the executable...")
|
ColorPrint(ColorYellow, "Replacing the executable...")
|
||||||
@@ -275,7 +268,11 @@ func downloadFile(
|
|||||||
client HttpClient,
|
client HttpClient,
|
||||||
url string,
|
url string,
|
||||||
destPath string,
|
destPath string,
|
||||||
|
useMirror bool,
|
||||||
) error {
|
) error {
|
||||||
|
if useMirror {
|
||||||
|
url = strings.Replace(url, "github.com", "gh.beszel.dev", 1)
|
||||||
|
}
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -350,52 +347,3 @@ func archiveSuffix(binaryName, goos, goarch string) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%s_%s_%s.tar.gz", binaryName, goos, goarch)
|
return fmt.Sprintf("%s_%s_%s.tar.gz", binaryName, goos, goarch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractTarGz(srcPath, destDir string) error {
|
|
||||||
src, err := os.Open(srcPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
gz, err := gzip.NewReader(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer gz.Close()
|
|
||||||
|
|
||||||
tr := tar.NewReader(gz)
|
|
||||||
|
|
||||||
for {
|
|
||||||
header, err := tr.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if header.Typeflag == tar.TypeDir {
|
|
||||||
if err := os.MkdirAll(filepath.Join(destDir, header.Name), 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(filepath.Join(destDir, header.Name)), 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
outFile, err := os.Create(filepath.Join(destDir, header.Name))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer outFile.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(outFile, tr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ghupdate
|
package ghupdate
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestReleaseFindAssetBySuffix(t *testing.T) {
|
func TestReleaseFindAssetBySuffix(t *testing.T) {
|
||||||
r := release{
|
r := release{
|
||||||
@@ -21,3 +24,22 @@ func TestReleaseFindAssetBySuffix(t *testing.T) {
|
|||||||
t.Fatalf("Expected asset with id %d, got %v", 2, asset)
|
t.Fatalf("Expected asset with id %d, got %v", 2, asset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtractFailure(t *testing.T) {
|
||||||
|
testDir := t.TempDir()
|
||||||
|
|
||||||
|
// Test with missing zip file
|
||||||
|
missingZipPath := filepath.Join(testDir, "missing_test.zip")
|
||||||
|
extractedPath := filepath.Join(testDir, "zip_extract")
|
||||||
|
|
||||||
|
if err := extract(missingZipPath, extractedPath); err == nil {
|
||||||
|
t.Fatal("Expected Extract to fail due to missing zip file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with missing tar.gz file
|
||||||
|
missingTarPath := filepath.Join(testDir, "missing_test.tar.gz")
|
||||||
|
|
||||||
|
if err := extract(missingTarPath, extractedPath); err == nil {
|
||||||
|
t.Fatal("Expected Extract to fail due to missing tar.gz file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestSystemManagerNew(t *testing.T) {
|
|||||||
user, err := tests.CreateUser(hub, "test@test.com", "testtesttest")
|
user, err := tests.CreateUser(hub, "test@test.com", "testtesttest")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
synctest.Run(func() {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
sm.Initialize()
|
sm.Initialize()
|
||||||
|
|
||||||
record, err := tests.CreateRecord(hub, "systems", map[string]any{
|
record, err := tests.CreateRecord(hub, "systems", map[string]any{
|
||||||
@@ -110,9 +110,11 @@ func TestSystemManagerNew(t *testing.T) {
|
|||||||
err = hub.Delete(record)
|
err = hub.Delete(record)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.False(t, sm.HasSystem(record.Id), "System should not exist in the store after deletion")
|
assert.False(t, sm.HasSystem(record.Id), "System should not exist in the store after deletion")
|
||||||
|
})
|
||||||
|
|
||||||
testOld(t, hub)
|
testOld(t, hub)
|
||||||
|
|
||||||
|
synctest.Test(t, func(t *testing.T) {
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
|
|||||||
4
beszel/site/package-lock.json
generated
4
beszel/site/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.12.3",
|
"version": "0.12.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.12.3",
|
"version": "0.12.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@henrygd/queue": "^1.0.7",
|
"@henrygd/queue": "^1.0.7",
|
||||||
"@henrygd/semaphore": "^0.0.2",
|
"@henrygd/semaphore": "^0.0.2",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.12.3",
|
"version": "0.12.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package beszel
|
|||||||
import "github.com/blang/semver"
|
import "github.com/blang/semver"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "0.12.3"
|
Version = "0.12.4"
|
||||||
AppName = "beszel"
|
AppName = "beszel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
- Add battery charge monitoring.
|
- Add battery charge monitoring.
|
||||||
|
|
||||||
|
- Add fallback mirror to the `update` commands. (#1035)
|
||||||
|
|
||||||
- Fix blank token field in insecure contexts.
|
- Fix blank token field in insecure contexts.
|
||||||
|
|
||||||
- Allow opening internal router links in new tab.
|
- Allow opening internal router links in new tab.
|
||||||
|
|||||||
Reference in New Issue
Block a user