2014-05-02 05:21:46 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2016-12-21 15:13:17 +03:00
// Copyright 2016 The Gitea Authors. All rights reserved.
2014-05-02 05:21:46 +04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
2014-05-05 21:08:01 +04:00
"fmt"
2016-11-10 01:18:22 +03:00
"io/ioutil"
2014-05-02 05:21:46 +04:00
"os"
"path"
2017-01-12 07:47:20 +03:00
"path/filepath"
2014-05-05 21:08:01 +04:00
"time"
2014-05-02 05:21:46 +04:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/models"
2019-12-17 19:12:10 +03:00
"code.gitea.io/gitea/modules/log"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/setting"
2017-04-12 10:44:54 +03:00
2019-08-23 19:40:30 +03:00
"github.com/unknwon/cae/zip"
"github.com/unknwon/com"
2016-11-10 01:18:22 +03:00
"github.com/urfave/cli"
2014-05-02 05:21:46 +04:00
)
2016-11-04 14:42:18 +03:00
// CmdDump represents the available dump sub-command.
2014-05-02 05:21:46 +04:00
var CmdDump = cli . Command {
Name : "dump" ,
2016-12-21 15:13:17 +03:00
Usage : "Dump Gitea files and database" ,
2014-05-05 08:55:17 +04:00
Description : ` Dump compresses all related files and database into zip file .
2016-12-21 15:13:17 +03:00
It can be used for backup and capture Gitea server image to send to maintainer ` ,
2014-05-02 05:21:46 +04:00
Action : runDump ,
2014-09-08 03:39:26 +04:00
Flags : [ ] cli . Flag {
2019-04-01 07:31:37 +03:00
cli . StringFlag {
Name : "file, f" ,
Value : fmt . Sprintf ( "gitea-dump-%d.zip" , time . Now ( ) . Unix ( ) ) ,
Usage : "Name of the dump file which will be created." ,
} ,
2016-11-10 01:18:22 +03:00
cli . BoolFlag {
2019-05-01 23:36:09 +03:00
Name : "verbose, V" ,
2016-11-10 01:18:22 +03:00
Usage : "Show process details" ,
} ,
cli . StringFlag {
Name : "tempdir, t" ,
Value : os . TempDir ( ) ,
Usage : "Temporary dir path" ,
} ,
2017-01-03 11:20:28 +03:00
cli . StringFlag {
Name : "database, d" ,
Usage : "Specify the database SQL syntax" ,
} ,
2019-01-14 00:52:26 +03:00
cli . BoolFlag {
Name : "skip-repository, R" ,
Usage : "Skip the repository dumping" ,
} ,
2014-09-08 03:39:26 +04:00
} ,
2014-05-02 05:21:46 +04:00
}
2019-12-17 19:12:10 +03:00
func fatal ( format string , args ... interface { } ) {
fmt . Fprintf ( os . Stderr , format + "\n" , args ... )
log . Fatal ( format , args ... )
}
2016-05-12 21:32:28 +03:00
func runDump ( ctx * cli . Context ) error {
2015-09-17 06:08:46 +03:00
setting . NewContext ( )
2017-01-12 07:47:20 +03:00
setting . NewServices ( ) // cannot access session settings otherwise
2017-01-23 12:11:18 +03:00
err := models . SetEngine ( )
if err != nil {
return err
}
2014-05-02 05:21:46 +04:00
2016-05-24 03:10:05 +03:00
tmpDir := ctx . String ( "tempdir" )
if _ , err := os . Stat ( tmpDir ) ; os . IsNotExist ( err ) {
2019-12-17 19:12:10 +03:00
fatal ( "Path does not exist: %s" , tmpDir )
2016-05-24 03:10:05 +03:00
}
2018-01-13 01:16:49 +03:00
tmpWorkDir , err := ioutil . TempDir ( tmpDir , "gitea-dump-" )
2015-11-28 16:07:51 +03:00
if err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to create tmp work directory: %v" , err )
2015-11-28 16:07:51 +03:00
}
2019-12-17 19:12:10 +03:00
log . Info ( "Creating tmp work dir: %s" , tmpWorkDir )
2015-11-28 16:07:51 +03:00
2017-06-09 03:24:15 +03:00
// work-around #1103
if os . Getenv ( "TMPDIR" ) == "" {
2018-01-13 01:16:49 +03:00
os . Setenv ( "TMPDIR" , tmpWorkDir )
2017-06-09 03:24:15 +03:00
}
2018-01-13 01:16:49 +03:00
dbDump := path . Join ( tmpWorkDir , "gitea-db.sql" )
2015-11-28 16:07:51 +03:00
2019-04-01 07:31:37 +03:00
fileName := ctx . String ( "file" )
2019-12-17 19:12:10 +03:00
log . Info ( "Packing dump files..." )
2019-01-14 00:52:26 +03:00
z , err := zip . Create ( fileName )
if err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to create %s: %v" , fileName , err )
2019-01-14 00:52:26 +03:00
}
2019-12-17 19:12:10 +03:00
2014-09-08 03:39:26 +04:00
zip . Verbose = ctx . Bool ( "verbose" )
2019-01-14 00:52:26 +03:00
if ctx . IsSet ( "skip-repository" ) {
2019-12-17 19:12:10 +03:00
log . Info ( "Skip dumping local repositories" )
2019-01-14 00:52:26 +03:00
} else {
2019-12-17 19:12:10 +03:00
log . Info ( "Dumping local repositories...%s" , setting . RepoRootPath )
2019-01-14 00:52:26 +03:00
reposDump := path . Join ( tmpWorkDir , "gitea-repo.zip" )
if err := zip . PackTo ( setting . RepoRootPath , reposDump , true ) ; err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to dump local repositories: %v" , err )
2019-01-14 00:52:26 +03:00
}
if err := z . AddFile ( "gitea-repo.zip" , reposDump ) ; err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to include gitea-repo.zip: %v" , err )
2019-01-14 00:52:26 +03:00
}
2014-05-02 05:21:46 +04:00
}
2017-01-03 11:20:28 +03:00
targetDBType := ctx . String ( "database" )
2019-08-24 12:24:45 +03:00
if len ( targetDBType ) > 0 && targetDBType != setting . Database . Type {
2019-12-17 19:12:10 +03:00
log . Info ( "Dumping database %s => %s..." , setting . Database . Type , targetDBType )
2017-01-03 11:20:28 +03:00
} else {
2019-12-17 19:12:10 +03:00
log . Info ( "Dumping database..." )
2017-01-03 11:20:28 +03:00
}
if err := models . DumpDatabase ( dbDump , targetDBType ) ; err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to dump database: %v" , err )
2014-05-05 08:55:17 +04:00
}
2016-12-21 15:13:17 +03:00
if err := z . AddFile ( "gitea-db.sql" , dbDump ) ; err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to include gitea-db.sql: %v" , err )
2015-11-28 14:11:38 +03:00
}
2019-04-05 16:24:28 +03:00
if len ( setting . CustomConf ) > 0 {
2019-12-17 19:12:10 +03:00
log . Info ( "Adding custom configuration file from %s" , setting . CustomConf )
2019-04-05 16:24:28 +03:00
if err := z . AddFile ( "app.ini" , setting . CustomConf ) ; err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to include specified app.ini: %v" , err )
2019-04-05 16:24:28 +03:00
}
}
2015-11-28 17:08:50 +03:00
customDir , err := os . Stat ( setting . CustomPath )
if err == nil && customDir . IsDir ( ) {
2016-05-12 21:32:28 +03:00
if err := z . AddDir ( "custom" , setting . CustomPath ) ; err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to include custom: %v" , err )
2016-05-12 21:32:28 +03:00
}
2015-11-28 17:08:50 +03:00
} else {
2019-12-17 19:12:10 +03:00
log . Info ( "Custom dir %s doesn't exist, skipped" , setting . CustomPath )
2015-11-28 14:11:38 +03:00
}
2017-01-12 07:47:20 +03:00
2017-03-02 12:41:33 +03:00
if com . IsExist ( setting . AppDataPath ) {
2019-12-17 19:12:10 +03:00
log . Info ( "Packing data directory...%s" , setting . AppDataPath )
2017-01-12 07:47:20 +03:00
2017-03-02 12:41:33 +03:00
var sessionAbsPath string
if setting . SessionConfig . Provider == "file" {
2017-11-03 11:56:20 +03:00
sessionAbsPath = setting . SessionConfig . ProviderConfig
2017-03-02 12:41:33 +03:00
}
if err := zipAddDirectoryExclude ( z , "data" , setting . AppDataPath , sessionAbsPath ) ; err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to include data directory: %v" , err )
2017-03-02 12:41:33 +03:00
}
2017-01-12 07:47:20 +03:00
}
2020-01-17 05:56:51 +03:00
if com . IsExist ( setting . LogRootPath ) {
if err := z . AddDir ( "log" , setting . LogRootPath ) ; err != nil {
fatal ( "Failed to include log: %v" , err )
}
2015-11-28 14:11:38 +03:00
}
2017-02-26 11:01:49 +03:00
2014-05-05 08:55:17 +04:00
if err = z . Close ( ) ; err != nil {
2016-12-01 02:56:15 +03:00
_ = os . Remove ( fileName )
2019-12-17 19:12:10 +03:00
fatal ( "Failed to save %s: %v" , fileName , err )
2014-05-05 08:55:17 +04:00
}
2014-05-02 05:21:46 +04:00
2016-08-17 21:38:42 +03:00
if err := os . Chmod ( fileName , 0600 ) ; err != nil {
2019-12-17 19:12:10 +03:00
log . Info ( "Can't change file access permissions mask to 0600: %v" , err )
2016-08-17 21:38:42 +03:00
}
2019-12-17 19:12:10 +03:00
log . Info ( "Removing tmp work dir: %s" , tmpWorkDir )
2016-12-01 02:56:15 +03:00
2018-01-13 01:16:49 +03:00
if err := os . RemoveAll ( tmpWorkDir ) ; err != nil {
2019-12-17 19:12:10 +03:00
fatal ( "Failed to remove %s: %v" , tmpWorkDir , err )
2016-12-01 02:56:15 +03:00
}
2019-12-17 19:12:10 +03:00
log . Info ( "Finish dumping in file %s" , fileName )
2016-05-12 21:32:28 +03:00
return nil
2014-05-02 05:21:46 +04:00
}
2017-01-12 07:47:20 +03:00
// zipAddDirectoryExclude zips absPath to specified zipPath inside z excluding excludeAbsPath
func zipAddDirectoryExclude ( zip * zip . ZipArchive , zipPath , absPath string , excludeAbsPath string ) error {
absPath , err := filepath . Abs ( absPath )
if err != nil {
return err
}
dir , err := os . Open ( absPath )
if err != nil {
return err
}
defer dir . Close ( )
zip . AddEmptyDir ( zipPath )
files , err := dir . Readdir ( 0 )
if err != nil {
return err
}
for _ , file := range files {
currentAbsPath := path . Join ( absPath , file . Name ( ) )
currentZipPath := path . Join ( zipPath , file . Name ( ) )
if file . IsDir ( ) {
if currentAbsPath != excludeAbsPath {
if err = zipAddDirectoryExclude ( zip , currentZipPath , currentAbsPath , excludeAbsPath ) ; err != nil {
return err
}
}
} else {
if err = zip . AddFile ( currentZipPath , currentAbsPath ) ; err != nil {
return err
}
}
}
return nil
}