mirror of
https://github.com/syncthing/syncthing.git
synced 2025-12-23 22:18:14 -05:00
fix(cmd): provide temporary GUI/API server during database migration (#10279)
This adds a temporary GUI/API server during the database migration. It responds with 200 OK and some log output for every request. This serves two purposes: - Primarily, for deployments that use the API as a health check, it gives them something positive to accept during the migration, reducing the risk of the migration getting killed halfway through and restarted, thus never completing. - Secondarily, it gives humans who happen to try to load the GUI some sort of indication of what's going on. Obviously, anything that expects a well-formed API response at this stage is still going to fail. They were already failing though, as we didn't even listen at this point before.
This commit is contained in:
@@ -479,7 +479,7 @@ func (c *serveCmd) syncthingMain() {
|
||||
})
|
||||
}
|
||||
|
||||
if err := syncthing.TryMigrateDatabase(c.DBDeleteRetentionInterval); err != nil {
|
||||
if err := syncthing.TryMigrateDatabase(ctx, c.DBDeleteRetentionInterval, cfgWrapper.GUI().Address()); err != nil {
|
||||
slog.Error("Failed to migrate old-style database", slogutil.Error(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
package syncthing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -156,7 +158,7 @@ func OpenDatabase(path string, deleteRetention time.Duration) (db.DB, error) {
|
||||
}
|
||||
|
||||
// Attempts migration of the old (LevelDB-based) database type to the new (SQLite-based) type
|
||||
func TryMigrateDatabase(deleteRetention time.Duration) error {
|
||||
func TryMigrateDatabase(ctx context.Context, deleteRetention time.Duration, apiAddr string) error {
|
||||
oldDBDir := locations.Get(locations.LegacyDatabase)
|
||||
if _, err := os.Lstat(oldDBDir); err != nil {
|
||||
// No old database
|
||||
@@ -170,6 +172,12 @@ func TryMigrateDatabase(deleteRetention time.Duration) error {
|
||||
}
|
||||
defer be.Close()
|
||||
|
||||
// Start a temporary API server during the migration
|
||||
api := migratingAPI{addr: apiAddr}
|
||||
apiCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
go api.Serve(apiCtx)
|
||||
|
||||
sdb, err := sqlite.OpenForMigration(locations.Get(locations.Database))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -284,3 +292,27 @@ func TryMigrateDatabase(deleteRetention time.Duration) error {
|
||||
slog.Info("Migration complete", "files", totFiles, "blocks", totBlocks/1000, "duration", time.Since(t0).Truncate(time.Second))
|
||||
return nil
|
||||
}
|
||||
|
||||
type migratingAPI struct {
|
||||
addr string
|
||||
}
|
||||
|
||||
func (m migratingAPI) Serve(ctx context.Context) error {
|
||||
srv := &http.Server{
|
||||
Addr: m.addr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte("*** Database migration in progress ***\n\n"))
|
||||
for _, line := range slogutil.GlobalRecorder.Since(time.Time{}) {
|
||||
line.WriteTo(w)
|
||||
}
|
||||
}),
|
||||
}
|
||||
go func() {
|
||||
slog.InfoContext(ctx, "Starting temporary GUI/API during migration", slogutil.Address(m.addr))
|
||||
err := srv.ListenAndServe()
|
||||
slog.InfoContext(ctx, "Temporary GUI/API closed", slogutil.Address(m.addr), slogutil.Error(err))
|
||||
}()
|
||||
<-ctx.Done()
|
||||
return srv.Close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user