loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at forgejo 514 lines 14 kB view raw
1// Copyright 2014 The Gogs Authors. All rights reserved. 2// Copyright 2016 The Gitea Authors. All rights reserved. 3// SPDX-License-Identifier: MIT 4 5package cmd 6 7import ( 8 "fmt" 9 "io" 10 "os" 11 "path" 12 "path/filepath" 13 "strings" 14 "time" 15 16 "forgejo.org/models/db" 17 "forgejo.org/modules/json" 18 "forgejo.org/modules/log" 19 "forgejo.org/modules/setting" 20 "forgejo.org/modules/storage" 21 "forgejo.org/modules/util" 22 23 "code.forgejo.org/go-chi/session" 24 "github.com/mholt/archiver/v3" 25 "github.com/urfave/cli/v2" 26) 27 28func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error { 29 if verbose { 30 log.Info("Adding file %s", customName) 31 } 32 33 return w.Write(archiver.File{ 34 FileInfo: archiver.FileInfo{ 35 FileInfo: info, 36 CustomName: customName, 37 }, 38 ReadCloser: r, 39 }) 40} 41 42func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error { 43 file, err := os.Open(absPath) 44 if err != nil { 45 return err 46 } 47 defer file.Close() 48 fileInfo, err := file.Stat() 49 if err != nil { 50 return err 51 } 52 53 return addReader(w, file, fileInfo, filePath, verbose) 54} 55 56func isSubdir(upper, lower string) (bool, error) { 57 if relPath, err := filepath.Rel(upper, lower); err != nil { 58 return false, err 59 } else if relPath == "." || !strings.HasPrefix(relPath, ".") { 60 return true, nil 61 } 62 return false, nil 63} 64 65type outputType struct { 66 Enum []string 67 Default string 68 selected string 69} 70 71func (o outputType) Join() string { 72 return strings.Join(o.Enum, ", ") 73} 74 75func (o *outputType) Set(value string) error { 76 for _, enum := range o.Enum { 77 if enum == value { 78 o.selected = value 79 return nil 80 } 81 } 82 83 return fmt.Errorf("allowed values are %s", o.Join()) 84} 85 86func (o outputType) String() string { 87 if o.selected == "" { 88 return o.Default 89 } 90 return o.selected 91} 92 93var outputTypeEnum = &outputType{ 94 Enum: []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"}, 95 Default: "zip", 96} 97 98// CmdDump represents the available dump sub-command. 99var CmdDump = &cli.Command{ 100 Name: "dump", 101 Usage: "Dump Forgejo files and database", 102 Description: `Dump compresses all related files and database into zip file. 103It can be used for backup and capture Forgejo server image to send to maintainer`, 104 Action: runDump, 105 Flags: []cli.Flag{ 106 &cli.StringFlag{ 107 Name: "file", 108 Aliases: []string{"f"}, 109 Value: fmt.Sprintf("forgejo-dump-%d.zip", time.Now().Unix()), 110 Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.", 111 }, 112 &cli.BoolFlag{ 113 Name: "verbose", 114 Aliases: []string{"V"}, 115 Usage: "Show process details", 116 }, 117 &cli.BoolFlag{ 118 Name: "quiet", 119 Aliases: []string{"q"}, 120 Usage: "Only display warnings and errors", 121 }, 122 &cli.StringFlag{ 123 Name: "tempdir", 124 Aliases: []string{"t"}, 125 Usage: "Temporary dir path", 126 }, 127 &cli.StringFlag{ 128 Name: "database", 129 Aliases: []string{"d"}, 130 Usage: "Specify the database SQL syntax: sqlite3, mysql, postgres", 131 }, 132 &cli.BoolFlag{ 133 Name: "skip-repository", 134 Aliases: []string{"R"}, 135 Usage: "Skip repositories", 136 }, 137 &cli.BoolFlag{ 138 Name: "skip-log", 139 Aliases: []string{"L"}, 140 Usage: "Skip logs", 141 }, 142 &cli.BoolFlag{ 143 Name: "skip-custom-dir", 144 Usage: "Skip custom directory", 145 }, 146 &cli.BoolFlag{ 147 Name: "skip-lfs-data", 148 Usage: "Skip LFS data", 149 }, 150 &cli.BoolFlag{ 151 Name: "skip-attachment-data", 152 Usage: "Skip attachment data", 153 }, 154 &cli.BoolFlag{ 155 Name: "skip-package-data", 156 Usage: "Skip package data", 157 }, 158 &cli.BoolFlag{ 159 Name: "skip-index", 160 Usage: "Skip bleve index data", 161 }, 162 &cli.BoolFlag{ 163 Name: "skip-repo-archives", 164 Usage: "Skip repository archives", 165 }, 166 &cli.GenericFlag{ 167 Name: "type", 168 Value: outputTypeEnum, 169 Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()), 170 }, 171 }, 172} 173 174func fatal(format string, args ...any) { 175 fmt.Fprintf(os.Stderr, format+"\n", args...) 176 log.Fatal(format, args...) 177} 178 179func runDump(ctx *cli.Context) error { 180 var file *os.File 181 fileName := ctx.String("file") 182 outType := ctx.String("type") 183 if fileName == "-" { 184 file = os.Stdout 185 setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) 186 } else { 187 for _, suffix := range outputTypeEnum.Enum { 188 if strings.HasSuffix(fileName, "."+suffix) { 189 fileName = strings.TrimSuffix(fileName, "."+suffix) 190 break 191 } 192 } 193 fileName += "." + outType 194 } 195 setting.MustInstalled() 196 197 // make sure we are logging to the console no matter what the configuration tells us do to 198 // FIXME: don't use CfgProvider directly 199 if _, err := setting.CfgProvider.Section("log").NewKey("MODE", "console"); err != nil { 200 fatal("Setting logging mode to console failed: %v", err) 201 } 202 if _, err := setting.CfgProvider.Section("log.console").NewKey("STDERR", "true"); err != nil { 203 fatal("Setting console logger to stderr failed: %v", err) 204 } 205 206 // Set loglevel to Warn if quiet-mode is requested 207 if ctx.Bool("quiet") { 208 if _, err := setting.CfgProvider.Section("log.console").NewKey("LEVEL", "Warn"); err != nil { 209 fatal("Setting console log-level failed: %v", err) 210 } 211 } 212 213 if !setting.InstallLock { 214 log.Error("Is '%s' really the right config path?\n", setting.CustomConf) 215 return fmt.Errorf("forgejo is not initialized") 216 } 217 setting.LoadSettings() // cannot access session settings otherwise 218 219 verbose := ctx.Bool("verbose") 220 if verbose && ctx.Bool("quiet") { 221 return fmt.Errorf("--quiet and --verbose cannot both be set") 222 } 223 224 stdCtx, cancel := installSignals() 225 defer cancel() 226 227 err := db.InitEngine(stdCtx) 228 if err != nil { 229 return err 230 } 231 232 if err := storage.Init(); err != nil { 233 return err 234 } 235 236 if file == nil { 237 file, err = os.Create(fileName) 238 if err != nil { 239 fatal("Failed to open %s: %v", fileName, err) 240 } 241 } 242 defer file.Close() 243 244 absFileName, err := filepath.Abs(fileName) 245 if err != nil { 246 return err 247 } 248 249 var iface any 250 if fileName == "-" { 251 iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType)) 252 } else { 253 iface, err = archiver.ByExtension(fileName) 254 } 255 if err != nil { 256 fatal("Failed to get archiver for extension: %v", err) 257 } 258 259 w, _ := iface.(archiver.Writer) 260 if err := w.Create(file); err != nil { 261 fatal("Creating archiver.Writer failed: %v", err) 262 } 263 defer w.Close() 264 265 if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") { 266 log.Info("Skipping local repositories") 267 } else { 268 log.Info("Dumping local repositories... %s", setting.RepoRootPath) 269 if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil { 270 fatal("Failed to include repositories: %v", err) 271 } 272 273 if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") { 274 log.Info("Skipping LFS data") 275 } else if !setting.LFS.StartServer { 276 log.Info("LFS not enabled - skipping") 277 } else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error { 278 info, err := object.Stat() 279 if err != nil { 280 return err 281 } 282 283 return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose) 284 }); err != nil { 285 fatal("Failed to dump LFS objects: %v", err) 286 } 287 } 288 289 tmpDir := ctx.String("tempdir") 290 if tmpDir == "" { 291 tmpDir, err = os.MkdirTemp("", "forgejo-dump-*") 292 if err != nil { 293 fatal("Failed to create temporary directory: %v", err) 294 } 295 296 defer func() { 297 if err := util.Remove(tmpDir); err != nil { 298 log.Warn("Failed to remove temporary directory: %s: Error: %v", tmpDir, err) 299 } 300 }() 301 } 302 303 if _, err := os.Stat(tmpDir); os.IsNotExist(err) { 304 fatal("Path does not exist: %s", tmpDir) 305 } 306 307 dbDump, err := os.CreateTemp(tmpDir, "forgejo-db.sql") 308 if err != nil { 309 fatal("Failed to create temporary file: %v", err) 310 } 311 defer func() { 312 _ = dbDump.Close() 313 if err := util.Remove(dbDump.Name()); err != nil { 314 log.Warn("Failed to remove temporary database file: %s: Error: %v", dbDump.Name(), err) 315 } 316 }() 317 318 targetDBType := ctx.String("database") 319 if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() { 320 log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType) 321 } else { 322 log.Info("Dumping database...") 323 } 324 325 if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil { 326 fatal("Failed to dump database: %v", err) 327 } 328 329 if err := addFile(w, "forgejo-db.sql", dbDump.Name(), verbose); err != nil { 330 fatal("Failed to include forgejo-db.sql: %v", err) 331 } 332 333 if len(setting.CustomConf) > 0 { 334 log.Info("Adding custom configuration file from %s", setting.CustomConf) 335 if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil { 336 fatal("Failed to include specified app.ini: %v", err) 337 } 338 } 339 340 if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") { 341 log.Info("Skipping custom directory") 342 } else { 343 customDir, err := os.Stat(setting.CustomPath) 344 if err == nil && customDir.IsDir() { 345 if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is { 346 if err := addRecursiveExclude(w, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil { 347 fatal("Failed to include custom: %v", err) 348 } 349 } else { 350 log.Info("Custom dir %s is inside data dir %s, skipping", setting.CustomPath, setting.AppDataPath) 351 } 352 } else { 353 log.Info("Custom dir %s does not exist, skipping", setting.CustomPath) 354 } 355 } 356 357 isExist, err := util.IsExist(setting.AppDataPath) 358 if err != nil { 359 log.Error("Failed to check if %s exists: %v", setting.AppDataPath, err) 360 } 361 if isExist { 362 log.Info("Packing data directory...%s", setting.AppDataPath) 363 364 var excludes []string 365 if setting.SessionConfig.OriginalProvider == "file" { 366 var opts session.Options 367 if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil { 368 return err 369 } 370 excludes = append(excludes, opts.ProviderConfig) 371 } 372 373 if ctx.IsSet("skip-index") && ctx.Bool("skip-index") { 374 log.Info("Skipping bleve index data") 375 excludes = append(excludes, setting.Indexer.RepoPath) 376 excludes = append(excludes, setting.Indexer.IssuePath) 377 } 378 379 if ctx.IsSet("skip-repo-archives") && ctx.Bool("skip-repo-archives") { 380 log.Info("Skipping repository archives data") 381 excludes = append(excludes, setting.RepoArchive.Storage.Path) 382 } 383 384 excludes = append(excludes, setting.RepoRootPath) 385 excludes = append(excludes, setting.LFS.Storage.Path) 386 excludes = append(excludes, setting.Attachment.Storage.Path) 387 excludes = append(excludes, setting.Packages.Storage.Path) 388 excludes = append(excludes, setting.Log.RootPath) 389 excludes = append(excludes, absFileName) 390 if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil { 391 fatal("Failed to include data directory: %v", err) 392 } 393 } 394 395 if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") { 396 log.Info("Skipping attachment data") 397 } else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error { 398 info, err := object.Stat() 399 if err != nil { 400 return err 401 } 402 403 return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose) 404 }); err != nil { 405 fatal("Failed to dump attachments: %v", err) 406 } 407 408 if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") { 409 log.Info("Skipping package data") 410 } else if !setting.Packages.Enabled { 411 log.Info("Package registry not enabled - skipping") 412 } else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error { 413 info, err := object.Stat() 414 if err != nil { 415 return err 416 } 417 418 return addReader(w, object, info, path.Join("data", "packages", objPath), verbose) 419 }); err != nil { 420 fatal("Failed to dump packages: %v", err) 421 } 422 423 // Doesn't check if LogRootPath exists before processing --skip-log intentionally, 424 // ensuring that it's clear the dump is skipped whether the directory's initialized 425 // yet or not. 426 if ctx.IsSet("skip-log") && ctx.Bool("skip-log") { 427 log.Info("Skipping log files") 428 } else { 429 isExist, err := util.IsExist(setting.Log.RootPath) 430 if err != nil { 431 log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err) 432 } 433 if isExist { 434 if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil { 435 fatal("Failed to include log: %v", err) 436 } 437 } 438 } 439 440 if fileName != "-" { 441 if err = w.Close(); err != nil { 442 _ = util.Remove(fileName) 443 fatal("Failed to save %s: %v", fileName, err) 444 } 445 446 if err := os.Chmod(fileName, 0o600); err != nil { 447 log.Info("Can't change file access permissions mask to 0600: %v", err) 448 } 449 } 450 451 if fileName != "-" { 452 log.Info("Finish dumping in file %s", fileName) 453 } else { 454 log.Info("Finish dumping to stdout") 455 } 456 457 return nil 458} 459 460// addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath 461func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error { 462 absPath, err := filepath.Abs(absPath) 463 if err != nil { 464 return err 465 } 466 dir, err := os.Open(absPath) 467 if err != nil { 468 return err 469 } 470 defer dir.Close() 471 472 files, err := dir.Readdir(0) 473 if err != nil { 474 return err 475 } 476 for _, file := range files { 477 currentAbsPath := filepath.Join(absPath, file.Name()) 478 currentInsidePath := path.Join(insidePath, file.Name()) 479 480 if util.SliceContainsString(excludeAbsPath, currentAbsPath) { 481 log.Debug("Skipping %q (matched an excluded path)", currentAbsPath) 482 continue 483 } 484 485 if file.IsDir() { 486 if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil { 487 return err 488 } 489 if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil { 490 return err 491 } 492 } else { 493 // only copy regular files and symlink regular files, skip non-regular files like socket/pipe/... 494 shouldAdd := file.Mode().IsRegular() 495 if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink { 496 target, err := filepath.EvalSymlinks(currentAbsPath) 497 if err != nil { 498 return err 499 } 500 targetStat, err := os.Stat(target) 501 if err != nil { 502 return err 503 } 504 shouldAdd = targetStat.Mode().IsRegular() 505 } 506 if shouldAdd { 507 if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil { 508 return err 509 } 510 } 511 } 512 } 513 return nil 514}