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
"log"
"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"
"code.gitea.io/gitea/modules/setting"
2016-11-14 19:03:37 +03:00
"github.com/Unknwon/cae/zip"
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 {
2016-11-10 01:18:22 +03:00
cli . StringFlag {
Name : "config, c" ,
Value : "custom/conf/app.ini" ,
Usage : "Custom configuration file path" ,
} ,
cli . BoolFlag {
Name : "verbose, v" ,
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" ,
} ,
2014-09-08 03:39:26 +04:00
} ,
2014-05-02 05:21:46 +04:00
}
2016-05-12 21:32:28 +03:00
func runDump ( ctx * cli . Context ) error {
2015-02-05 13:12:37 +03:00
if ctx . IsSet ( "config" ) {
setting . CustomConf = ctx . String ( "config" )
}
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
2015-09-17 06:08:46 +03:00
models . LoadConfigs ( )
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 ) {
log . Fatalf ( "Path does not exist: %s" , tmpDir )
}
2016-12-21 15:13:17 +03:00
TmpWorkDir , err := ioutil . TempDir ( tmpDir , "gitea-dump-" )
2015-11-28 16:07:51 +03:00
if err != nil {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "Failed to create tmp work directory: %v" , err )
2015-11-28 16:07:51 +03:00
}
log . Printf ( "Creating tmp work dir: %s" , TmpWorkDir )
2016-12-21 15:13:17 +03:00
reposDump := path . Join ( TmpWorkDir , "gitea-repo.zip" )
dbDump := path . Join ( TmpWorkDir , "gitea-db.sql" )
2015-11-28 16:07:51 +03:00
2014-05-26 04:11:25 +04:00
log . Printf ( "Dumping local repositories...%s" , setting . RepoRootPath )
2014-09-08 03:39:26 +04:00
zip . Verbose = ctx . Bool ( "verbose" )
2015-11-28 16:07:51 +03:00
if err := zip . PackTo ( setting . RepoRootPath , reposDump , true ) ; err != nil {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "Failed to dump local repositories: %v" , err )
2014-05-02 05:21:46 +04:00
}
2017-01-03 11:20:28 +03:00
targetDBType := ctx . String ( "database" )
if len ( targetDBType ) > 0 && targetDBType != models . DbCfg . Type {
log . Printf ( "Dumping database %s => %s..." , models . DbCfg . Type , targetDBType )
} else {
log . Printf ( "Dumping database..." )
}
if err := models . DumpDatabase ( dbDump , targetDBType ) ; err != nil {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "Failed to dump database: %v" , err )
2014-05-05 08:55:17 +04:00
}
2016-12-21 15:13:17 +03:00
fileName := fmt . Sprintf ( "gitea-dump-%d.zip" , time . Now ( ) . Unix ( ) )
2014-05-05 08:55:17 +04:00
log . Printf ( "Packing dump files..." )
2014-05-05 21:08:01 +04:00
z , err := zip . Create ( fileName )
2014-05-02 05:21:46 +04:00
if err != nil {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "Failed to create %s: %v" , fileName , err )
2014-05-02 05:21:46 +04:00
}
2016-12-21 15:13:17 +03:00
if err := z . AddFile ( "gitea-repo.zip" , reposDump ) ; err != nil {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "Failed to include gitea-repo.zip: %v" , err )
2015-11-28 14:11:38 +03:00
}
2016-12-21 15:13:17 +03:00
if err := z . AddFile ( "gitea-db.sql" , dbDump ) ; err != nil {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "Failed to include gitea-db.sql: %v" , err )
2015-11-28 14:11:38 +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 {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "Failed to include custom: %v" , err )
2016-05-12 21:32:28 +03:00
}
2015-11-28 17:08:50 +03:00
} else {
log . Printf ( "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
log . Printf ( "Packing data directory...%s" , setting . AppDataPath )
var sessionAbsPath string
if setting . SessionConfig . Provider == "file" {
if len ( setting . SessionConfig . ProviderConfig ) == 0 {
setting . SessionConfig . ProviderConfig = "data/sessions"
}
sessionAbsPath , _ = filepath . Abs ( setting . SessionConfig . ProviderConfig )
}
if err := zipAddDirectoryExclude ( z , "data" , setting . AppDataPath , sessionAbsPath ) ; err != nil {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "Failed to include data directory: %v" , err )
2017-01-12 07:47:20 +03:00
}
2016-05-12 21:32:28 +03:00
if err := z . AddDir ( "log" , setting . LogRootPath ) ; err != nil {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "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 )
2017-01-29 23:13:57 +03:00
log . Fatalf ( "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 {
log . Printf ( "Can't change file access permissions mask to 0600: %v" , err )
}
2015-11-28 16:07:51 +03:00
log . Printf ( "Removing tmp work dir: %s" , TmpWorkDir )
2016-12-01 02:56:15 +03:00
if err := os . RemoveAll ( TmpWorkDir ) ; err != nil {
2017-01-29 23:13:57 +03:00
log . Fatalf ( "Failed to remove %s: %v" , TmpWorkDir , err )
2016-12-01 02:56:15 +03:00
}
2015-11-28 17:22:10 +03:00
log . Printf ( "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
}