2023-10-17 06:31:13 +02:00
|
|
|
// Copyright 2023, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2022-07-01 19:48:14 +02:00
|
|
|
package sstore
|
|
|
|
|
|
|
|
import (
|
2023-11-02 22:05:43 +01:00
|
|
|
"errors"
|
2022-07-01 19:48:14 +02:00
|
|
|
"fmt"
|
2023-03-12 22:42:18 +01:00
|
|
|
"io"
|
2023-11-02 22:05:43 +01:00
|
|
|
"io/fs"
|
2022-10-31 20:40:45 +01:00
|
|
|
"log"
|
2023-03-12 22:42:18 +01:00
|
|
|
"os"
|
2022-07-01 19:48:14 +02:00
|
|
|
"strconv"
|
2023-01-23 08:10:18 +01:00
|
|
|
"time"
|
2022-07-01 19:48:14 +02:00
|
|
|
|
|
|
|
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
|
|
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
2022-10-31 20:24:21 +01:00
|
|
|
"github.com/golang-migrate/migrate/v4/source/iofs"
|
2022-07-01 19:48:14 +02:00
|
|
|
_ "github.com/mattn/go-sqlite3"
|
2023-10-26 07:07:00 +02:00
|
|
|
sh2db "github.com/wavetermdev/waveterm/wavesrv/db"
|
2022-07-01 19:48:14 +02:00
|
|
|
|
|
|
|
"github.com/golang-migrate/migrate/v4"
|
|
|
|
)
|
|
|
|
|
Allow dots in alias and add port to canonical name (#209)
* allow numerical start and allow dots in ssh alias
We previously restricted the ssh alias to start with an
alphabetic character and did not allow dots within it. This caused
problems with users wanting to use an ip address as an alias. This lifts
that restriction so both can freely be used.
Note that while it is common to use the hostname as the alias, hostnames
are not allowed to use the underscore character. However, we do allow
the alias to contain an underscore. I do not think we can remove this
from aliases now but it is something to watch out for in the future.
* allow backslash in username
This involved converting the regexp string into a raw string to able to
use the backslash character. this is important.
* Revert "allow backslash in username"
This reverts commit cafe2812411cfba15b7ece1250a6def29d092366. Upon
reflection, more time is needed to evaluate that this actually corrects
the issue. It will be performed with proper diligence at a later time.
* add port to end of canoncial names when not 22
The canonical name is the identifying key in the database, so it causes
problems if another remote entry has the same canonical name. By adding
the port number to the end of this, it is possible to differentiate the
two.
* add db migrations for adding port to canonicalname
The up migration adds the port to the existing canonical id if it exists
and is not 22. The down migration strips the port off the canonical name
and deletes extra remotes with the same canonical name. If all remotes
with that canonical name have been archived, it keeps the first one
added to the database. If any have not been archived, it keeps the first
added to the database from the non-archived group.
* remove ability to edit port number via ssh import
Previously, ssh imports could edit the port number since it was possible
to change them in the config file without changin the canonical name.
Now that the canonical name contains the port, a change in the port will
simply create a new database entry. For this reason, the ability to
modify the ssh port is dead code and has been removed.
* allow backslash in username
This involved converting the regexp string into a raw string to able to
use the backslash character. this is important.
* simplify up migration logic
The previous up migration was suboptimal because it was accounting for a
corner case not found in production. That case no longer needs to be
considered, so the procedure was simplified.
2024-01-04 19:16:26 +01:00
|
|
|
const MaxMigration = 29
|
2023-03-12 22:42:18 +01:00
|
|
|
const MigratePrimaryScreenVersion = 9
|
2023-07-28 20:48:11 +02:00
|
|
|
const CmdScreenSpecialMigration = 13
|
2023-07-31 02:16:43 +02:00
|
|
|
const CmdLineSpecialMigration = 20
|
2023-03-12 22:42:18 +01:00
|
|
|
|
2022-07-01 19:48:14 +02:00
|
|
|
func MakeMigrate() (*migrate.Migrate, error) {
|
2022-10-31 20:24:21 +01:00
|
|
|
fsVar, err := iofs.New(sh2db.MigrationFS, "migrations")
|
2022-07-01 19:48:14 +02:00
|
|
|
if err != nil {
|
2022-10-31 20:24:21 +01:00
|
|
|
return nil, fmt.Errorf("opening iofs: %w", err)
|
2022-07-01 19:48:14 +02:00
|
|
|
}
|
2022-10-31 20:24:21 +01:00
|
|
|
// migrationPathUrl := fmt.Sprintf("file://%s", path.Join(wd, "db", "migrations"))
|
2023-03-12 22:42:18 +01:00
|
|
|
dbUrl := fmt.Sprintf("sqlite3://%s", GetDBName())
|
2022-10-31 20:24:21 +01:00
|
|
|
m, err := migrate.NewWithSourceInstance("iofs", fsVar, dbUrl)
|
|
|
|
// m, err := migrate.New(migrationPathUrl, dbUrl)
|
2022-07-01 19:48:14 +02:00
|
|
|
if err != nil {
|
2023-03-12 22:42:18 +01:00
|
|
|
return nil, fmt.Errorf("making migration db[%s]: %w", GetDBName(), err)
|
2022-07-01 19:48:14 +02:00
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
2023-11-02 22:05:43 +01:00
|
|
|
func copyFile(srcFile string, dstFile string, notFoundOk bool) error {
|
2023-03-12 22:42:18 +01:00
|
|
|
if srcFile == dstFile {
|
|
|
|
return fmt.Errorf("cannot copy %s to itself", srcFile)
|
|
|
|
}
|
|
|
|
srcFd, err := os.Open(srcFile)
|
2023-11-02 22:05:43 +01:00
|
|
|
if notFoundOk && err != nil && errors.Is(err, fs.ErrNotExist) {
|
|
|
|
return nil
|
|
|
|
}
|
2023-03-12 22:42:18 +01:00
|
|
|
if err != nil {
|
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
|
|
|
return fmt.Errorf("cannot open %s: %v", srcFile, err)
|
2023-03-12 22:42:18 +01:00
|
|
|
}
|
|
|
|
defer srcFd.Close()
|
|
|
|
dstFd, err := os.OpenFile(dstFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
|
|
|
if err != nil {
|
Implement a Sidebar for Tabs (#157)
* work on basic sidebar layout
* fix more golang warnings
* sidebar open/close
* add ability to set width of split
* sidebar add and remove, set width, etc.
* almost working sidebar implementation -- still needs height/width, input control, and bug with initial add, but getting there
* add isSidebarOpen() method
* fix resize jump -- must set width in error handler as well (before window is loaded)
* sidebar UI touchups and help
* more sidebar progress, render more like regular lines, just in the right column
* merge
* move migration to 26
* simplify sidebar types
* checkpoint
* proxy things through parent screen object for sidebar
* checkpoint, add/remove from sidebar
* work on add/remove icons for sidebar
* fix height calculation, remove close button
* bring back close button when no line is selected
* add sidebar flag to run command to run new command output in sidebar
* implement 'sidebar' kwarg in eval. this lets sidebar work for slashcommands as well that produce lines (codeedit, mdview, etc.)
* prettier
* minor fixes
* working on resizing. must exclude sidebar entries and send separate resize events based on size of sidebar (implement exclude / include for resize)
* fix sidebar terminal command resizing
* add sidebar header (toggles for half/partial width and close). add hotkey to open/close sidebar (Cmd-Ctrl-S). more robust calculation for sidebar width. add width validation. minimum sidebar width is 200px. other fixes, etc.
2023-12-18 08:46:53 +01:00
|
|
|
return fmt.Errorf("cannot open destination file %s: %v", dstFile, err)
|
2023-03-12 22:42:18 +01:00
|
|
|
}
|
|
|
|
_, err = io.Copy(dstFd, srcFd)
|
|
|
|
if err != nil {
|
|
|
|
dstFd.Close()
|
|
|
|
return fmt.Errorf("error copying file: %v", err)
|
|
|
|
}
|
|
|
|
return dstFd.Close()
|
|
|
|
}
|
|
|
|
|
2023-07-28 20:48:11 +02:00
|
|
|
func MigrateUpStep(m *migrate.Migrate, newVersion uint) error {
|
|
|
|
startTime := time.Now()
|
|
|
|
err := m.Migrate(newVersion)
|
2022-07-01 19:48:14 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-07-28 20:48:11 +02:00
|
|
|
if newVersion == CmdScreenSpecialMigration {
|
2023-07-31 02:16:43 +02:00
|
|
|
mErr := RunMigration13()
|
2023-07-28 20:48:11 +02:00
|
|
|
if mErr != nil {
|
2023-07-31 02:16:43 +02:00
|
|
|
return fmt.Errorf("migrating to v%d: %w", newVersion, mErr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if newVersion == CmdLineSpecialMigration {
|
|
|
|
mErr := RunMigration20()
|
|
|
|
if mErr != nil {
|
|
|
|
return fmt.Errorf("migrating to v%d: %w", newVersion, mErr)
|
2023-07-28 20:48:11 +02:00
|
|
|
}
|
2023-03-12 22:42:18 +01:00
|
|
|
}
|
2023-07-28 20:48:11 +02:00
|
|
|
log.Printf("[db] migration v%d, elapsed %v\n", newVersion, time.Since(startTime))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func MigrateUp(targetVersion uint) error {
|
|
|
|
m, err := MakeMigrate()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
curVersion, dirty, err := MigrateVersion(m)
|
2023-03-12 22:42:18 +01:00
|
|
|
if dirty {
|
|
|
|
return fmt.Errorf("cannot migrate up, database is dirty")
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot get current migration version: %v", err)
|
|
|
|
}
|
2023-07-28 20:48:11 +02:00
|
|
|
if curVersion >= targetVersion {
|
2023-03-12 22:42:18 +01:00
|
|
|
return nil
|
|
|
|
}
|
2023-07-28 20:48:11 +02:00
|
|
|
log.Printf("[db] migrating from %d to %d\n", curVersion, targetVersion)
|
2023-03-12 22:42:18 +01:00
|
|
|
log.Printf("[db] backing up database %s to %s\n", DBFileName, DBFileNameBackup)
|
2023-11-02 22:05:43 +01:00
|
|
|
os.Remove(GetDBBackupName()) // don't report error
|
|
|
|
os.Remove(GetDBWALBackupName()) // don't report error
|
|
|
|
err = copyFile(GetDBName(), GetDBBackupName(), false)
|
2023-03-12 22:42:18 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating database backup: %v", err)
|
|
|
|
}
|
2023-11-02 22:05:43 +01:00
|
|
|
err = copyFile(GetDBWALName(), GetDBWALBackupName(), true)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating database(wal) backup: %v", err)
|
|
|
|
}
|
2023-07-28 20:48:11 +02:00
|
|
|
for newVersion := curVersion + 1; newVersion <= targetVersion; newVersion++ {
|
|
|
|
err = MigrateUpStep(m, newVersion)
|
|
|
|
if err != nil {
|
2023-07-31 03:46:57 +02:00
|
|
|
return fmt.Errorf("during migration v%d: %w", newVersion, err)
|
2023-07-28 20:48:11 +02:00
|
|
|
}
|
2022-07-01 19:48:14 +02:00
|
|
|
}
|
2023-07-28 20:48:11 +02:00
|
|
|
log.Printf("[db] migration done, new version = %d\n", targetVersion)
|
2022-07-01 19:48:14 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-28 20:48:11 +02:00
|
|
|
// returns curVersion, dirty, error
|
|
|
|
func MigrateVersion(m *migrate.Migrate) (uint, bool, error) {
|
|
|
|
if m == nil {
|
|
|
|
var err error
|
|
|
|
m, err = MakeMigrate()
|
|
|
|
if err != nil {
|
|
|
|
return 0, false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
curVersion, dirty, err := m.Version()
|
|
|
|
if err == migrate.ErrNilVersion {
|
|
|
|
return 0, false, nil
|
2022-07-01 19:48:14 +02:00
|
|
|
}
|
2023-07-28 20:48:11 +02:00
|
|
|
return curVersion, dirty, err
|
2022-07-01 19:48:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func MigrateDown() error {
|
|
|
|
m, err := MakeMigrate()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = m.Down()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func MigrateGoto(n uint) error {
|
2023-07-28 20:48:11 +02:00
|
|
|
curVersion, _, _ := MigrateVersion(nil)
|
|
|
|
if curVersion == n {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if curVersion < n {
|
|
|
|
return MigrateUp(n)
|
|
|
|
}
|
2022-07-01 19:48:14 +02:00
|
|
|
m, err := MakeMigrate()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = m.Migrate(n)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TryMigrateUp() error {
|
2023-07-28 20:48:11 +02:00
|
|
|
curVersion, _, _ := MigrateVersion(nil)
|
|
|
|
log.Printf("[db] db version = %d\n", curVersion)
|
|
|
|
if curVersion >= MaxMigration {
|
|
|
|
return nil
|
2022-07-01 19:48:14 +02:00
|
|
|
}
|
2023-07-28 20:48:11 +02:00
|
|
|
err := MigrateUp(MaxMigration)
|
2022-07-01 19:48:14 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return MigratePrintVersion()
|
|
|
|
}
|
|
|
|
|
|
|
|
func MigratePrintVersion() error {
|
2023-07-28 20:48:11 +02:00
|
|
|
version, dirty, err := MigrateVersion(nil)
|
2022-07-01 19:48:14 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error getting db version: %v", err)
|
|
|
|
}
|
|
|
|
if dirty {
|
|
|
|
return fmt.Errorf("error db is dirty, version=%d", version)
|
|
|
|
}
|
2022-10-31 20:40:45 +01:00
|
|
|
log.Printf("[db] version=%d\n", version)
|
2022-07-01 19:48:14 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func MigrateCommandOpts(opts []string) error {
|
|
|
|
var err error
|
|
|
|
if opts[0] == "--migrate-up" {
|
2023-03-12 22:42:18 +01:00
|
|
|
fmt.Printf("migrate-up %v\n", GetDBName())
|
2023-01-23 08:10:18 +01:00
|
|
|
time.Sleep(3 * time.Second)
|
2023-07-28 20:48:11 +02:00
|
|
|
err = MigrateUp(MaxMigration)
|
2022-07-01 19:48:14 +02:00
|
|
|
} else if opts[0] == "--migrate-down" {
|
2023-03-12 22:42:18 +01:00
|
|
|
fmt.Printf("migrate-down %v\n", GetDBName())
|
2023-01-23 08:10:18 +01:00
|
|
|
time.Sleep(3 * time.Second)
|
2022-07-01 19:48:14 +02:00
|
|
|
err = MigrateDown()
|
|
|
|
} else if opts[0] == "--migrate-goto" {
|
|
|
|
n, err := strconv.Atoi(opts[1])
|
|
|
|
if err == nil {
|
2023-03-12 22:42:18 +01:00
|
|
|
fmt.Printf("migrate-goto %v => %d\n", GetDBName(), n)
|
2023-01-23 08:10:18 +01:00
|
|
|
time.Sleep(3 * time.Second)
|
2022-07-01 19:48:14 +02:00
|
|
|
err = MigrateGoto(uint(n))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err = fmt.Errorf("invalid migration command")
|
|
|
|
}
|
|
|
|
if err != nil && err.Error() == migrate.ErrNoChange.Error() {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return MigratePrintVersion()
|
|
|
|
}
|