github.com/safing/portbase@v0.19.5/updater/registry.go (about) 1 package updater 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "runtime" 9 "strings" 10 "sync" 11 12 "github.com/safing/portbase/log" 13 "github.com/safing/portbase/utils" 14 ) 15 16 const ( 17 onWindows = runtime.GOOS == "windows" 18 ) 19 20 // ResourceRegistry is a registry for managing update resources. 21 type ResourceRegistry struct { 22 sync.RWMutex 23 24 Name string 25 storageDir *utils.DirStructure 26 tmpDir *utils.DirStructure 27 indexes []*Index 28 state *RegistryState 29 30 resources map[string]*Resource 31 UpdateURLs []string 32 UserAgent string 33 MandatoryUpdates []string 34 AutoUnpack []string 35 36 // Verification holds a map of VerificationOptions assigned to their 37 // applicable identifier path prefix. 38 // Use an empty string to denote the default. 39 // Use empty options to disable verification for a path prefix. 40 Verification map[string]*VerificationOptions 41 42 // UsePreReleases signifies that pre-releases should be used when selecting a 43 // version. Even if false, a pre-release version will still be used if it is 44 // defined as the current version by an index. 45 UsePreReleases bool 46 47 // DevMode specifies if a local 0.0.0 version should be always chosen, when available. 48 DevMode bool 49 50 // Online specifies if resources may be downloaded if not available locally. 51 Online bool 52 53 // StateNotifyFunc may be set to receive any changes to the registry state. 54 // The specified function may lock the state, but may not block or take a 55 // lot of time. 56 StateNotifyFunc func(*RegistryState) 57 } 58 59 // AddIndex adds a new index to the resource registry. 60 // The order is important, as indexes added later will override the current 61 // release from earlier indexes. 62 func (reg *ResourceRegistry) AddIndex(idx Index) { 63 reg.Lock() 64 defer reg.Unlock() 65 66 // Get channel name from path. 67 idx.Channel = strings.TrimSuffix( 68 filepath.Base(idx.Path), filepath.Ext(idx.Path), 69 ) 70 71 reg.indexes = append(reg.indexes, &idx) 72 } 73 74 // PreInitUpdateState sets the initial update state of the registry before initialization. 75 func (reg *ResourceRegistry) PreInitUpdateState(s UpdateState) error { 76 if reg.state != nil { 77 return errors.New("registry already initialized") 78 } 79 80 reg.state = &RegistryState{ 81 Updates: s, 82 } 83 return nil 84 } 85 86 // Initialize initializes a raw registry struct and makes it ready for usage. 87 func (reg *ResourceRegistry) Initialize(storageDir *utils.DirStructure) error { 88 // check if storage dir is available 89 err := storageDir.Ensure() 90 if err != nil { 91 return err 92 } 93 94 // set default name 95 if reg.Name == "" { 96 reg.Name = "updater" 97 } 98 99 // initialize private attributes 100 reg.storageDir = storageDir 101 reg.tmpDir = storageDir.ChildDir("tmp", 0o0700) 102 reg.resources = make(map[string]*Resource) 103 if reg.state == nil { 104 reg.state = &RegistryState{} 105 } 106 reg.state.ID = StateReady 107 reg.state.reg = reg 108 109 // remove tmp dir to delete old entries 110 err = reg.Cleanup() 111 if err != nil { 112 log.Warningf("%s: failed to remove tmp dir: %s", reg.Name, err) 113 } 114 115 // (re-)create tmp dir 116 err = reg.tmpDir.Ensure() 117 if err != nil { 118 log.Warningf("%s: failed to create tmp dir: %s", reg.Name, err) 119 } 120 121 // Check verification options. 122 if reg.Verification != nil { 123 for prefix, opts := range reg.Verification { 124 // Check if verification is disable for this prefix. 125 if opts == nil { 126 continue 127 } 128 129 // If enabled, a trust store is required. 130 if opts.TrustStore == nil { 131 return fmt.Errorf("verification enabled for prefix %q, but no trust store configured", prefix) 132 } 133 134 // DownloadPolicy must be equal or stricter than DiskLoadPolicy. 135 if opts.DiskLoadPolicy < opts.DownloadPolicy { 136 return errors.New("verification download policy must be equal or stricter than the disk load policy") 137 } 138 139 // Warn if all policies are disabled. 140 if opts.DownloadPolicy == SignaturePolicyDisable && 141 opts.DiskLoadPolicy == SignaturePolicyDisable { 142 log.Warningf("%s: verification enabled for prefix %q, but all policies set to disable", reg.Name, prefix) 143 } 144 } 145 } 146 147 return nil 148 } 149 150 // StorageDir returns the main storage dir of the resource registry. 151 func (reg *ResourceRegistry) StorageDir() *utils.DirStructure { 152 return reg.storageDir 153 } 154 155 // TmpDir returns the temporary working dir of the resource registry. 156 func (reg *ResourceRegistry) TmpDir() *utils.DirStructure { 157 return reg.tmpDir 158 } 159 160 // SetDevMode sets the development mode flag. 161 func (reg *ResourceRegistry) SetDevMode(on bool) { 162 reg.Lock() 163 defer reg.Unlock() 164 165 reg.DevMode = on 166 } 167 168 // SetUsePreReleases sets the UsePreReleases flag. 169 func (reg *ResourceRegistry) SetUsePreReleases(yes bool) { 170 reg.Lock() 171 defer reg.Unlock() 172 173 reg.UsePreReleases = yes 174 } 175 176 // AddResource adds a resource to the registry. Does _not_ select new version. 177 func (reg *ResourceRegistry) AddResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error { 178 reg.Lock() 179 defer reg.Unlock() 180 181 err := reg.addResource(identifier, version, index, available, currentRelease, preRelease) 182 return err 183 } 184 185 func (reg *ResourceRegistry) addResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error { 186 res, ok := reg.resources[identifier] 187 if !ok { 188 res = reg.newResource(identifier) 189 reg.resources[identifier] = res 190 } 191 res.Index = index 192 193 return res.AddVersion(version, available, currentRelease, preRelease) 194 } 195 196 // AddResources adds resources to the registry. Errors are logged, the last one is returned. Despite errors, non-failing resources are still added. Does _not_ select new versions. 197 func (reg *ResourceRegistry) AddResources(versions map[string]string, index *Index, available, currentRelease, preRelease bool) error { 198 reg.Lock() 199 defer reg.Unlock() 200 201 // add versions and their flags to registry 202 var lastError error 203 for identifier, version := range versions { 204 lastError = reg.addResource(identifier, version, index, available, currentRelease, preRelease) 205 if lastError != nil { 206 log.Warningf("%s: failed to add resource %s: %s", reg.Name, identifier, lastError) 207 } 208 } 209 210 return lastError 211 } 212 213 // SelectVersions selects new resource versions depending on the current registry state. 214 func (reg *ResourceRegistry) SelectVersions() { 215 reg.RLock() 216 defer reg.RUnlock() 217 218 for _, res := range reg.resources { 219 res.Lock() 220 res.selectVersion() 221 res.Unlock() 222 } 223 } 224 225 // GetSelectedVersions returns a list of the currently selected versions. 226 func (reg *ResourceRegistry) GetSelectedVersions() (versions map[string]string) { 227 reg.RLock() 228 defer reg.RUnlock() 229 230 for _, res := range reg.resources { 231 res.Lock() 232 versions[res.Identifier] = res.SelectedVersion.VersionNumber 233 res.Unlock() 234 } 235 236 return 237 } 238 239 // Purge deletes old updates, retaining a certain amount, specified by the keep 240 // parameter. Will at least keep 2 updates per resource. 241 func (reg *ResourceRegistry) Purge(keep int) { 242 reg.RLock() 243 defer reg.RUnlock() 244 245 for _, res := range reg.resources { 246 res.Purge(keep) 247 } 248 } 249 250 // ResetResources removes all resources from the registry. 251 func (reg *ResourceRegistry) ResetResources() { 252 reg.Lock() 253 defer reg.Unlock() 254 255 reg.resources = make(map[string]*Resource) 256 } 257 258 // ResetIndexes removes all indexes from the registry. 259 func (reg *ResourceRegistry) ResetIndexes() { 260 reg.Lock() 261 defer reg.Unlock() 262 263 reg.indexes = make([]*Index, 0, len(reg.indexes)) 264 } 265 266 // Cleanup removes temporary files. 267 func (reg *ResourceRegistry) Cleanup() error { 268 // delete download tmp dir 269 return os.RemoveAll(reg.tmpDir.Path) 270 }