mirror of
https://github.com/harivansh-afk/betterNAS.git
synced 2026-04-16 16:01:01 +00:00
Fix install script: strip v prefix from version for archive name
This commit is contained in:
parent
8002158a45
commit
1d564b738d
16 changed files with 552 additions and 26 deletions
|
|
@ -1,6 +1,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -10,6 +12,7 @@ type appConfig struct {
|
|||
statePath string
|
||||
dbPath string
|
||||
sessionTTL time.Duration
|
||||
nodeOfflineThreshold time.Duration
|
||||
registrationEnabled bool
|
||||
corsOrigin string
|
||||
}
|
||||
|
|
@ -21,7 +24,13 @@ type app struct {
|
|||
store store
|
||||
}
|
||||
|
||||
const defaultNodeOfflineThreshold = 2 * time.Minute
|
||||
|
||||
func newApp(config appConfig, startedAt time.Time) (*app, error) {
|
||||
if config.nodeOfflineThreshold <= 0 {
|
||||
config.nodeOfflineThreshold = defaultNodeOfflineThreshold
|
||||
}
|
||||
|
||||
var s store
|
||||
var err error
|
||||
if config.dbPath != "" {
|
||||
|
|
@ -41,6 +50,68 @@ func newApp(config appConfig, startedAt time.Time) (*app, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (a *app) presentedNode(node nasNode) nasNode {
|
||||
presented := copyNasNode(node)
|
||||
if !nodeHeartbeatIsFresh(presented.LastSeenAt, a.now().UTC(), a.config.nodeOfflineThreshold) {
|
||||
presented.Status = "offline"
|
||||
}
|
||||
return presented
|
||||
}
|
||||
|
||||
func (a *app) listNodes(ownerID string) []nasNode {
|
||||
nodes := a.store.listNodes(ownerID)
|
||||
presented := make([]nasNode, 0, len(nodes))
|
||||
for _, node := range nodes {
|
||||
presented = append(presented, a.presentedNode(node))
|
||||
}
|
||||
|
||||
sort.Slice(presented, func(i, j int) bool {
|
||||
return presented[i].ID < presented[j].ID
|
||||
})
|
||||
|
||||
return presented
|
||||
}
|
||||
|
||||
func (a *app) listConnectedExports(ownerID string) []storageExport {
|
||||
exports := a.store.listExports(ownerID)
|
||||
connected := make([]storageExport, 0, len(exports))
|
||||
for _, export := range exports {
|
||||
context, ok := a.store.exportContext(export.ID, ownerID)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !nodeIsConnected(a.presentedNode(context.node)) {
|
||||
continue
|
||||
}
|
||||
connected = append(connected, export)
|
||||
}
|
||||
|
||||
return connected
|
||||
}
|
||||
|
||||
func nodeHeartbeatIsFresh(lastSeenAt string, referenceTime time.Time, threshold time.Duration) bool {
|
||||
lastSeenAt = strings.TrimSpace(lastSeenAt)
|
||||
if threshold <= 0 || lastSeenAt == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
parsedLastSeenAt, err := time.Parse(time.RFC3339, lastSeenAt)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
referenceTime = referenceTime.UTC()
|
||||
if parsedLastSeenAt.After(referenceTime) {
|
||||
return true
|
||||
}
|
||||
|
||||
return referenceTime.Sub(parsedLastSeenAt) <= threshold
|
||||
}
|
||||
|
||||
func nodeIsConnected(node nasNode) bool {
|
||||
return node.Status == "online" || node.Status == "degraded"
|
||||
}
|
||||
|
||||
type nextcloudBackendStatus struct {
|
||||
Configured bool `json:"configured"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
|
|
|
|||
|
|
@ -36,6 +36,16 @@ func newAppFromEnv(startedAt time.Time) (*app, error) {
|
|||
sessionTTL = parsedSessionTTL
|
||||
}
|
||||
|
||||
nodeOfflineThreshold := defaultNodeOfflineThreshold
|
||||
rawNodeOfflineThreshold := strings.TrimSpace(env("BETTERNAS_NODE_OFFLINE_THRESHOLD", "2m"))
|
||||
if rawNodeOfflineThreshold != "" {
|
||||
parsedNodeOfflineThreshold, err := time.ParseDuration(rawNodeOfflineThreshold)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeOfflineThreshold = parsedNodeOfflineThreshold
|
||||
}
|
||||
|
||||
app, err := newApp(
|
||||
appConfig{
|
||||
version: env("BETTERNAS_VERSION", "0.1.0-dev"),
|
||||
|
|
@ -43,6 +53,7 @@ func newAppFromEnv(startedAt time.Time) (*app, error) {
|
|||
statePath: env("BETTERNAS_CONTROL_PLANE_STATE_PATH", ".state/control-plane/state.json"),
|
||||
dbPath: env("BETTERNAS_CONTROL_PLANE_DB_PATH", ".state/control-plane/betternas.db"),
|
||||
sessionTTL: sessionTTL,
|
||||
nodeOfflineThreshold: nodeOfflineThreshold,
|
||||
registrationEnabled: env("BETTERNAS_REGISTRATION_ENABLED", "true") == "true",
|
||||
corsOrigin: env("BETTERNAS_CORS_ORIGIN", ""),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ func (a *app) handler() http.Handler {
|
|||
mux.HandleFunc("POST /api/v1/auth/login", a.handleAuthLogin)
|
||||
mux.HandleFunc("POST /api/v1/auth/logout", a.handleAuthLogout)
|
||||
mux.HandleFunc("GET /api/v1/auth/me", a.handleAuthMe)
|
||||
mux.HandleFunc("GET /api/v1/nodes", a.handleNodesList)
|
||||
mux.HandleFunc("POST /api/v1/nodes/register", a.handleNodeRegister)
|
||||
mux.HandleFunc("POST /api/v1/nodes/{nodeId}/heartbeat", a.handleNodeHeartbeat)
|
||||
mux.HandleFunc("PUT /api/v1/nodes/{nodeId}/exports", a.handleNodeExports)
|
||||
|
|
@ -74,6 +75,15 @@ func (a *app) handleVersion(w http.ResponseWriter, _ *http.Request) {
|
|||
})
|
||||
}
|
||||
|
||||
func (a *app) handleNodesList(w http.ResponseWriter, r *http.Request) {
|
||||
currentUser, ok := a.requireSessionUser(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, a.listNodes(currentUser.ID))
|
||||
}
|
||||
|
||||
func (a *app) handleNodeRegister(w http.ResponseWriter, r *http.Request) {
|
||||
currentUser, ok := a.requireSessionUser(w, r)
|
||||
if !ok {
|
||||
|
|
@ -177,7 +187,7 @@ func (a *app) handleExportsList(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, a.store.listExports(currentUser.ID))
|
||||
writeJSON(w, http.StatusOK, a.listConnectedExports(currentUser.ID))
|
||||
}
|
||||
|
||||
func (a *app) handleMountProfileIssue(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -202,6 +212,11 @@ func (a *app) handleMountProfileIssue(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, errExportNotFound.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
context.node = a.presentedNode(context.node)
|
||||
if !nodeIsConnected(context.node) {
|
||||
http.Error(w, errMountTargetUnavailable.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
mountURL, err := buildMountURL(context)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -346,6 +346,27 @@ func (s *sqliteStore) listExports(ownerID string) []storageExport {
|
|||
return exports
|
||||
}
|
||||
|
||||
func (s *sqliteStore) listNodes(ownerID string) []nasNode {
|
||||
rows, err := s.db.Query("SELECT id, machine_id, owner_id, display_name, agent_version, status, last_seen_at, direct_address, relay_address FROM nodes WHERE owner_id = ? ORDER BY id", ownerID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var nodes []nasNode
|
||||
for rows.Next() {
|
||||
node := s.scanNode(rows)
|
||||
if node.ID != "" {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
}
|
||||
if nodes == nil {
|
||||
nodes = []nasNode{}
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (s *sqliteStore) listExportsForNode(nodeID string) []storageExport {
|
||||
rows, err := s.db.Query("SELECT id, node_id, owner_id, label, path, mount_path, capacity_bytes FROM exports WHERE node_id = ? ORDER BY id", nodeID)
|
||||
if err != nil {
|
||||
|
|
@ -401,15 +422,29 @@ func (s *sqliteStore) exportContext(exportID string, ownerID string) (exportCont
|
|||
}
|
||||
|
||||
func (s *sqliteStore) nodeByID(nodeID string) (nasNode, bool) {
|
||||
row := s.db.QueryRow(
|
||||
"SELECT id, machine_id, owner_id, display_name, agent_version, status, last_seen_at, direct_address, relay_address FROM nodes WHERE id = ?",
|
||||
nodeID)
|
||||
n := s.scanNode(row)
|
||||
if n.ID == "" {
|
||||
return nasNode{}, false
|
||||
}
|
||||
|
||||
return n, true
|
||||
}
|
||||
|
||||
type sqliteNodeScanner interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
||||
|
||||
func (s *sqliteStore) scanNode(scanner sqliteNodeScanner) nasNode {
|
||||
var n nasNode
|
||||
var directAddr, relayAddr sql.NullString
|
||||
var lastSeenAt sql.NullString
|
||||
var ownerID sql.NullString
|
||||
err := s.db.QueryRow(
|
||||
"SELECT id, machine_id, owner_id, display_name, agent_version, status, last_seen_at, direct_address, relay_address FROM nodes WHERE id = ?",
|
||||
nodeID).Scan(&n.ID, &n.MachineID, &ownerID, &n.DisplayName, &n.AgentVersion, &n.Status, &lastSeenAt, &directAddr, &relayAddr)
|
||||
err := scanner.Scan(&n.ID, &n.MachineID, &ownerID, &n.DisplayName, &n.AgentVersion, &n.Status, &lastSeenAt, &directAddr, &relayAddr)
|
||||
if err != nil {
|
||||
return nasNode{}, false
|
||||
return nasNode{}
|
||||
}
|
||||
if ownerID.Valid {
|
||||
n.OwnerID = ownerID.String
|
||||
|
|
@ -423,7 +458,7 @@ func (s *sqliteStore) nodeByID(nodeID string) (nasNode, bool) {
|
|||
if relayAddr.Valid {
|
||||
n.RelayAddress = &relayAddr.String
|
||||
}
|
||||
return n, true
|
||||
return n
|
||||
}
|
||||
|
||||
func (s *sqliteStore) nodeAuthByMachineID(machineID string) (nodeAuthState, bool) {
|
||||
|
|
|
|||
|
|
@ -320,6 +320,25 @@ func (s *memoryStore) listExports(ownerID string) []storageExport {
|
|||
return exports
|
||||
}
|
||||
|
||||
func (s *memoryStore) listNodes(ownerID string) []nasNode {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
nodes := make([]nasNode, 0, len(s.state.NodesByID))
|
||||
for _, node := range s.state.NodesByID {
|
||||
if node.OwnerID != ownerID {
|
||||
continue
|
||||
}
|
||||
nodes = append(nodes, copyNasNode(node))
|
||||
}
|
||||
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
return nodes[i].ID < nodes[j].ID
|
||||
})
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (s *memoryStore) exportContext(exportID string, ownerID string) (exportContext, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ type store interface {
|
|||
upsertExports(nodeID string, ownerID string, request nodeExportsRequest) ([]storageExport, error)
|
||||
recordHeartbeat(nodeID string, ownerID string, request nodeHeartbeatRequest) error
|
||||
listExports(ownerID string) []storageExport
|
||||
listNodes(ownerID string) []nasNode
|
||||
exportContext(exportID string, ownerID string) (exportContext, bool)
|
||||
nodeByID(nodeID string) (nasNode, bool)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue