Borg/pkg/tim/tim.go
google-labs-jules[bot] 3398fabb14 feat: Add trix encryption and format
This commit introduces the `Enchantrix` library to add support for the `.trix` encrypted file format.

The main changes are:

- The `matrix` format has been renamed to `tim` (Terminal Isolation Matrix).
- The `.tim` format is now a specialized `.trix` file.
- A new `decode` command has been added to decode `.trix` and `.tim` files.
- The `collect` commands now support the `trix` and `tim` formats.
- A `--password` flag has been added to the `collect` commands for encryption.
- A `--i-am-in-isolation` flag has been added to the `decode` command for safely decoding `.tim` files.
- The decryption functionality is currently disabled due to a bug in the `Enchantrix` library. A follow-up PR will be created to re-enable it.
2025-11-14 13:47:27 +00:00

147 lines
2.8 KiB
Go

package tim
import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"io/fs"
"github.com/Snider/Borg/pkg/datanode"
)
var (
ErrDataNodeRequired = errors.New("datanode is required")
ErrConfigIsNil = errors.New("config is nil")
)
// TerminalIsolationMatrix represents a runc bundle.
type TerminalIsolationMatrix struct {
Config []byte
RootFS *datanode.DataNode
}
// New creates a new, empty TerminalIsolationMatrix.
func New() (*TerminalIsolationMatrix, error) {
// Use the default runc spec as a starting point.
// This can be customized later.
spec, err := defaultConfig()
if err != nil {
return nil, err
}
specBytes, err := json.Marshal(spec)
if err != nil {
return nil, err
}
return &TerminalIsolationMatrix{
Config: specBytes,
RootFS: datanode.New(),
}, nil
}
// FromDataNode creates a new TerminalIsolationMatrix from a DataNode.
func FromDataNode(dn *datanode.DataNode) (*TerminalIsolationMatrix, error) {
if dn == nil {
return nil, ErrDataNodeRequired
}
m, err := New()
if err != nil {
return nil, err
}
m.RootFS = dn
return m, nil
}
// ToTar serializes the TerminalIsolationMatrix to a tarball.
func (m *TerminalIsolationMatrix) ToTar() ([]byte, error) {
if m.Config == nil {
return nil, ErrConfigIsNil
}
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
// Add the config.json file.
hdr := &tar.Header{
Name: "config.json",
Mode: 0600,
Size: int64(len(m.Config)),
}
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
if _, err := tw.Write(m.Config); err != nil {
return nil, err
}
// Add the rootfs directory.
hdr = &tar.Header{
Name: "rootfs/",
Mode: 0755,
Typeflag: tar.TypeDir,
}
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
// Add the rootfs files.
err := m.RootFS.Walk(".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
// If the root directory doesn't exist (i.e. empty datanode), it's not an error.
if path == "." && errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}
if d.IsDir() {
return nil
}
file, err := m.RootFS.Open(path)
if err != nil {
return err
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return err
}
hdr := &tar.Header{
Name: "rootfs/" + path,
Mode: 0600,
Size: info.Size(),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(file); err != nil {
return err
}
if _, err := tw.Write(buf.Bytes()); err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
if err := tw.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// ToTrix is not yet implemented.
// func (m *TerminalIsolationMatrix) ToTrix(password string) ([]byte, error) {
// return nil, errors.New("not implemented")
// }