From 5f4d0f9628adddd280254ad0748dc6c8055490b7 Mon Sep 17 00:00:00 2001 From: Felicitas Pojtinger Date: Sun, 19 Dec 2021 17:53:06 +0100 Subject: [PATCH] refactor: Make `httpsrv` a subcommand of `stbak`, add decryption support --- cmd/stbak/cmd/operation_archive.go | 2 +- cmd/stbak/cmd/operation_delete.go | 2 +- cmd/stbak/cmd/operation_restore.go | 2 +- cmd/stbak/cmd/operation_update.go | 2 +- cmd/stbak/cmd/serve_http.go | 153 +++++++++++++++++++++++++++++ cmd/stbak/cmd/serve_root.go | 18 ++++ cmd/sthttp/main.go | 108 -------------------- internal/persisters/metadata.go | 18 ++++ 8 files changed, 193 insertions(+), 112 deletions(-) create mode 100644 cmd/stbak/cmd/serve_http.go create mode 100644 cmd/stbak/cmd/serve_root.go delete mode 100644 cmd/sthttp/main.go diff --git a/cmd/stbak/cmd/operation_archive.go b/cmd/stbak/cmd/operation_archive.go index f377167..5aa8259 100644 --- a/cmd/stbak/cmd/operation_archive.go +++ b/cmd/stbak/cmd/operation_archive.go @@ -30,7 +30,7 @@ const ( var operationArchiveCmd = &cobra.Command{ Use: "archive", - Aliases: []string{"arc", "a", "c"}, + Aliases: []string{"arc", "a", "c", "add", "post"}, Short: "Archive a file or directory to tape or tar file", PreRunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { diff --git a/cmd/stbak/cmd/operation_delete.go b/cmd/stbak/cmd/operation_delete.go index c82f94e..1cad3dc 100644 --- a/cmd/stbak/cmd/operation_delete.go +++ b/cmd/stbak/cmd/operation_delete.go @@ -17,7 +17,7 @@ const ( var operationDeleteCmd = &cobra.Command{ Use: "delete", - Aliases: []string{"del", "d", "rm"}, + Aliases: []string{"del", "d", "rm", "remove"}, Short: "Delete a file or directory from tape or tar file", PreRunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { diff --git a/cmd/stbak/cmd/operation_restore.go b/cmd/stbak/cmd/operation_restore.go index 937f7ee..92e72cb 100644 --- a/cmd/stbak/cmd/operation_restore.go +++ b/cmd/stbak/cmd/operation_restore.go @@ -21,7 +21,7 @@ const ( var operationRestoreCmd = &cobra.Command{ Use: "restore", - Aliases: []string{"res", "r", "x"}, + Aliases: []string{"res", "r", "x", "get", "extract"}, Short: "Restore a file or directory from tape or tar file", PreRunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { diff --git a/cmd/stbak/cmd/operation_update.go b/cmd/stbak/cmd/operation_update.go index b0d181c..048455b 100644 --- a/cmd/stbak/cmd/operation_update.go +++ b/cmd/stbak/cmd/operation_update.go @@ -20,7 +20,7 @@ import ( var operationUpdateCmd = &cobra.Command{ Use: "update", - Aliases: []string{"upd", "u"}, + Aliases: []string{"upd", "u", "put"}, Short: "Update a file or directory's content and metadata on tape or tar file", PreRunE: func(cmd *cobra.Command, args []string) error { if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { diff --git a/cmd/stbak/cmd/serve_http.go b/cmd/stbak/cmd/serve_http.go new file mode 100644 index 0000000..30946ca --- /dev/null +++ b/cmd/stbak/cmd/serve_http.go @@ -0,0 +1,153 @@ +package cmd + +import ( + "context" + "log" + "net/http" + "time" + + sfs "github.com/pojntfx/stfs/internal/fs" + "github.com/pojntfx/stfs/internal/handlers" + "github.com/pojntfx/stfs/internal/keys" + "github.com/pojntfx/stfs/internal/logging" + "github.com/pojntfx/stfs/internal/persisters" + "github.com/pojntfx/stfs/pkg/config" + "github.com/pojntfx/stfs/pkg/operations" + "github.com/pojntfx/stfs/pkg/tape" + "github.com/spf13/afero" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const ( + laddrFlag = "laddr" + cacheFlag = "cache" +) + +var serveHTTPCmd = &cobra.Command{ + Use: "http", + Aliases: []string{"htt", "h"}, + Short: "Serve tape or tar file and the index over HTTP (read-only)", + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := viper.BindPFlags(cmd.PersistentFlags()); err != nil { + return err + } + + if err := keys.CheckKeyAccessible(viper.GetString(encryptionFlag), viper.GetString(identityFlag)); err != nil { + return err + } + + return keys.CheckKeyAccessible(viper.GetString(signatureFlag), viper.GetString(recipientFlag)) + }, + RunE: func(cmd *cobra.Command, args []string) error { + pubkey, err := keys.ReadKey(viper.GetString(signatureFlag), viper.GetString(recipientFlag)) + if err != nil { + return err + } + + recipient, err := keys.ParseSignerRecipient(viper.GetString(signatureFlag), pubkey) + if err != nil { + return err + } + + privkey, err := keys.ReadKey(viper.GetString(encryptionFlag), viper.GetString(identityFlag)) + if err != nil { + return err + } + + identity, err := keys.ParseIdentity(viper.GetString(encryptionFlag), privkey, viper.GetString(passwordFlag)) + if err != nil { + return err + } + + tm := tape.NewTapeManager( + viper.GetString(driveFlag), + viper.GetInt(recordSizeFlag), + false, + ) + + metadataPersister := persisters.NewMetadataPersister(viper.GetString(metadataFlag)) + if err := metadataPersister.Open(); err != nil { + return err + } + + root, err := metadataPersister.GetRootPath(context.Background()) + if err != nil { + return err + } + + logger := logging.NewLogger() + + ops := operations.NewOperations( + config.BackendConfig{ + GetWriter: tm.GetWriter, + CloseWriter: tm.Close, + + GetReader: tm.GetReader, + CloseReader: tm.Close, + + GetDrive: tm.GetDrive, + CloseDrive: tm.Close, + }, + config.MetadataConfig{ + Metadata: metadataPersister, + }, + + config.PipeConfig{ + Compression: viper.GetString(compressionFlag), + Encryption: viper.GetString(encryptionFlag), + Signature: viper.GetString(signatureFlag), + RecordSize: viper.GetInt(recordSizeFlag), + }, + config.CryptoConfig{ + Recipient: recipient, + Identity: identity, + Password: viper.GetString(passwordFlag), + }, + + logger.PrintHeaderEvent, + ) + + stfs := sfs.NewFileSystem( + ops, + + config.MetadataConfig{ + Metadata: metadataPersister, + }, + + logger.PrintHeader, + ) + + var fs afero.Fs + if viper.GetBool(cacheFlag) { + fs = afero.NewCacheOnReadFs(afero.NewBasePathFs(stfs, root), afero.NewMemMapFs(), time.Hour) + } else { + fs = afero.NewBasePathFs(stfs, root) + } + + log.Println("Listening on", viper.GetString(laddrFlag)) + + return http.ListenAndServe( + viper.GetString(laddrFlag), + handlers.PanicHandler( + http.FileServer( + afero.NewHttpFs(fs), + ), + ), + ) + + }, +} + +func init() { + serveHTTPCmd.PersistentFlags().IntP(recordSizeFlag, "z", 20, "Amount of 512-bit blocks per record") + serveHTTPCmd.PersistentFlags().StringP(identityFlag, "i", "", "Path to private key of recipient that has been encrypted for") + serveHTTPCmd.PersistentFlags().StringP(passwordFlag, "p", "", "Password for the private key") + serveHTTPCmd.PersistentFlags().StringP(recipientFlag, "r", "", "Path to the public key to verify with") + serveHTTPCmd.PersistentFlags().StringP(laddrFlag, "a", "localhost:1337", "Listen address") + serveHTTPCmd.PersistentFlags().BoolP(cacheFlag, "n", true, "Enable in-memory caching") + + viper.AutomaticEnv() + + serveCmd.AddCommand(serveHTTPCmd) +} diff --git a/cmd/stbak/cmd/serve_root.go b/cmd/stbak/cmd/serve_root.go new file mode 100644 index 0000000..52f89f3 --- /dev/null +++ b/cmd/stbak/cmd/serve_root.go @@ -0,0 +1,18 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var serveCmd = &cobra.Command{ + Use: "serve", + Aliases: []string{"ser", "s", "srv"}, + Short: "Serve tape or tar file and the index", +} + +func init() { + viper.AutomaticEnv() + + rootCmd.AddCommand(serveCmd) +} diff --git a/cmd/sthttp/main.go b/cmd/sthttp/main.go deleted file mode 100644 index 015367b..0000000 --- a/cmd/sthttp/main.go +++ /dev/null @@ -1,108 +0,0 @@ -package main - -import ( - "flag" - "log" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/pojntfx/stfs/internal/fs" - "github.com/pojntfx/stfs/internal/handlers" - "github.com/pojntfx/stfs/internal/logging" - "github.com/pojntfx/stfs/internal/persisters" - "github.com/pojntfx/stfs/pkg/config" - "github.com/pojntfx/stfs/pkg/operations" - "github.com/pojntfx/stfs/pkg/tape" - "github.com/spf13/afero" -) - -func main() { - home, err := os.UserHomeDir() - if err != nil { - panic(err) - } - - laddr := flag.String("laddr", "localhost:1337", "Listen address") - dir := flag.String("dir", "/", "Directory to use as the root directory") - drive := flag.String("drive", "/dev/nst0", "Tape or tar file to use") - metadata := flag.String("metadata", filepath.Join(home, ".local", "share", "stbak", "var", "lib", "stbak", "metadata.sqlite"), "Metadata database to use") - recordSize := flag.Int("recordSize", 20, "Amount of 512-bit blocks per record") - enableCache := flag.Bool("cache", true, "Enable in-memory caching") - - flag.Parse() - - tm := tape.NewTapeManager( - *drive, - *recordSize, - false, - ) - - metadataPersister := persisters.NewMetadataPersister(*metadata) - if err := metadataPersister.Open(); err != nil { - panic(err) - } - - logger := logging.NewLogger() - - ops := operations.NewOperations( - config.BackendConfig{ - GetWriter: tm.GetWriter, - CloseWriter: tm.Close, - - GetReader: tm.GetReader, - CloseReader: tm.Close, - - GetDrive: tm.GetDrive, - CloseDrive: tm.Close, - }, - config.MetadataConfig{ - Metadata: metadataPersister, - }, - - config.PipeConfig{ - Compression: config.NoneKey, - Encryption: config.NoneKey, - Signature: config.NoneKey, - RecordSize: *recordSize, - }, - config.CryptoConfig{ - Recipient: []byte{}, - Identity: []byte{}, - Password: "", - }, - - logger.PrintHeaderEvent, - ) - - stfs := fs.NewFileSystem( - ops, - - config.MetadataConfig{ - Metadata: metadataPersister, - }, - - logger.PrintHeader, - ) - - var fs afero.Fs - if *enableCache { - fs = afero.NewCacheOnReadFs(afero.NewBasePathFs(stfs, *dir), afero.NewMemMapFs(), time.Hour) - } else { - fs = afero.NewBasePathFs(stfs, *dir) - } - - log.Println("Listening on", *laddr) - - panic( - http.ListenAndServe( - *laddr, - handlers.PanicHandler( - http.FileServer( - afero.NewHttpFs(fs), - ), - ), - ), - ) -} diff --git a/internal/persisters/metadata.go b/internal/persisters/metadata.go index 6ee97eb..f1bd957 100644 --- a/internal/persisters/metadata.go +++ b/internal/persisters/metadata.go @@ -187,6 +187,24 @@ func (p *MetadataPersister) GetHeaderChildren(ctx context.Context, name string) return outhdrs, nil } +func (p *MetadataPersister) GetRootPath(ctx context.Context) (string, error) { + root := models.Header{} + + if err := queries.Raw( + fmt.Sprintf( + `select min(length(%v) - length(replace(%v, "/", ""))) as depth, name from %v where %v != 1`, + models.HeaderColumns.Name, + models.HeaderColumns.Name, + models.TableNames.Headers, + models.HeaderColumns.Deleted, + ), + ).Bind(ctx, p.db, &root); err != nil { + return "", err + } + + return root.Name, nil +} + func (p *MetadataPersister) GetHeaderDirectChildren(ctx context.Context, name string, limit int) (models.HeaderSlice, error) { prefix := strings.TrimSuffix(name, "/") + "/" rootDepth := 0