Skip to content

Commit 6f3b650

Browse files
committed
feat: initial commit
0 parents  commit 6f3b650

File tree

8 files changed

+509
-0
lines changed

8 files changed

+509
-0
lines changed

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Nuno David
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

go.mod

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/ndavd/agevault
2+
3+
go 1.23.0
4+
5+
require filippo.io/age v1.2.0
6+
7+
require (
8+
golang.org/x/crypto v0.24.0 // indirect
9+
golang.org/x/sys v0.23.0 // indirect
10+
golang.org/x/term v0.23.0 // indirect
11+
)

go.sum

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
2+
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
3+
filippo.io/age v1.2.0 h1:vRDp7pUMaAJzXNIWJVAZnEf/Dyi4Vu4wI8S1LBzufhE=
4+
filippo.io/age v1.2.0/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
5+
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
6+
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
7+
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
8+
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
9+
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
10+
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
11+
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
12+
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=

internal/archive/archive.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package archive
2+
3+
import (
4+
"archive/zip"
5+
"bytes"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"io/fs"
10+
"os"
11+
"path/filepath"
12+
"strings"
13+
14+
"github.com/ndavd/agevault/internal/utils"
15+
)
16+
17+
func ZipDirectory(inputSource string, destinationWriter io.Writer) error {
18+
exists, isDir := utils.Exists(inputSource)
19+
if !exists || !isDir {
20+
return errors.New("source does not exist or is not a directory")
21+
}
22+
writer := zip.NewWriter(destinationWriter)
23+
defer writer.Close()
24+
return filepath.Walk(inputSource, func(path string, info fs.FileInfo, err error) error {
25+
if err != nil {
26+
return err
27+
}
28+
if info.IsDir() {
29+
path = fmt.Sprintf("%s%c", path, os.PathSeparator)
30+
_, err = writer.Create(path)
31+
return err
32+
}
33+
file, err := os.Open(path)
34+
if err != nil {
35+
return err
36+
}
37+
defer file.Close()
38+
f, err := writer.Create(path)
39+
if err != nil {
40+
return err
41+
}
42+
_, err = io.Copy(f, file)
43+
return err
44+
})
45+
}
46+
47+
func UnZip(inputReader bytes.Reader, outputDestination string) error {
48+
reader, err := zip.NewReader(&inputReader, inputReader.Size())
49+
if err != nil {
50+
return err
51+
}
52+
destination, err := filepath.Abs(outputDestination)
53+
if err != nil {
54+
return err
55+
}
56+
for _, f := range reader.File {
57+
err := unzipFile(f, destination)
58+
if err != nil {
59+
return err
60+
}
61+
}
62+
return err
63+
}
64+
65+
func unzipFile(f *zip.File, destination string) error {
66+
path := filepath.Join(destination, f.Name)
67+
if !strings.HasPrefix(path, filepath.Clean(destination)+string(os.PathSeparator)) {
68+
return fmt.Errorf("invalid file path: %s", path)
69+
}
70+
if f.FileInfo().IsDir() {
71+
if err := os.MkdirAll(path, os.ModePerm); err != nil {
72+
return err
73+
}
74+
return nil
75+
}
76+
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
77+
return err
78+
}
79+
destinationFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
80+
if err != nil {
81+
return err
82+
}
83+
defer destinationFile.Close()
84+
zippedFile, err := f.Open()
85+
if err != nil {
86+
return err
87+
}
88+
defer zippedFile.Close()
89+
_, err = io.Copy(destinationFile, zippedFile)
90+
return err
91+
}

internal/crypt/crypt.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package crypt
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
"syscall"
9+
10+
"filippo.io/age"
11+
"golang.org/x/term"
12+
)
13+
14+
func EncryptToFile(destinationFilename string, data []byte, recipient age.Recipient) error {
15+
file, err := os.Create(destinationFilename)
16+
if err != nil {
17+
return err
18+
}
19+
defer file.Close()
20+
writeCloser, err := age.Encrypt(file, recipient)
21+
if err != nil {
22+
return err
23+
}
24+
if _, err = writeCloser.Write(data); err != nil {
25+
return err
26+
}
27+
return writeCloser.Close()
28+
}
29+
30+
func DecryptToWriter(destinationWriter io.Writer, encryptedDataReader io.Reader, identity age.Identity) error {
31+
reader, err := age.Decrypt(encryptedDataReader, identity)
32+
if err != nil {
33+
return err
34+
}
35+
_, err = io.Copy(destinationWriter, reader)
36+
return err
37+
}
38+
39+
func ReadSecret(label string, confirm bool) (string, error) {
40+
prefix := ""
41+
if confirm {
42+
prefix = "create "
43+
}
44+
fmt.Printf("%s%s: ", prefix, label)
45+
secretBytes, err := term.ReadPassword(int(syscall.Stdin))
46+
fmt.Println()
47+
if err != nil {
48+
return "", err
49+
}
50+
if len(secretBytes) == 0 {
51+
return "", errors.New("passphrase cannot be empty")
52+
}
53+
if confirm {
54+
fmt.Printf("confirm %s: ", label)
55+
confirmSecretBytes, err := term.ReadPassword(int(syscall.Stdin))
56+
fmt.Println()
57+
if err != nil {
58+
return "", err
59+
}
60+
if string(secretBytes) != string(confirmSecretBytes) {
61+
return "", fmt.Errorf("%s not matching", label)
62+
}
63+
}
64+
return string(secretBytes), nil
65+
}

internal/shredder/shredder.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package shredder
2+
3+
import (
4+
"crypto/rand"
5+
"errors"
6+
"io/fs"
7+
"os"
8+
"path/filepath"
9+
10+
"github.com/ndavd/agevault/internal/utils"
11+
)
12+
13+
func ShredFile(path string, iterations int) error {
14+
file, err := os.OpenFile(path, os.O_WRONLY, 0644)
15+
if err != nil {
16+
return err
17+
}
18+
info, err := file.Stat()
19+
if err != nil {
20+
return err
21+
}
22+
random := make([]byte, info.Size())
23+
for i := 0; i < iterations; i++ {
24+
if _, err = rand.Read(random); err != nil {
25+
return err
26+
}
27+
if _, err = file.WriteAt(random, 0); err != nil {
28+
return err
29+
}
30+
if err = file.Sync(); err != nil {
31+
return err
32+
}
33+
}
34+
if err = file.Close(); err != nil {
35+
return err
36+
}
37+
return os.Remove(path)
38+
}
39+
40+
func ShredDir(path string, iterations int) error {
41+
exists, isDir := utils.Exists(path)
42+
if !exists || !isDir {
43+
return errors.New("is not a directory or does not exist")
44+
}
45+
err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
46+
if err != nil {
47+
return err
48+
}
49+
if !d.IsDir() && d.Type().IsRegular() {
50+
ShredFile(path, iterations)
51+
}
52+
return nil
53+
})
54+
if err != nil {
55+
return err
56+
}
57+
return os.RemoveAll(path)
58+
}

internal/utils/utils.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package utils
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"os"
7+
"os/exec"
8+
)
9+
10+
func RunCmd(name string, args ...string) error {
11+
cmd := exec.Command(name, args...)
12+
var stderr bytes.Buffer
13+
cmd.Stderr = &stderr
14+
err := cmd.Run()
15+
if err != nil {
16+
return errors.New(stderr.String())
17+
}
18+
return nil
19+
}
20+
21+
func Exists(path string) (exists bool, isDir bool) {
22+
info, err := os.Stat(path)
23+
exists = true
24+
if err != nil {
25+
exists = false
26+
return
27+
}
28+
isDir = info.IsDir()
29+
return
30+
}
31+
32+
type MatcherFunc func(filename string) bool
33+
34+
func FileMatchInCwd(match MatcherFunc) (string, error) {
35+
currentDir, err := os.Getwd()
36+
if err != nil {
37+
return "", err
38+
}
39+
dir, err := os.Open(currentDir)
40+
if err != nil {
41+
return "", err
42+
}
43+
defer dir.Close()
44+
files, err := dir.ReadDir(-1)
45+
if err != nil {
46+
return "", err
47+
}
48+
for _, file := range files {
49+
if !file.IsDir() && match(file.Name()) {
50+
return file.Name(), nil
51+
}
52+
}
53+
return "", nil
54+
}

0 commit comments

Comments
 (0)