Add encrypted S3 remote move
ober
52bb91bd24c100191bf971c174f0f1749809894c
--- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ jdrive s3 cp -r ./backup-dir /backups jdrive s3 cp /backups/notes.txt ./notes-restored.txt jdrive s3 cp -r /backups ./restored-backups jdrive s3 cp -r /backups /archive +jdrive s3 mv /archive/backups/notes.txt /archive/notes.txt jdrive s3 sync ./backup-dir /backups --delete jdrive s3 check ./backup-dir /backups jdrive s3 ls /backups --- a/docs/jerboa-drive-plan.md +++ b/docs/jerboa-drive-plan.md @@ -96,6 +96,7 @@ Completed so far: - `jdrive s3 unlock-test` - `jdrive s3 mkdir` - `jdrive s3 cp [-r]` + - `jdrive s3 mv [-r]` - `jdrive s3 sync` - `jdrive s3 check` - `jdrive s3 ls` @@ -113,6 +114,7 @@ Completed so far: - recursive local-to-remote upload - recursive remote-to-local copy - recursive remote-to-remote copy with re-encryption for destination paths + - remote move/rename through encrypted copy-then-remove - local-to-remote sync - local-vs-remote check - simple `*` include/exclude filters --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -131,6 +131,7 @@ jdrive s3 cp -r ./backup-dir /backups jdrive s3 cp /backups/notes.txt ./notes-restored.txt jdrive s3 cp -r /backups ./restored-backups jdrive s3 cp -r /backups /archive +jdrive s3 mv /archive/backups/notes.txt /archive/notes.txt jdrive s3 sync ./backup-dir /backups --delete jdrive s3 check ./backup-dir /backups jdrive s3 ls /backups --- a/protonstorage/cli.ss +++ b/protonstorage/cli.ss @@ -47,6 +47,7 @@ " s3 status Show S3 backend configuration\n" " s3 init --bucket B Create encrypted S3 drive profile\n" " s3 cp [-r] SRC DST Copy to/from encrypted S3 drive\n" + " s3 mv [-r] REMOTE DST Move encrypted S3 file/tree\n" " s3 ls [REMOTE] List encrypted S3 drive manifest\n" " doctor Show implementation status\n" "\n" @@ -80,6 +81,7 @@ " s3 profile-status Show local encrypted S3 profile state\n" " s3 unlock-test Verify S3 profile password/YubiKey unlock\n" " s3 cp [-r] SRC DST Copy local/remote file/tree through encrypted S3\n" + " s3 mv [-r] REMOTE DST Move encrypted S3 file/tree\n" " s3 sync LOCAL REMOTE Sync local tree to encrypted S3\n" " s3 check LOCAL REMOTE Compare local tree to encrypted S3\n" " s3 ls [REMOTE] List encrypted S3 paths\n" @@ -1333,6 +1335,28 @@ opts "--chunk-size" jdrive-s3-default-chunk-size))))] + [(or (string=? action "mv") (string=? action "move")) + (unless (= (length pos) 2) + (die 2 "usage: jdrive s3 mv [-r] REMOTE_PATH REMOTE_PATH")) + (let* ([password (s3-vault-password-from-opts opts)] + [material (s3-yubikey-material-for-profile opts)] + [source (car pos)] + [destination (cadr pos)]) + (unless (and (remote-path? source) (remote-path? destination)) + (die 2 "s3 mv requires remote paths beginning with /")) + (print-json + (jdrive-s3-move! + (profile-option opts) + (state-root-option opts) + password + material + source + destination + (recursive-upload? opts) + (option-number + opts + "--chunk-size" + jdrive-s3-default-chunk-size))))] [(string=? action "cat") (unless (= (length pos) 1) (die 2 "usage: jdrive s3 cat REMOTE_PATH")) --- a/protonstorage/s3/drive.ss +++ b/protonstorage/s3/drive.ss @@ -26,6 +26,7 @@ jdrive-s3-check-local-path! jdrive-s3-sync-local-to-remote! jdrive-s3-copy! + jdrive-s3-move! jdrive-s3-cat-file-bytes jdrive-s3-cat-file-range-bytes jdrive-s3-get-file! @@ -1786,6 +1787,49 @@ source destination)])) + (define (jdrive-s3-move! + profile state-root vault-password yubikey-material source destination + recursive? chunk-size) + (let ([source-path (remote-path-normalize source)] + [destination-path (remote-path-normalize destination)]) + (when (string=? source-path destination-path) + (error 'jdrive-s3-move! "source and destination are the same" source-path)) + (when (under-prefix? source-path destination-path) + (error 'jdrive-s3-move! + "refusing to move a remote tree into itself" + source-path + destination-path)) + (let ([copy-summary + (jdrive-s3-copy! + profile + state-root + vault-password + yubikey-material + source-path + destination-path + #t + #t + recursive? + chunk-size)]) + (let ([remove-summary + (jdrive-s3-remove! + profile + state-root + vault-password + yubikey-material + source-path + recursive?)]) + (json-object + "Backend" "s3" + "Profile" (jdrive-s3-profile-name profile) + "Source" source-path + "Destination" destination-path + "Recursive" (if recursive? #t #f) + "MovedFiles" (hashtable-ref copy-summary "CopiedFiles" 0) + "CopiedBytes" (hashtable-ref copy-summary "CopiedBytes" 0) + "CopiedChunks" (hashtable-ref copy-summary "CopiedChunks" 0) + "RemovedFiles" (hashtable-ref remove-summary "RemovedFiles" 0)))))) + (define (jdrive-s3-cat-file-bytes profile state-root vault-password yubikey-material remote-path) (jdrive-s3-cat-file-range-bytes profile --- a/test/integration-s3.ss +++ b/test/integration-s3.ss @@ -237,6 +237,21 @@ 0 -1))) +(jdrive-s3-move! + profile state-root vault-password #vu8() + "/copy/a.txt" + "/moved/a.txt" + #f + chunk-size) +(check "remote file move decrypts" + (bytevector=? + (string->utf8 "alpha plaintext") + (jdrive-s3-cat-file-range-bytes + profile state-root vault-password #vu8() + "/moved/a.txt" + 0 + -1))) + (jdrive-s3-remove! profile state-root vault-password #vu8() "/" --- a/test/test-all.ss +++ b/test/test-all.ss @@ -120,6 +120,7 @@ (test-string-contains? usage-string "s3 target --bucket B --key K") (test-string-contains? usage-string "s3 init --bucket B") (test-string-contains? usage-string "s3 cp [-r] SRC DST") + (test-string-contains? usage-string "s3 mv [-r] REMOTE DST") (test-string-contains? usage-string "s3 sync LOCAL REMOTE") (test-string-contains? usage-string "s3 check LOCAL REMOTE") (test-string-contains? usage-string "s3 mkdir REMOTE")