You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dump.go 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2016 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package cmd
  6. import (
  7. "encoding/json"
  8. "fmt"
  9. "io/ioutil"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "strings"
  14. "time"
  15. "code.gitea.io/gitea/models"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/setting"
  18. "gitea.com/macaron/session"
  19. archiver "github.com/mholt/archiver/v3"
  20. "github.com/unknwon/com"
  21. "github.com/urfave/cli"
  22. )
  23. func addFile(w archiver.Writer, filePath string, absPath string, verbose bool) error {
  24. if verbose {
  25. log.Info("Adding file %s\n", filePath)
  26. }
  27. file, err := os.Open(absPath)
  28. if err != nil {
  29. return err
  30. }
  31. defer file.Close()
  32. fileInfo, err := file.Stat()
  33. if err != nil {
  34. return err
  35. }
  36. return w.Write(archiver.File{
  37. FileInfo: archiver.FileInfo{
  38. FileInfo: fileInfo,
  39. CustomName: filePath,
  40. },
  41. ReadCloser: file,
  42. })
  43. }
  44. func addRecursive(w archiver.Writer, dirPath string, absPath string, verbose bool) error {
  45. if verbose {
  46. log.Info("Adding dir %s\n", dirPath)
  47. }
  48. dir, err := os.Open(absPath)
  49. if err != nil {
  50. return fmt.Errorf("Could not open directory %s: %s", absPath, err)
  51. }
  52. files, err := dir.Readdir(0)
  53. if err != nil {
  54. return fmt.Errorf("Unable to list files in %s: %s", absPath, err)
  55. }
  56. if err := addFile(w, dirPath, absPath, false); err != nil {
  57. return err
  58. }
  59. for _, fileInfo := range files {
  60. if fileInfo.IsDir() {
  61. err = addRecursive(w, filepath.Join(dirPath, fileInfo.Name()), filepath.Join(absPath, fileInfo.Name()), verbose)
  62. } else {
  63. err = addFile(w, filepath.Join(dirPath, fileInfo.Name()), filepath.Join(absPath, fileInfo.Name()), verbose)
  64. }
  65. if err != nil {
  66. return err
  67. }
  68. }
  69. return nil
  70. }
  71. func isSubdir(upper string, lower string) (bool, error) {
  72. if relPath, err := filepath.Rel(upper, lower); err != nil {
  73. return false, err
  74. } else if relPath == "." || !strings.HasPrefix(relPath, ".") {
  75. return true, nil
  76. }
  77. return false, nil
  78. }
  79. type outputType struct {
  80. Enum []string
  81. Default string
  82. selected string
  83. }
  84. func (o outputType) Join() string {
  85. return strings.Join(o.Enum, ", ")
  86. }
  87. func (o *outputType) Set(value string) error {
  88. for _, enum := range o.Enum {
  89. if enum == value {
  90. o.selected = value
  91. return nil
  92. }
  93. }
  94. return fmt.Errorf("allowed values are %s", o.Join())
  95. }
  96. func (o outputType) String() string {
  97. if o.selected == "" {
  98. return o.Default
  99. }
  100. return o.selected
  101. }
  102. var outputTypeEnum = &outputType{
  103. Enum: []string{"zip", "tar", "tar.gz", "tar.xz", "tar.bz2"},
  104. Default: "zip",
  105. }
  106. // CmdDump represents the available dump sub-command.
  107. var CmdDump = cli.Command{
  108. Name: "dump",
  109. Usage: "Dump Gitea files and database",
  110. Description: `Dump compresses all related files and database into zip file.
  111. It can be used for backup and capture Gitea server image to send to maintainer`,
  112. Action: runDump,
  113. Flags: []cli.Flag{
  114. cli.StringFlag{
  115. Name: "file, f",
  116. Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()),
  117. Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
  118. },
  119. cli.BoolFlag{
  120. Name: "verbose, V",
  121. Usage: "Show process details",
  122. },
  123. cli.StringFlag{
  124. Name: "tempdir, t",
  125. Value: os.TempDir(),
  126. Usage: "Temporary dir path",
  127. },
  128. cli.StringFlag{
  129. Name: "database, d",
  130. Usage: "Specify the database SQL syntax",
  131. },
  132. cli.BoolFlag{
  133. Name: "skip-repository, R",
  134. Usage: "Skip the repository dumping",
  135. },
  136. cli.BoolFlag{
  137. Name: "skip-log, L",
  138. Usage: "Skip the log dumping",
  139. },
  140. cli.GenericFlag{
  141. Name: "type",
  142. Value: outputTypeEnum,
  143. Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
  144. },
  145. },
  146. }
  147. func fatal(format string, args ...interface{}) {
  148. fmt.Fprintf(os.Stderr, format+"\n", args...)
  149. log.Fatal(format, args...)
  150. }
  151. func runDump(ctx *cli.Context) error {
  152. var file *os.File
  153. fileName := ctx.String("file")
  154. if fileName == "-" {
  155. file = os.Stdout
  156. err := log.DelLogger("console")
  157. if err != nil {
  158. fatal("Deleting default logger failed. Can not write to stdout: %v", err)
  159. }
  160. }
  161. setting.NewContext()
  162. // make sure we are logging to the console no matter what the configuration tells us do to
  163. if _, err := setting.Cfg.Section("log").NewKey("MODE", "console"); err != nil {
  164. fatal("Setting logging mode to console failed: %v", err)
  165. }
  166. if _, err := setting.Cfg.Section("log.console").NewKey("STDERR", "true"); err != nil {
  167. fatal("Setting console logger to stderr failed: %v", err)
  168. }
  169. setting.NewServices() // cannot access session settings otherwise
  170. err := models.SetEngine()
  171. if err != nil {
  172. return err
  173. }
  174. if file == nil {
  175. file, err = os.Create(fileName)
  176. if err != nil {
  177. fatal("Unable to open %s: %v", fileName, err)
  178. }
  179. }
  180. defer file.Close()
  181. verbose := ctx.Bool("verbose")
  182. outType := ctx.String("type")
  183. var iface interface{}
  184. if fileName == "-" {
  185. iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType))
  186. } else {
  187. iface, err = archiver.ByExtension(fileName)
  188. }
  189. if err != nil {
  190. fatal("Unable to get archiver for extension: %v", err)
  191. }
  192. w, _ := iface.(archiver.Writer)
  193. if err := w.Create(file); err != nil {
  194. fatal("Creating archiver.Writer failed: %v", err)
  195. }
  196. defer w.Close()
  197. if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
  198. log.Info("Skip dumping local repositories")
  199. } else {
  200. log.Info("Dumping local repositories... %s", setting.RepoRootPath)
  201. if err := addRecursive(w, "repos", setting.RepoRootPath, verbose); err != nil {
  202. fatal("Failed to include repositories: %v", err)
  203. }
  204. if _, err := os.Stat(setting.LFS.ContentPath); !os.IsNotExist(err) {
  205. log.Info("Dumping lfs... %s", setting.LFS.ContentPath)
  206. if err := addRecursive(w, "lfs", setting.LFS.ContentPath, verbose); err != nil {
  207. fatal("Failed to include lfs: %v", err)
  208. }
  209. }
  210. }
  211. tmpDir := ctx.String("tempdir")
  212. if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
  213. fatal("Path does not exist: %s", tmpDir)
  214. }
  215. dbDump, err := ioutil.TempFile(tmpDir, "gitea-db.sql")
  216. if err != nil {
  217. fatal("Failed to create tmp file: %v", err)
  218. }
  219. defer os.Remove(dbDump.Name())
  220. targetDBType := ctx.String("database")
  221. if len(targetDBType) > 0 && targetDBType != setting.Database.Type {
  222. log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
  223. } else {
  224. log.Info("Dumping database...")
  225. }
  226. if err := models.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
  227. fatal("Failed to dump database: %v", err)
  228. }
  229. if err := addFile(w, "gitea-db.sql", dbDump.Name(), verbose); err != nil {
  230. fatal("Failed to include gitea-db.sql: %v", err)
  231. }
  232. if len(setting.CustomConf) > 0 {
  233. log.Info("Adding custom configuration file from %s", setting.CustomConf)
  234. if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil {
  235. fatal("Failed to include specified app.ini: %v", err)
  236. }
  237. }
  238. customDir, err := os.Stat(setting.CustomPath)
  239. if err == nil && customDir.IsDir() {
  240. if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is {
  241. if err := addRecursive(w, "custom", setting.CustomPath, verbose); err != nil {
  242. fatal("Failed to include custom: %v", err)
  243. }
  244. } else {
  245. log.Info("Custom dir %s is inside data dir %s, skipped", setting.CustomPath, setting.AppDataPath)
  246. }
  247. } else {
  248. log.Info("Custom dir %s doesn't exist, skipped", setting.CustomPath)
  249. }
  250. if com.IsExist(setting.AppDataPath) {
  251. log.Info("Packing data directory...%s", setting.AppDataPath)
  252. var excludes []string
  253. if setting.Cfg.Section("session").Key("PROVIDER").Value() == "file" {
  254. var opts session.Options
  255. if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil {
  256. return err
  257. }
  258. excludes = append(excludes, opts.ProviderConfig)
  259. }
  260. excludes = append(excludes, setting.RepoRootPath)
  261. excludes = append(excludes, setting.LFS.ContentPath)
  262. excludes = append(excludes, setting.LogRootPath)
  263. if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
  264. fatal("Failed to include data directory: %v", err)
  265. }
  266. }
  267. // Doesn't check if LogRootPath exists before processing --skip-log intentionally,
  268. // ensuring that it's clear the dump is skipped whether the directory's initialized
  269. // yet or not.
  270. if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
  271. log.Info("Skip dumping log files")
  272. } else if com.IsExist(setting.LogRootPath) {
  273. if err := addRecursive(w, "log", setting.LogRootPath, verbose); err != nil {
  274. fatal("Failed to include log: %v", err)
  275. }
  276. }
  277. if fileName != "-" {
  278. if err = w.Close(); err != nil {
  279. _ = os.Remove(fileName)
  280. fatal("Failed to save %s: %v", fileName, err)
  281. }
  282. if err := os.Chmod(fileName, 0600); err != nil {
  283. log.Info("Can't change file access permissions mask to 0600: %v", err)
  284. }
  285. }
  286. if fileName != "-" {
  287. log.Info("Finish dumping in file %s", fileName)
  288. } else {
  289. log.Info("Finish dumping to stdout")
  290. }
  291. return nil
  292. }
  293. func contains(slice []string, s string) bool {
  294. for _, v := range slice {
  295. if v == s {
  296. return true
  297. }
  298. }
  299. return false
  300. }
  301. // addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath
  302. func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error {
  303. absPath, err := filepath.Abs(absPath)
  304. if err != nil {
  305. return err
  306. }
  307. dir, err := os.Open(absPath)
  308. if err != nil {
  309. return err
  310. }
  311. defer dir.Close()
  312. files, err := dir.Readdir(0)
  313. if err != nil {
  314. return err
  315. }
  316. for _, file := range files {
  317. currentAbsPath := path.Join(absPath, file.Name())
  318. currentInsidePath := path.Join(insidePath, file.Name())
  319. if file.IsDir() {
  320. if !contains(excludeAbsPath, currentAbsPath) {
  321. if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
  322. return err
  323. }
  324. if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
  325. return err
  326. }
  327. }
  328. } else {
  329. if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
  330. return err
  331. }
  332. }
  333. }
  334. return nil
  335. }