code.gitea.io/gitea@v1.22.3/services/doctor/paths.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package doctor 5 6 import ( 7 "context" 8 "fmt" 9 "os" 10 11 "code.gitea.io/gitea/modules/log" 12 "code.gitea.io/gitea/modules/setting" 13 ) 14 15 type configurationFile struct { 16 Name string 17 Path string 18 IsDirectory bool 19 Required bool 20 Writable bool 21 } 22 23 func checkConfigurationFile(logger log.Logger, autofix bool, fileOpts configurationFile) error { 24 logger.Info(`%-26s %q`, log.NewColoredValue(fileOpts.Name+":", log.Reset), fileOpts.Path) 25 fi, err := os.Stat(fileOpts.Path) 26 if err != nil { 27 if os.IsNotExist(err) && autofix && fileOpts.IsDirectory { 28 if err := os.MkdirAll(fileOpts.Path, 0o777); err != nil { 29 logger.Error(" Directory does not exist and could not be created. ERROR: %v", err) 30 return fmt.Errorf("Configuration directory: \"%q\" does not exist and could not be created. ERROR: %w", fileOpts.Path, err) 31 } 32 fi, err = os.Stat(fileOpts.Path) 33 } 34 } 35 if err != nil { 36 if fileOpts.Required { 37 logger.Error(" Is REQUIRED but is not accessible. ERROR: %v", err) 38 return fmt.Errorf("Configuration file \"%q\" is not accessible but is required. Error: %w", fileOpts.Path, err) 39 } 40 logger.Warn(" NOTICE: is not accessible (Error: %v)", err) 41 // this is a non-critical error 42 return nil 43 } 44 45 if fileOpts.IsDirectory && !fi.IsDir() { 46 logger.Error(" ERROR: not a directory") 47 return fmt.Errorf("Configuration directory \"%q\" is not a directory. Error: %w", fileOpts.Path, err) 48 } else if !fileOpts.IsDirectory && !fi.Mode().IsRegular() { 49 logger.Error(" ERROR: not a regular file") 50 return fmt.Errorf("Configuration file \"%q\" is not a regular file. Error: %w", fileOpts.Path, err) 51 } else if fileOpts.Writable { 52 if err := isWritableDir(fileOpts.Path); err != nil { 53 logger.Error(" ERROR: is required to be writable but is not writable: %v", err) 54 return fmt.Errorf("Configuration file \"%q\" is required to be writable but is not. Error: %w", fileOpts.Path, err) 55 } 56 } 57 return nil 58 } 59 60 func checkConfigurationFiles(ctx context.Context, logger log.Logger, autofix bool) error { 61 if fi, err := os.Stat(setting.CustomConf); err != nil || !fi.Mode().IsRegular() { 62 logger.Error("Failed to find configuration file at '%s'.", setting.CustomConf) 63 logger.Error("If you've never ran Gitea yet, this is normal and '%s' will be created for you on first run.", setting.CustomConf) 64 logger.Error("Otherwise check that you are running this command from the correct path and/or provide a `--config` parameter.") 65 logger.Critical("Cannot proceed without a configuration file") 66 return err 67 } 68 69 setting.MustInstalled() 70 71 configurationFiles := []configurationFile{ 72 {"Configuration File Path", setting.CustomConf, false, true, false}, 73 {"Repository Root Path", setting.RepoRootPath, true, true, true}, 74 {"Data Root Path", setting.AppDataPath, true, true, true}, 75 {"Custom File Root Path", setting.CustomPath, true, false, false}, 76 {"Work directory", setting.AppWorkPath, true, true, false}, 77 {"Log Root Path", setting.Log.RootPath, true, true, true}, 78 } 79 80 if !setting.HasBuiltinBindata { 81 configurationFiles = append(configurationFiles, configurationFile{"Static File Root Path", setting.StaticRootPath, true, true, false}) 82 } 83 84 numberOfErrors := 0 85 for _, configurationFile := range configurationFiles { 86 if err := checkConfigurationFile(logger, autofix, configurationFile); err != nil { 87 numberOfErrors++ 88 } 89 } 90 91 if numberOfErrors > 0 { 92 logger.Critical("Please check your configuration files and try again.") 93 return fmt.Errorf("%d configuration files with errors", numberOfErrors) 94 } 95 96 return nil 97 } 98 99 func isWritableDir(path string) error { 100 // There's no platform-independent way of checking if a directory is writable 101 // https://stackoverflow.com/questions/20026320/how-to-tell-if-folder-exists-and-is-writable 102 103 tmpFile, err := os.CreateTemp(path, "doctors-order") 104 if err != nil { 105 return err 106 } 107 if err := os.Remove(tmpFile.Name()); err != nil { 108 fmt.Printf("Warning: can't remove temporary file: '%s'\n", tmpFile.Name()) //nolint:forbidigo 109 } 110 tmpFile.Close() 111 return nil 112 } 113 114 func init() { 115 Register(&Check{ 116 Title: "Check paths and basic configuration", 117 Name: "paths", 118 IsDefault: true, 119 Run: checkConfigurationFiles, 120 AbortIfFailed: true, 121 SkipDatabaseInitialization: true, 122 Priority: 1, 123 }) 124 }