Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple Gitea Doctor improvements (#10943) (#10990) (#10064) (#9095) #10991

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
496 changes: 496 additions & 0 deletions cmd/doctor.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ arguments - which can alternatively be run by running the subcommand web.`
cmd.CmdMigrate,
cmd.CmdKeys,
cmd.CmdConvert,
cmd.CmdDoctor,
}
// Now adjust these commands to add our global configuration options

Expand Down
12 changes: 12 additions & 0 deletions models/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

package models

import (
"code.gitea.io/gitea/modules/setting"
"xorm.io/builder"
)

// DBContext represents a db context
type DBContext struct {
e Engine
Expand Down Expand Up @@ -53,3 +58,10 @@ func WithTx(f func(ctx DBContext) error) error {
sess.Close()
return err
}

// Iterate iterates the databases and doing something
func Iterate(ctx DBContext, tableBean interface{}, cond builder.Cond, fun func(idx int, bean interface{}) error) error {
return ctx.e.Where(cond).
BufferSize(setting.Database.IterateBufferSize).
Iterate(tableBean, fun)
}
46 changes: 46 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,52 @@ var migrations = []Migration{
NewMigration("Add block on rejected reviews branch protection", addBlockOnRejectedReviews),
}

// GetCurrentDBVersion returns the current db version
func GetCurrentDBVersion(x *xorm.Engine) (int64, error) {
if err := x.Sync(new(Version)); err != nil {
return -1, fmt.Errorf("sync: %v", err)
}

currentVersion := &Version{ID: 1}
has, err := x.Get(currentVersion)
if err != nil {
return -1, fmt.Errorf("get: %v", err)
}
if !has {
return -1, nil
}
return currentVersion.Version, nil
}

// ExpectedVersion returns the expected db version
func ExpectedVersion() int64 {
return int64(minDBVersion + len(migrations))
}

// EnsureUpToDate will check if the db is at the correct version
func EnsureUpToDate(x *xorm.Engine) error {
currentDB, err := GetCurrentDBVersion(x)
if err != nil {
return err
}

if currentDB < 0 {
return fmt.Errorf("Database has not been initialised")
}

if minDBVersion > currentDB {
return fmt.Errorf("DB version %d (<= %d) is too old for auto-migration. Upgrade to Gitea 1.6.4 first then upgrade to this version", currentDB, minDBVersion)
}

expected := ExpectedVersion()

if currentDB != expected {
return fmt.Errorf(`Current database version %d is not equal to the expected version %d. Please run "gitea [--config /path/to/app.ini] migrate" to update the database version`, currentDB, expected)
}

return nil
}

// Migrate database to current version
func Migrate(x *xorm.Engine) error {
if err := x.Sync(new(Version)); err != nil {
Expand Down
109 changes: 94 additions & 15 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -968,28 +968,29 @@ func CheckCreateRepository(doer, u *User, name string) error {
return nil
}

func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) {
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" && test -f \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType),
}
giteaHookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
}
return
}

// CreateDelegateHooks creates all the hooks scripts for the repo
func CreateDelegateHooks(repoPath string) error {
return createDelegateHooks(repoPath)
}

// createDelegateHooks creates all the hooks scripts for the repo
func createDelegateHooks(repoPath string) (err error) {

var (
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType),
}
giteaHookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
}
)

hookNames, hookTpls, giteaHookTpls := getHookTemplates()
hookDir := filepath.Join(repoPath, "hooks")

for i, hookName := range hookNames {
Expand All @@ -1008,16 +1009,94 @@ func createDelegateHooks(repoPath string) (err error) {
return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err)
}

if err = ensureExecutable(oldHookPath); err != nil {
return fmt.Errorf("Unable to set %s executable. Error %v", oldHookPath, err)
}

if err = os.Remove(newHookPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %v", newHookPath, err)
}
if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil {
return fmt.Errorf("write new hook file '%s': %v", newHookPath, err)
}

if err = ensureExecutable(newHookPath); err != nil {
return fmt.Errorf("Unable to set %s executable. Error %v", oldHookPath, err)
}
}

return nil
}
func checkExecutable(filename string) bool {
fileInfo, err := os.Stat(filename)
if err != nil {
return false
}
return (fileInfo.Mode() & 0100) > 0
}

func ensureExecutable(filename string) error {
fileInfo, err := os.Stat(filename)
if err != nil {
return err
}
if (fileInfo.Mode() & 0100) > 0 {
return nil
}
mode := fileInfo.Mode() | 0100
return os.Chmod(filename, mode)
}

// CheckDelegateHooks checks the hooks scripts for the repo
func CheckDelegateHooks(repoPath string) ([]string, error) {
hookNames, hookTpls, giteaHookTpls := getHookTemplates()

hookDir := filepath.Join(repoPath, "hooks")
results := make([]string, 0, 10)

for i, hookName := range hookNames {
oldHookPath := filepath.Join(hookDir, hookName)
newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")

cont := false
if !com.IsExist(oldHookPath) {
results = append(results, fmt.Sprintf("old hook file %s does not exist", oldHookPath))
cont = true
}
if !com.IsExist(oldHookPath + ".d") {
results = append(results, fmt.Sprintf("hooks directory %s does not exist", oldHookPath+".d"))
cont = true
}
if !com.IsExist(newHookPath) {
results = append(results, fmt.Sprintf("new hook file %s does not exist", newHookPath))
cont = true
}
if cont {
continue
}
contents, err := ioutil.ReadFile(oldHookPath)
if err != nil {
return results, err
}
if string(contents) != hookTpls[i] {
results = append(results, fmt.Sprintf("old hook file %s is out of date", oldHookPath))
}
if !checkExecutable(oldHookPath) {
results = append(results, fmt.Sprintf("old hook file %s is not executable", oldHookPath))
}
contents, err = ioutil.ReadFile(newHookPath)
if err != nil {
return results, err
}
if string(contents) != giteaHookTpls[i] {
results = append(results, fmt.Sprintf("new hook file %s is out of date", newHookPath))
}
if !checkExecutable(newHookPath) {
results = append(results, fmt.Sprintf("new hook file %s is not executable", newHookPath))
}
}
return results, nil
}

// initRepoCommit temporarily changes with work directory.
func initRepoCommit(tmpPath string, repo *Repository, u *User) (err error) {
Expand Down
26 changes: 20 additions & 6 deletions models/ssh_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"os"
Expand Down Expand Up @@ -687,14 +688,29 @@ func rewriteAllPublicKeys(e Engine) error {
}
}

err = e.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
if err := regeneratePublicKeys(e, t); err != nil {
return err
}

t.Close()
return os.Rename(tmpPath, fPath)
}

// RegeneratePublicKeys regenerates the authorized_keys file
func RegeneratePublicKeys(t io.Writer) error {
return regeneratePublicKeys(x, t)
}

func regeneratePublicKeys(e Engine, t io.Writer) error {
err := e.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
_, err = t.Write([]byte((bean.(*PublicKey)).AuthorizedString()))
return err
})
if err != nil {
return err
}

fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
if com.IsExist(fPath) {
f, err := os.Open(fPath)
if err != nil {
Expand All @@ -707,17 +723,15 @@ func rewriteAllPublicKeys(e Engine) error {
scanner.Scan()
continue
}
_, err = t.WriteString(line + "\n")
_, err = t.Write([]byte(line + "\n"))
if err != nil {
f.Close()
return err
}
}
f.Close()
}

t.Close()
return os.Rename(tmpPath, fPath)
return nil
}

// ________ .__ ____ __.
Expand Down
5 changes: 5 additions & 0 deletions modules/options/dynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,8 @@ func fileFromDir(name string) ([]byte, error) {

return []byte{}, fmt.Errorf("Asset file does not exist: %s", name)
}

// IsDynamic will return false when using embedded data (-tags bindata)
func IsDynamic() bool {
return true
}
5 changes: 5 additions & 0 deletions modules/options/static.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,8 @@ func fileFromDir(name string) ([]byte, error) {

return ioutil.ReadAll(f)
}

// IsDynamic will return false when using embedded data (-tags bindata)
func IsDynamic() bool {
return false
}