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