github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/instance/lifecycle/helpers.go (about) 1 package lifecycle 2 3 import ( 4 "context" 5 "errors" 6 "os" 7 "strings" 8 9 "github.com/cozy/cozy-stack/model/app" 10 "github.com/cozy/cozy-stack/model/instance" 11 "github.com/cozy/cozy-stack/model/vfs" 12 "github.com/cozy/cozy-stack/pkg/config/config" 13 "github.com/cozy/cozy-stack/pkg/consts" 14 "github.com/cozy/cozy-stack/pkg/couchdb" 15 "github.com/cozy/cozy-stack/pkg/utils" 16 multierror "github.com/hashicorp/go-multierror" 17 "golang.org/x/net/idna" 18 "golang.org/x/sync/errgroup" 19 ) 20 21 func update(inst *instance.Instance) error { 22 if err := instance.Update(inst); err != nil { 23 inst.Logger().Errorf("Could not update: %s", err.Error()) 24 return err 25 } 26 return nil 27 } 28 29 func installApp(inst *instance.Instance, slug string) error { 30 source := "registry://" + slug + "/stable" 31 installer, err := app.NewInstaller(inst, app.Copier(consts.WebappType, inst), &app.InstallerOptions{ 32 Operation: app.Install, 33 Type: consts.WebappType, 34 SourceURL: source, 35 Slug: slug, 36 Registries: inst.Registries(), 37 }) 38 if err != nil { 39 return err 40 } 41 _, err = installer.RunSync() 42 return err 43 } 44 45 // DefineViewsAndIndex can be used to ensure that the CouchDB views and indexes 46 // used by the stack are correctly set. It expects that most index/view don't 47 // exist. It is faster when creating a new instance for example. 48 func DefineViewsAndIndex(inst *instance.Instance) error { 49 g, _ := errgroup.WithContext(context.Background()) 50 couchdb.DefineIndexes(g, inst, couchdb.Indexes) 51 couchdb.DefineViews(g, inst, couchdb.Views) 52 if err := g.Wait(); err != nil { 53 return err 54 } 55 inst.IndexViewsVersion = couchdb.IndexViewsVersion 56 return nil 57 } 58 59 // UpdateViewsAndIndex can be used to ensure that the CouchDB views and indexes 60 // used by the stack are correctly set. It has the same effect as 61 // DefineViewsAndIndex, but it expect most index/views already exist. 62 func UpdateViewsAndIndex(inst *instance.Instance) error { 63 err := couchdb.UpdateIndexesAndViews(inst, couchdb.Indexes, couchdb.Views) 64 if err != nil { 65 return err 66 } 67 inst.IndexViewsVersion = couchdb.IndexViewsVersion 68 return nil 69 } 70 71 func createDefaultFilesTree(inst *instance.Instance) error { 72 var errf error 73 fs := inst.VFS() 74 75 createDir := func(dir *vfs.DirDoc, err error) { 76 if err != nil { 77 errf = multierror.Append(errf, err) 78 return 79 } 80 dir.CozyMetadata = vfs.NewCozyMetadata(inst.PageURL("/", nil)) 81 err = fs.CreateDir(dir) 82 if err != nil && !os.IsExist(err) { 83 errf = multierror.Append(errf, err) 84 } 85 } 86 87 // Check if we create the "Administrative" and "Photos" folders. By 88 // default, we are creating it, but some contexts may not want to create them. 89 createAdministrativeFolder := true 90 createPhotosFolder := true 91 if ctxSettings, ok := inst.SettingsContext(); ok { 92 if administrativeFolderParam, ok := ctxSettings["init_administrative_folder"]; ok { 93 createAdministrativeFolder = administrativeFolderParam.(bool) 94 } 95 if photosFolderParam, ok := ctxSettings["init_photos_folder"]; ok { 96 createPhotosFolder = photosFolderParam.(bool) 97 } 98 } 99 100 administrativeReference := couchdb.DocReference{ 101 ID: "io.cozy.apps/administrative", 102 Type: consts.Apps, 103 } 104 if createAdministrativeFolder { 105 name := inst.Translate("Tree Administrative") 106 administrativeFolder, err := vfs.NewDirDocWithPath(name, consts.RootDirID, "/", nil) 107 if err == nil { 108 administrativeFolder.AddReferencedBy(administrativeReference) 109 } 110 createDir(administrativeFolder, err) 111 } else { 112 root, err := fs.DirByID(consts.RootDirID) 113 if err != nil { 114 return err 115 } 116 olddoc := root.Clone().(*vfs.DirDoc) 117 root.AddReferencedBy(administrativeReference) 118 if err := fs.UpdateDirDoc(olddoc, root); err != nil { 119 errf = multierror.Append(errf, err) 120 } 121 } 122 123 if createPhotosFolder { 124 name := inst.Translate("Tree Photos") 125 createDir(vfs.NewDirDocWithPath(name, consts.RootDirID, "/", nil)) 126 } 127 128 return errf 129 } 130 131 func checkAliases(inst *instance.Instance, aliases []string) ([]string, error) { 132 if len(aliases) == 0 { 133 return nil, nil 134 } 135 aliases = utils.UniqueStrings(aliases) 136 kept := make([]string, 0, len(aliases)) 137 for _, alias := range aliases { 138 alias = strings.TrimSpace(alias) 139 if alias == "" { 140 continue 141 } 142 alias, err := validateDomain(alias) 143 if err != nil { 144 return nil, err 145 } 146 if alias == inst.Domain { 147 return nil, instance.ErrExists 148 } 149 other, err := instance.Get(alias) 150 if !errors.Is(err, instance.ErrNotFound) { 151 if err != nil { 152 return nil, err 153 } 154 if other.ID() != inst.ID() { 155 return nil, instance.ErrExists 156 } 157 } 158 kept = append(kept, alias) 159 } 160 return kept, nil 161 } 162 163 const illegalChars = " /,;&?#@|='\"\t\r\n\x00" 164 const illegalFirstChars = "0123456789." 165 166 func validateDomain(domain string) (string, error) { 167 var err error 168 if domain, err = idna.ToUnicode(domain); err != nil { 169 return "", instance.ErrIllegalDomain 170 } 171 domain = strings.TrimSpace(domain) 172 if domain == "" || domain == ".." || domain == "." { 173 return "", instance.ErrIllegalDomain 174 } 175 if strings.ContainsAny(domain, illegalChars) { 176 return "", instance.ErrIllegalDomain 177 } 178 if strings.ContainsAny(domain[:1], illegalFirstChars) { 179 return "", instance.ErrIllegalDomain 180 } 181 domain = strings.ToLower(domain) 182 if config.GetConfig().Subdomains == config.FlatSubdomains { 183 parts := strings.SplitN(domain, ".", 2) 184 if strings.Contains(parts[0], "-") { 185 return "", instance.ErrIllegalDomain 186 } 187 } 188 return domain, nil 189 }