2014-05-02 09:21:46 +08:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2016-12-21 20:13:17 +08:00
// Copyright 2016 The Gitea Authors. All rights reserved.
2022-11-28 02:20:29 +08:00
// SPDX-License-Identifier: MIT
2014-05-02 09:21:46 +08:00
package cmd
import (
2014-05-06 01:08:01 +08:00
"fmt"
2014-05-02 09:21:46 +08:00
"os"
"path"
2017-01-12 12:47:20 +08:00
"path/filepath"
2020-06-06 04:47:39 +08:00
"strings"
2014-05-02 09:21:46 +08:00
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2024-04-03 10:16:46 +08:00
"code.gitea.io/gitea/modules/dump"
2021-07-25 00:03:58 +08:00
"code.gitea.io/gitea/modules/json"
2019-12-18 00:12:10 +08:00
"code.gitea.io/gitea/modules/log"
2016-11-11 00:24:48 +08:00
"code.gitea.io/gitea/modules/setting"
2020-09-29 17:05:13 +08:00
"code.gitea.io/gitea/modules/storage"
2020-08-12 04:05:34 +08:00
"code.gitea.io/gitea/modules/util"
2017-04-12 15:44:54 +08:00
2021-01-26 23:36:53 +08:00
"gitea.com/go-chi/session"
2022-06-18 22:06:32 +08:00
"github.com/mholt/archiver/v3"
2023-07-21 17:28:19 +08:00
"github.com/urfave/cli/v2"
2014-05-02 09:21:46 +08:00
)
2016-11-04 19:42:18 +08:00
// CmdDump represents the available dump sub-command.
2023-07-21 17:28:19 +08:00
var CmdDump = & cli . Command {
2024-04-03 10:16:46 +08:00
Name : "dump" ,
Usage : "Dump Gitea files and database" ,
Description : ` Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer ` ,
Action : runDump ,
2014-09-08 07:39:26 +08:00
Flags : [ ] cli . Flag {
2023-07-21 17:28:19 +08:00
& cli . StringFlag {
Name : "file" ,
Aliases : [ ] string { "f" } ,
2024-04-03 10:16:46 +08:00
Usage : ` Name of the dump file which will be created, default to "gitea-dump- { time}.zip". Supply '-' for stdout. See type for available types. ` ,
2019-04-01 12:31:37 +08:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
Name : "verbose" ,
Aliases : [ ] string { "V" } ,
Usage : "Show process details" ,
2016-11-10 06:18:22 +08:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
Name : "quiet" ,
Aliases : [ ] string { "q" } ,
Usage : "Only display warnings and errors" ,
2023-04-10 21:46:23 +08:00
} ,
2023-07-21 17:28:19 +08:00
& cli . StringFlag {
Name : "tempdir" ,
Aliases : [ ] string { "t" } ,
Value : os . TempDir ( ) ,
Usage : "Temporary dir path" ,
2016-11-10 06:18:22 +08:00
} ,
2023-07-21 17:28:19 +08:00
& cli . StringFlag {
Name : "database" ,
Aliases : [ ] string { "d" } ,
2023-09-04 02:44:01 +08:00
Usage : "Specify the database SQL syntax: sqlite3, mysql, mssql, postgres" ,
2017-01-03 16:20:28 +08:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
Name : "skip-repository" ,
Aliases : [ ] string { "R" } ,
Usage : "Skip the repository dumping" ,
2019-01-14 05:52:26 +08:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
Name : "skip-log" ,
Aliases : [ ] string { "L" } ,
Usage : "Skip the log dumping" ,
2020-05-01 09:30:31 +08:00
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2021-02-08 09:00:12 +08:00
Name : "skip-custom-dir" ,
Usage : "Skip custom directory" ,
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2021-04-12 17:33:32 +08:00
Name : "skip-lfs-data" ,
Usage : "Skip LFS data" ,
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2021-04-12 17:33:32 +08:00
Name : "skip-attachment-data" ,
Usage : "Skip attachment data" ,
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2022-04-27 04:30:51 +08:00
Name : "skip-package-data" ,
Usage : "Skip package data" ,
} ,
2023-07-21 17:28:19 +08:00
& cli . BoolFlag {
2022-10-24 11:19:21 +08:00
Name : "skip-index" ,
Usage : "Skip bleve index data" ,
} ,
2024-04-03 10:16:46 +08:00
& cli . StringFlag {
2020-06-06 04:47:39 +08:00
Name : "type" ,
2024-04-03 10:16:46 +08:00
Usage : fmt . Sprintf ( ` Dump output format, default to "zip", supported types: %s ` , strings . Join ( dump . SupportedOutputTypes , ", " ) ) ,
2020-06-06 04:47:39 +08:00
} ,
2014-09-08 07:39:26 +08:00
} ,
2014-05-02 09:21:46 +08:00
}
2023-07-05 02:36:08 +08:00
func fatal ( format string , args ... any ) {
2019-12-18 00:12:10 +08:00
log . Fatal ( format , args ... )
}
2016-05-13 02:32:28 +08:00
func runDump ( ctx * cli . Context ) error {
Refactor path & config system (#25330)
# The problem
There were many "path tricks":
* By default, Gitea uses its program directory as its work path
* Gitea tries to use the "work path" to guess its "custom path" and
"custom conf (app.ini)"
* Users might want to use other directories as work path
* The non-default work path should be passed to Gitea by GITEA_WORK_DIR
or "--work-path"
* But some Gitea processes are started without these values
* The "serv" process started by OpenSSH server
* The CLI sub-commands started by site admin
* The paths are guessed by SetCustomPathAndConf again and again
* The default values of "work path / custom path / custom conf" can be
changed when compiling
# The solution
* Use `InitWorkPathAndCommonConfig` to handle these path tricks, and use
test code to cover its behaviors.
* When Gitea's web server runs, write the WORK_PATH to "app.ini", this
value must be the most correct one, because if this value is not right,
users would find that the web UI doesn't work and then they should be
able to fix it.
* Then all other sub-commands can use the WORK_PATH in app.ini to
initialize their paths.
* By the way, when Gitea starts for git protocol, it shouldn't output
any log, otherwise the git protocol gets broken and client blocks
forever.
The "work path" priority is: WORK_PATH in app.ini > cmd arg --work-path
> env var GITEA_WORK_DIR > builtin default
The "app.ini" searching order is: cmd arg --config > cmd arg "work path
/ custom path" > env var "work path / custom path" > builtin default
## ⚠️ BREAKING
If your instance's "work path / custom path / custom conf" doesn't meet
the requirements (eg: work path must be absolute), Gitea will report a
fatal error and exit. You need to set these values according to the
error log.
----
Close #24818
Close #24222
Close #21606
Close #21498
Close #25107
Close #24981
Maybe close #24503
Replace #23301
Replace #22754
And maybe more
2023-06-21 13:50:26 +08:00
setting . MustInstalled ( )
2021-12-01 15:50:01 +08:00
2024-04-03 10:16:46 +08:00
quite := ctx . Bool ( "quiet" )
verbose := ctx . Bool ( "verbose" )
if verbose && quite {
fatal ( "Option --quiet and --verbose cannot both be set" )
2020-06-06 04:47:39 +08:00
}
2024-04-03 10:16:46 +08:00
// outFileName is either "-" or a file name (will be made absolute)
outFileName , outType := dump . PrepareFileNameAndType ( ctx . String ( "file" ) , ctx . String ( "type" ) )
if outType == "" {
fatal ( "Invalid output type" )
2020-06-06 04:47:39 +08:00
}
2023-04-10 21:46:23 +08:00
2024-04-03 10:16:46 +08:00
outFile := os . Stdout
if outFileName != "-" {
var err error
if outFileName , err = filepath . Abs ( outFileName ) ; err != nil {
fatal ( "Unable to get absolute path of dump file: %v" , err )
}
if exist , _ := util . IsExist ( outFileName ) ; exist {
fatal ( "Dump file %q exists" , outFileName )
2023-04-10 21:46:23 +08:00
}
2024-04-03 10:16:46 +08:00
if outFile , err = os . Create ( outFileName ) ; err != nil {
fatal ( "Unable to create dump file %q: %v" , outFileName , err )
}
defer outFile . Close ( )
2023-04-10 21:46:23 +08:00
}
2024-04-03 10:16:46 +08:00
setupConsoleLogger ( util . Iif ( quite , log . WARN , log . INFO ) , log . CanColorStderr , os . Stderr )
2017-01-23 17:11:18 +08:00
2024-04-03 10:16:46 +08:00
setting . DisableLoggerInit ( )
setting . LoadSettings ( ) // cannot access session settings otherwise
2023-04-10 21:46:23 +08:00
2021-11-07 11:11:27 +08:00
stdCtx , cancel := installSignals ( )
defer cancel ( )
err := db . InitEngine ( stdCtx )
2017-01-23 17:11:18 +08:00
if err != nil {
return err
}
2014-05-02 09:21:46 +08:00
2024-04-03 10:16:46 +08:00
if err = storage . Init ( ) ; err != nil {
2020-09-29 17:05:13 +08:00
return err
}
2024-04-03 10:16:46 +08:00
archiverGeneric , err := archiver . ByExtension ( "." + outType )
2019-01-14 05:52:26 +08:00
if err != nil {
2020-06-06 04:47:39 +08:00
fatal ( "Unable to get archiver for extension: %v" , err )
2019-01-14 05:52:26 +08:00
}
2019-12-18 00:12:10 +08:00
2024-04-03 10:16:46 +08:00
archiverWriter := archiverGeneric . ( archiver . Writer )
if err := archiverWriter . Create ( outFile ) ; err != nil {
2020-06-06 04:47:39 +08:00
fatal ( "Creating archiver.Writer failed: %v" , err )
}
2024-04-03 10:16:46 +08:00
defer archiverWriter . Close ( )
dumper := & dump . Dumper {
Writer : archiverWriter ,
Verbose : verbose ,
}
dumper . GlobalExcludeAbsPath ( outFileName )
2019-01-14 05:52:26 +08:00
2020-05-03 11:57:45 +08:00
if ctx . IsSet ( "skip-repository" ) && ctx . Bool ( "skip-repository" ) {
2019-12-18 00:12:10 +08:00
log . Info ( "Skip dumping local repositories" )
2019-01-14 05:52:26 +08:00
} else {
2020-06-06 04:47:39 +08:00
log . Info ( "Dumping local repositories... %s" , setting . RepoRootPath )
2024-04-03 10:16:46 +08:00
if err := dumper . AddRecursiveExclude ( "repos" , setting . RepoRootPath , nil ) ; err != nil {
2020-06-06 04:47:39 +08:00
fatal ( "Failed to include repositories: %v" , err )
2019-01-14 05:52:26 +08:00
}
2020-06-06 04:47:39 +08:00
2021-04-12 17:33:32 +08:00
if ctx . IsSet ( "skip-lfs-data" ) && ctx . Bool ( "skip-lfs-data" ) {
log . Info ( "Skip dumping LFS data" )
2023-03-23 20:30:28 +08:00
} else if ! setting . LFS . StartServer {
log . Info ( "LFS isn't enabled. Skip dumping LFS data" )
2023-03-13 18:23:51 +08:00
} else if err := storage . LFS . IterateObjects ( "" , func ( objPath string , object storage . Object ) error {
2020-09-29 17:05:13 +08:00
info , err := object . Stat ( )
if err != nil {
return err
2020-06-06 04:47:39 +08:00
}
2024-04-03 10:16:46 +08:00
return dumper . AddReader ( object , info , path . Join ( "data" , "lfs" , objPath ) )
2020-09-29 17:05:13 +08:00
} ) ; err != nil {
fatal ( "Failed to dump LFS objects: %v" , err )
2019-01-14 05:52:26 +08:00
}
2014-05-02 09:21:46 +08:00
}
2020-06-06 04:47:39 +08:00
tmpDir := ctx . String ( "tempdir" )
if _ , err := os . Stat ( tmpDir ) ; os . IsNotExist ( err ) {
fatal ( "Path does not exist: %s" , tmpDir )
}
2021-09-22 13:38:34 +08:00
dbDump , err := os . CreateTemp ( tmpDir , "gitea-db.sql" )
2020-06-06 04:47:39 +08:00
if err != nil {
fatal ( "Failed to create tmp file: %v" , err )
}
2020-08-12 04:05:34 +08:00
defer func ( ) {
2023-03-02 23:57:31 +08:00
_ = dbDump . Close ( )
2020-08-12 04:05:34 +08:00
if err := util . Remove ( dbDump . Name ( ) ) ; err != nil {
log . Warn ( "Unable to remove temporary file: %s: Error: %v" , dbDump . Name ( ) , err )
}
} ( )
2020-06-06 04:47:39 +08:00
2017-01-03 16:20:28 +08:00
targetDBType := ctx . String ( "database" )
2023-03-07 18:51:06 +08:00
if len ( targetDBType ) > 0 && targetDBType != setting . Database . Type . String ( ) {
2019-12-18 00:12:10 +08:00
log . Info ( "Dumping database %s => %s..." , setting . Database . Type , targetDBType )
2017-01-03 16:20:28 +08:00
} else {
2019-12-18 00:12:10 +08:00
log . Info ( "Dumping database..." )
2017-01-03 16:20:28 +08:00
}
2021-09-19 19:49:59 +08:00
if err := db . DumpDatabase ( dbDump . Name ( ) , targetDBType ) ; err != nil {
2019-12-18 00:12:10 +08:00
fatal ( "Failed to dump database: %v" , err )
2014-05-05 12:55:17 +08:00
}
2024-04-03 10:16:46 +08:00
if err = dumper . AddFile ( "gitea-db.sql" , dbDump . Name ( ) ) ; err != nil {
2019-12-18 00:12:10 +08:00
fatal ( "Failed to include gitea-db.sql: %v" , err )
2015-11-28 19:11:38 +08:00
}
2019-04-05 21:24:28 +08:00
2024-04-03 10:16:46 +08:00
log . Info ( "Adding custom configuration file from %s" , setting . CustomConf )
if err = dumper . AddFile ( "app.ini" , setting . CustomConf ) ; err != nil {
fatal ( "Failed to include specified app.ini: %v" , err )
2019-04-05 21:24:28 +08:00
}
2021-02-08 09:00:12 +08:00
if ctx . IsSet ( "skip-custom-dir" ) && ctx . Bool ( "skip-custom-dir" ) {
2021-07-08 19:38:13 +08:00
log . Info ( "Skipping custom directory" )
2021-02-08 09:00:12 +08:00
} else {
customDir , err := os . Stat ( setting . CustomPath )
if err == nil && customDir . IsDir ( ) {
2024-04-03 10:16:46 +08:00
if is , _ := dump . IsSubdir ( setting . AppDataPath , setting . CustomPath ) ; ! is {
if err := dumper . AddRecursiveExclude ( "custom" , setting . CustomPath , nil ) ; err != nil {
2021-02-08 09:00:12 +08:00
fatal ( "Failed to include custom: %v" , err )
}
} else {
log . Info ( "Custom dir %s is inside data dir %s, skipped" , setting . CustomPath , setting . AppDataPath )
2020-06-06 04:47:39 +08:00
}
} else {
2021-02-08 09:00:12 +08:00
log . Info ( "Custom dir %s doesn't exist, skipped" , setting . CustomPath )
2016-05-13 02:32:28 +08:00
}
2015-11-28 19:11:38 +08:00
}
2017-01-12 12:47:20 +08:00
2020-11-28 10:42:08 +08:00
isExist , err := util . IsExist ( setting . AppDataPath )
if err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , setting . AppDataPath , err )
}
if isExist {
2019-12-18 00:12:10 +08:00
log . Info ( "Packing data directory...%s" , setting . AppDataPath )
2017-01-12 12:47:20 +08:00
2020-06-06 04:47:39 +08:00
var excludes [ ] string
2023-02-20 00:12:01 +08:00
if setting . SessionConfig . OriginalProvider == "file" {
2020-06-06 04:47:39 +08:00
var opts session . Options
if err = json . Unmarshal ( [ ] byte ( setting . SessionConfig . ProviderConfig ) , & opts ) ; err != nil {
return err
}
excludes = append ( excludes , opts . ProviderConfig )
2017-03-02 17:41:33 +08:00
}
2020-06-06 04:47:39 +08:00
2022-10-24 11:19:21 +08:00
if ctx . IsSet ( "skip-index" ) && ctx . Bool ( "skip-index" ) {
excludes = append ( excludes , setting . Indexer . RepoPath )
excludes = append ( excludes , setting . Indexer . IssuePath )
}
2020-06-06 04:47:39 +08:00
excludes = append ( excludes , setting . RepoRootPath )
2023-06-14 11:42:38 +08:00
excludes = append ( excludes , setting . LFS . Storage . Path )
excludes = append ( excludes , setting . Attachment . Storage . Path )
excludes = append ( excludes , setting . Packages . Storage . Path )
2023-02-20 00:12:01 +08:00
excludes = append ( excludes , setting . Log . RootPath )
2024-04-03 10:16:46 +08:00
if err := dumper . AddRecursiveExclude ( "data" , setting . AppDataPath , excludes ) ; err != nil {
2019-12-18 00:12:10 +08:00
fatal ( "Failed to include data directory: %v" , err )
2017-03-02 17:41:33 +08:00
}
2017-01-12 12:47:20 +08:00
}
2021-04-12 17:33:32 +08:00
if ctx . IsSet ( "skip-attachment-data" ) && ctx . Bool ( "skip-attachment-data" ) {
log . Info ( "Skip dumping attachment data" )
2023-03-13 18:23:51 +08:00
} else if err := storage . Attachments . IterateObjects ( "" , func ( objPath string , object storage . Object ) error {
2020-09-29 17:05:13 +08:00
info , err := object . Stat ( )
if err != nil {
return err
}
2024-04-03 10:16:46 +08:00
return dumper . AddReader ( object , info , path . Join ( "data" , "attachments" , objPath ) )
2020-09-29 17:05:13 +08:00
} ) ; err != nil {
fatal ( "Failed to dump attachments: %v" , err )
}
2022-04-27 04:30:51 +08:00
if ctx . IsSet ( "skip-package-data" ) && ctx . Bool ( "skip-package-data" ) {
log . Info ( "Skip dumping package data" )
2023-03-23 20:30:28 +08:00
} else if ! setting . Packages . Enabled {
log . Info ( "Packages isn't enabled. Skip dumping package data" )
2023-03-13 18:23:51 +08:00
} else if err := storage . Packages . IterateObjects ( "" , func ( objPath string , object storage . Object ) error {
2022-04-27 04:30:51 +08:00
info , err := object . Stat ( )
if err != nil {
return err
}
2024-04-03 10:16:46 +08:00
return dumper . AddReader ( object , info , path . Join ( "data" , "packages" , objPath ) )
2022-04-27 04:30:51 +08:00
} ) ; err != nil {
fatal ( "Failed to dump packages: %v" , err )
}
2020-05-01 09:30:31 +08:00
// Doesn't check if LogRootPath exists before processing --skip-log intentionally,
// ensuring that it's clear the dump is skipped whether the directory's initialized
// yet or not.
if ctx . IsSet ( "skip-log" ) && ctx . Bool ( "skip-log" ) {
log . Info ( "Skip dumping log files" )
2020-11-28 10:42:08 +08:00
} else {
2023-02-20 00:12:01 +08:00
isExist , err := util . IsExist ( setting . Log . RootPath )
2020-11-28 10:42:08 +08:00
if err != nil {
2023-02-20 00:12:01 +08:00
log . Error ( "Unable to check if %s exists. Error: %v" , setting . Log . RootPath , err )
2020-11-28 10:42:08 +08:00
}
if isExist {
2024-04-03 10:16:46 +08:00
if err := dumper . AddRecursiveExclude ( "log" , setting . Log . RootPath , nil ) ; err != nil {
2020-11-28 10:42:08 +08:00
fatal ( "Failed to include log: %v" , err )
}
2020-01-17 10:56:51 +08:00
}
2015-11-28 19:11:38 +08:00
}
2017-02-26 16:01:49 +08:00
2024-04-03 10:16:46 +08:00
if outFileName == "-" {
log . Info ( "Finish dumping to stdout" )
} else {
if err = archiverWriter . Close ( ) ; err != nil {
_ = os . Remove ( outFileName )
fatal ( "Failed to save %q: %v" , outFileName , err )
2020-06-06 04:47:39 +08:00
}
2024-04-03 10:16:46 +08:00
if err = os . Chmod ( outFileName , 0 o600 ) ; err != nil {
2020-06-06 04:47:39 +08:00
log . Info ( "Can't change file access permissions mask to 0600: %v" , err )
}
2024-04-03 10:16:46 +08:00
log . Info ( "Finish dumping in file %s" , outFileName )
2017-01-12 12:47:20 +08:00
}
return nil
}