github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/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.GetFromCouch(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  }