mirror of
https://github.com/juanfont/headscale.git
synced 2026-11-04 20:49:24 +09:00
Every write inserted a row and nothing deleted one, while every reader takes only the newest. Writers store and install under one lock, so an uncommitted insert cannot outlive the newer row that pruned the rest.
102 lines
2.3 KiB
Go
102 lines
2.3 KiB
Go
package db
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/types"
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// SetPolicy replaces the policy in the database. Only the newest row is
|
|
// ever read, so older ones are removed rather than kept as history.
|
|
func (hsdb *HSDatabase) SetPolicy(policy string) (*types.Policy, error) {
|
|
return Write(hsdb.DB, func(tx *gorm.DB) (*types.Policy, error) {
|
|
p := types.Policy{
|
|
Data: policy,
|
|
}
|
|
|
|
err := tx.Clauses(clause.Returning{}).Create(&p).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Unscoped: a soft delete would keep the data. Deleting by id
|
|
// never removes a newer row from a concurrent writer.
|
|
err = tx.Unscoped().Where("id < ?", p.ID).Delete(&types.Policy{}).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &p, nil
|
|
})
|
|
}
|
|
|
|
// GetPolicy returns the latest policy in the database.
|
|
func (hsdb *HSDatabase) GetPolicy() (*types.Policy, error) {
|
|
return GetPolicy(hsdb.DB)
|
|
}
|
|
|
|
// GetPolicy returns the latest policy from the database.
|
|
// This standalone function can be used in contexts where [HSDatabase] is not available,
|
|
// such as during migrations.
|
|
func GetPolicy(tx *gorm.DB) (*types.Policy, error) {
|
|
var p types.Policy
|
|
|
|
// Query:
|
|
// SELECT * FROM policies ORDER BY id DESC LIMIT 1;
|
|
err := tx.
|
|
Order("id DESC").
|
|
Limit(1).
|
|
First(&p).Error
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, types.ErrPolicyNotFound
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return &p, nil
|
|
}
|
|
|
|
// PolicyBytes loads policy configuration from file or database based on the configured mode.
|
|
// Returns nil if no policy is configured, which is valid.
|
|
// This standalone function can be used in contexts where [HSDatabase] is not available,
|
|
// such as during migrations.
|
|
func PolicyBytes(tx *gorm.DB, cfg *types.Config) ([]byte, error) {
|
|
switch cfg.Policy.Mode {
|
|
case types.PolicyModeFile:
|
|
path := cfg.Policy.Path
|
|
|
|
// It is fine to start headscale without a policy file.
|
|
if len(path) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
absPath := util.AbsolutePathFromConfigPath(path)
|
|
|
|
return os.ReadFile(absPath)
|
|
|
|
case types.PolicyModeDB:
|
|
p, err := GetPolicy(tx)
|
|
if err != nil {
|
|
if errors.Is(err, types.ErrPolicyNotFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
if p.Data == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
return []byte(p.Data), nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|