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 sets the policy in the database. func (hsdb *HSDatabase) SetPolicy(policy string) (*types.Policy, error) { // Create a new policy. p := types.Policy{ Data: policy, } err := hsdb.DB.Clauses(clause.Returning{}).Create(&p).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 }