data.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // This is a centralized place to connect to the database, and hold a reference to it.
  2. // Other packages can share this reference. This package would also be a place to add any kind of
  3. // persistence-related convenience methods or migrations.
  4. package data
  5. import (
  6. "database/sql"
  7. "fmt"
  8. "os"
  9. "path/filepath"
  10. "time"
  11. "github.com/owncast/owncast/config"
  12. "github.com/owncast/owncast/utils"
  13. log "github.com/sirupsen/logrus"
  14. )
  15. const (
  16. schemaVersion = 7
  17. )
  18. var (
  19. _db *sql.DB
  20. _datastore *Datastore
  21. )
  22. // GetDatabase will return the shared instance of the actual database.
  23. func GetDatabase() *sql.DB {
  24. return _db
  25. }
  26. // GetStore will return the shared instance of the read/write datastore.
  27. func GetStore() *Datastore {
  28. return _datastore
  29. }
  30. // SetupPersistence will open the datastore and make it available.
  31. func SetupPersistence(file string) error {
  32. // Allow support for in-memory databases for tests.
  33. var db *sql.DB
  34. if file == ":memory:" {
  35. inMemoryDb, err := sql.Open("sqlite3", file)
  36. if err != nil {
  37. log.Fatal(err.Error())
  38. }
  39. db = inMemoryDb
  40. } else {
  41. // Create empty DB file if it doesn't exist.
  42. if !utils.DoesFileExists(file) {
  43. log.Traceln("Creating new database at", file)
  44. _, err := os.Create(file) //nolint:gosec
  45. if err != nil {
  46. log.Fatal(err.Error())
  47. }
  48. }
  49. onDiskDb, err := sql.Open("sqlite3", fmt.Sprintf("file:%s?_cache_size=10000&cache=shared&_journal_mode=WAL", file))
  50. if err != nil {
  51. return err
  52. }
  53. db = onDiskDb
  54. db.SetMaxOpenConns(1)
  55. }
  56. _db = db
  57. // Some SQLite optimizations
  58. _, _ = db.Exec("pragma journal_mode = WAL")
  59. _, _ = db.Exec("pragma synchronous = normal")
  60. _, _ = db.Exec("pragma temp_store = memory")
  61. _, _ = db.Exec("pragma wal_checkpoint(full)")
  62. createWebhooksTable()
  63. createUsersTable(db)
  64. createAccessTokenTable(db)
  65. if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS config (
  66. "key" string NOT NULL PRIMARY KEY,
  67. "value" TEXT
  68. );`); err != nil {
  69. return err
  70. }
  71. var version int
  72. err := db.QueryRow("SELECT value FROM config WHERE key='version'").
  73. Scan(&version)
  74. if err != nil {
  75. if err != sql.ErrNoRows {
  76. return err
  77. }
  78. // fresh database: initialize it with the current schema version
  79. _, err := db.Exec("INSERT INTO config(key, value) VALUES(?, ?)", "version", schemaVersion)
  80. if err != nil {
  81. return err
  82. }
  83. version = schemaVersion
  84. }
  85. // is database from a newer Owncast version?
  86. if version > schemaVersion {
  87. return fmt.Errorf("incompatible database version %d (versions up to %d are supported)",
  88. version, schemaVersion)
  89. }
  90. // is database schema outdated?
  91. if version < schemaVersion {
  92. if err := migrateDatabaseSchema(db, version, schemaVersion); err != nil {
  93. return err
  94. }
  95. }
  96. _datastore = &Datastore{}
  97. _datastore.Setup()
  98. dbBackupTicker := time.NewTicker(1 * time.Hour)
  99. go func() {
  100. backupFile := filepath.Join(config.BackupDirectory, "owncastdb.bak")
  101. for range dbBackupTicker.C {
  102. utils.Backup(_db, backupFile)
  103. }
  104. }()
  105. return nil
  106. }