Рыба проекта. Минимальная функциональность

This commit is contained in:
2026-08-03 22:22:24 +03:00
commit 8c8631ac9c
80 changed files with 10618 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
package main
import (
"context"
"crypto/rand"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"time"
"github.com/google/uuid"
"github.com/spf13/cobra"
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/config"
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/composite"
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/keymanager"
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/mlkem768"
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/crypto/x25519"
pgdumpadapter "git.tswf.io/infra/go-synapse-backupper/pkg/adapters/pgdump"
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/pipeline"
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/retention"
"git.tswf.io/infra/go-synapse-backupper/pkg/adapters/storage/local"
"git.tswf.io/infra/go-synapse-backupper/pkg/domain"
"git.tswf.io/infra/go-synapse-backupper/pkg/domain/crypto"
"git.tswf.io/infra/go-synapse-backupper/pkg/domain/pgdump"
)
var (
newKeyManager = keymanager.NewKeyManager
newLocalSink = local.NewLocalSink
newRunner = defaultNewRunner
outputWriter io.Writer = os.Stderr
)
type pipelineRunner interface {
Run(
ctx context.Context,
pgDumpOpts pgdump.Options,
recipients []crypto.RecipientPub,
sink domain.Sink,
rand io.Reader,
) error
}
func defaultNewRunner(options ...pipeline.Option) pipelineRunner {
return pipeline.NewRunner(options...)
}
func init() {
config.RegisterFlags(backupCmd)
}
var backupCmd = &cobra.Command{
Use: "backup",
Short: "Run a one-off backup",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
if ctx == nil {
ctx = context.Background()
}
startTime := time.Now().UTC()
runID, err := uuid.NewRandom()
if err != nil {
return fmt.Errorf("generate run-id: %w", err)
}
logger := slog.New(slog.NewTextHandler(outputWriter, nil))
logger.Info(
"backup started",
slog.String("run_id", runID.String()),
slog.Time("start_time", startTime),
)
cfg, err := config.Load(cmd)
if err != nil {
return fmt.Errorf("load config: %w", err)
}
keyManager := newKeyManager(crypto.NewRegistry())
pqPub, err := keyManager.LoadPub(cfg.PQPublicKeyPath, cfg.PQScheme)
if err != nil {
return fmt.Errorf("load PQ public key: %w", err)
}
classicalPub, err := keyManager.LoadPub(cfg.ClassicalPublicKeyPath, cfg.ClassicalScheme)
if err != nil {
return fmt.Errorf("load classical public key: %w", err)
}
recipients := []crypto.RecipientPub{pqPub, classicalPub}
pgDumpOpts := pgdump.Options{
Host: cfg.PG.Host,
Port: cfg.PG.Port,
Database: cfg.PG.Database,
User: cfg.PG.User,
Password: cfg.PG.Password,
Key: fmt.Sprintf("synapse-%s.dump.pqenc", startTime.Format("20060102-150405")),
ExcludeTables: cfg.PG.ExcludeTables,
}
finalPath := filepath.Join(cfg.Backup.Dir, pgDumpOpts.Key)
logger.Info(
"backup destination",
slog.String("backup_dir", cfg.Backup.Dir),
slog.String("output_path", finalPath),
)
sink := newLocalSink(cfg.Backup.Dir)
registry := crypto.NewRegistry()
_ = registry.Register(0x0006, func() crypto.KEM { return mlkem768.New() })
_ = registry.Register(0x0007, func() crypto.KEM { return x25519.New() })
runner := newRunner(
pipeline.WithDumper(pgdumpadapter.New()),
pipeline.WithEncryptor(composite.NewEncryptor(registry)),
)
err = runner.Run(ctx, pgDumpOpts, recipients, sink, rand.Reader)
endTime := time.Now().UTC()
var byteCount int64
if info, statErr := os.Stat(finalPath); statErr == nil {
byteCount = info.Size()
}
if err != nil {
logger.Error(
"backup failed",
slog.String("run_id", runID.String()),
slog.String("output_path", finalPath),
slog.Time("start_time", startTime),
slog.Time("end_time", endTime),
slog.Int64("byte_count", byteCount),
slog.String("error", err.Error()),
)
return err
}
logger.Info(
"backup completed",
slog.String("run_id", runID.String()),
slog.String("output_path", finalPath),
slog.Time("start_time", startTime),
slog.Time("end_time", endTime),
slog.Int64("byte_count", byteCount),
)
if _, pruneErr := retention.PruneByAge(
ctx,
cfg.Backup.Dir,
cfg.Backup.RetentionDays,
time.Now(),
); pruneErr != nil {
logger.Error(
"retention pruning failed",
slog.String("error", pruneErr.Error()),
)
return pruneErr
}
return nil
},
}