mirror of
https://github.com/tronbyt/server.git
synced 2026-08-31 06:57:10 +02:00
Fix OTA capability check and separate user apps/repo paths
- internal/data: Add OTACapable method to Device checking for firmware >= 1.4.4 - web/templates: Update firmware update UI to check for OTA capability - internal/gitutils: Fix EnsureRepo to handle non-repo directories by re-cloning - internal/server: Move user git repo sync location to 'repo' directory to avoid conflict with 'apps' uploads - internal/apps: Update ListUserApps to scan both 'apps' and 'repo' directories - CHANGELOG: Update OTA requirements note
This commit is contained in:
+1
-1
@@ -30,7 +30,7 @@ The entire backend has been rewritten in Go (1.25+). This change offers:
|
||||
* `reset-password`: Manually reset a user's password.
|
||||
* `health`: Perform health checks against the running server.
|
||||
* **Passkey Authentication:** Added support for passkey authentication (requires HTTPS on some browsers) for more secure and convenient logins.
|
||||
* **Over-The-Air (OTA) Updates:** Devices running compatible firmware can now be updated directly from the web interface. Updates are delivered via WebSocket commands or HTTP headers, streamlining the firmware management process.
|
||||
* **Over-The-Air (OTA) Updates:** Devices running compatible firmware can now be updated directly from the web interface. Updates are delivered via WebSocket commands or HTTP headers, streamlining the firmware management process. Requires device firmware version >= 1.4.4.
|
||||
* **App Configuration Export/Import:** Users can now export app configurations to a JSON file and import them back into existing app installations. This makes it easy to backup configurations or replicate complex setups across different apps.
|
||||
* **ZIP-packaged App Support:** Users can now upload and run apps packaged as ZIP files. This enables more complex apps that split logic across multiple files, reference external assets like images, and include metadata via manifest files.
|
||||
|
||||
|
||||
+27
-4
@@ -161,7 +161,30 @@ func scanSystemApps(dataDir string) ([]AppMetadata, error) {
|
||||
}
|
||||
|
||||
func ListUserApps(dataDir, username string) ([]AppMetadata, error) {
|
||||
userAppsDir := filepath.Join(dataDir, "users", username, "apps")
|
||||
var allApps []AppMetadata
|
||||
|
||||
// 1. Uploaded Apps
|
||||
uploaded, err := scanUserAppsDir(dataDir, username, "apps", "User uploaded app")
|
||||
if err != nil {
|
||||
// Just log error, don't fail completely
|
||||
slog.Warn("Failed to list uploaded user apps", "error", err)
|
||||
} else {
|
||||
allApps = append(allApps, uploaded...)
|
||||
}
|
||||
|
||||
// 2. Repo Apps
|
||||
repoApps, err := scanUserAppsDir(dataDir, username, "repo", "Git Repository app")
|
||||
if err != nil {
|
||||
slog.Warn("Failed to list user repo apps", "error", err)
|
||||
} else {
|
||||
allApps = append(allApps, repoApps...)
|
||||
}
|
||||
|
||||
return allApps, nil
|
||||
}
|
||||
|
||||
func scanUserAppsDir(dataDir, username, subDir, defaultSummary string) ([]AppMetadata, error) {
|
||||
userAppsDir := filepath.Join(dataDir, "users", username, subDir)
|
||||
var apps []AppMetadata
|
||||
|
||||
entries, err := os.ReadDir(userAppsDir)
|
||||
@@ -169,7 +192,7 @@ func ListUserApps(dataDir, username string) ([]AppMetadata, error) {
|
||||
return apps, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read user apps directory: %w", err)
|
||||
return nil, fmt.Errorf("failed to read user apps directory %s: %w", subDir, err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
@@ -184,7 +207,7 @@ func ListUserApps(dataDir, username string) ([]AppMetadata, error) {
|
||||
Name: appName,
|
||||
PackageName: appName,
|
||||
Author: username,
|
||||
Summary: "User uploaded app",
|
||||
Summary: defaultSummary,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -204,7 +227,7 @@ func ListUserApps(dataDir, username string) ([]AppMetadata, error) {
|
||||
|
||||
if starFile != "" {
|
||||
userApp.FileName = starFile
|
||||
userApp.Path = filepath.Join("users", username, "apps", appName, starFile)
|
||||
userApp.Path = filepath.Join("users", username, subDir, appName, starFile)
|
||||
|
||||
// Infer Preview/Preview2x from convention if files exist
|
||||
baseFileName := strings.TrimSuffix(starFile, ".star")
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// --- Enums & Value Types ---
|
||||
@@ -648,3 +650,18 @@ func (d *Device) GetEffectiveBrightness() int {
|
||||
}
|
||||
return brightness
|
||||
}
|
||||
|
||||
func (d *Device) OTACapable() bool {
|
||||
if !d.Type.SupportsFirmware() {
|
||||
return false
|
||||
}
|
||||
v := d.Info.FirmwareVersion
|
||||
if v == "" {
|
||||
return false
|
||||
}
|
||||
if !strings.HasPrefix(v, "v") {
|
||||
v = "v" + v
|
||||
}
|
||||
|
||||
return semver.Compare(v, "v1.4.4") >= 0
|
||||
}
|
||||
|
||||
@@ -125,6 +125,13 @@ func EnsureRepo(path string, repoURL string, token string, update bool) error {
|
||||
// Repo exists, open it
|
||||
r, err := git.PlainOpen(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, git.ErrRepositoryNotExists) {
|
||||
slog.Warn("Directory exists but is not a valid git repo, re-cloning", "path", path)
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return fmt.Errorf("failed to remove invalid repo directory: %w", err)
|
||||
}
|
||||
return EnsureRepo(path, repoURL, token, update)
|
||||
}
|
||||
// If not a git repo, maybe remove and re-clone?
|
||||
// For safety, error out.
|
||||
return fmt.Errorf("failed to open repo: %w", err)
|
||||
|
||||
@@ -186,7 +186,7 @@ func (s *Server) handleSetUserRepo(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
appsPath := filepath.Join(s.DataDir, "users", user.Username, "apps")
|
||||
appsPath := filepath.Join(s.DataDir, "users", user.Username, "repo")
|
||||
if err := gitutils.EnsureRepo(appsPath, repoURL, s.Config.GitHubToken, true); err != nil {
|
||||
slog.Error("Failed to sync user repo", "error", err)
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func (s *Server) handleRefreshUserRepo(w http.ResponseWriter, r *http.Request) {
|
||||
user := GetUser(r)
|
||||
|
||||
if user.AppRepoURL != "" {
|
||||
appsPath := filepath.Join(s.DataDir, "users", user.Username, "apps")
|
||||
appsPath := filepath.Join(s.DataDir, "users", user.Username, "repo")
|
||||
if err := gitutils.EnsureRepo(appsPath, user.AppRepoURL, s.Config.GitHubToken, true); err != nil {
|
||||
slog.Error("Failed to refresh user repo", "error", err)
|
||||
}
|
||||
|
||||
@@ -535,7 +535,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{ if .FirmwareAvailable }}
|
||||
{{ if and .FirmwareAvailable .Device.OTACapable }}
|
||||
<div class="device-settings-section">
|
||||
<h2>{{ t .Localizer "Firmware Update" }}</h2>
|
||||
<div style="padding: 10px;">
|
||||
|
||||
Reference in New Issue
Block a user