gopherbook/scripts-go/cbt.go
riomoo 9752725855
Squashed commit of the following:
commit c8126262de851ef2b9bfed5ca3b07b9dc927f203
Author: riomoo <alister@kamikishi.net>
Date:   Tue Jan 6 01:58:02 2026 -0500

    feat: planned additions

    - Watch folder
    - CBT support with AES-256-CFB encryption (script provided to make it
      easier)
2026-01-06 02:02:26 -05:00

231 lines
4.2 KiB
Go

package main
import (
"archive/tar"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
)
func main() {
inputDir := flag.String("input", "", "Input directory containing comic files")
outputFile := flag.String("output", "", "Output .cbt file")
password := flag.String("password", "", "Password for encryption (leave empty for no encryption)")
flag.Parse()
if *inputDir == "" || *outputFile == "" {
fmt.Println("Usage: cbt-creator -input <directory> -output <file.cbt> [-password <password>]")
os.Exit(1)
}
// Ensure output has .cbt extension
if !strings.HasSuffix(strings.ToLower(*outputFile), ".cbt") {
*outputFile += ".cbt"
}
var err error
if *password != "" {
err = createEncryptedCBT(*inputDir, *outputFile, *password)
fmt.Printf("Created encrypted CBT: %s\n", *outputFile)
} else {
err = createUnencryptedCBT(*inputDir, *outputFile)
fmt.Printf("Created unencrypted CBT: %s\n", *outputFile)
}
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
}
func createUnencryptedCBT(inputDir, outputPath string) error {
outFile, err := os.Create(outputPath)
if err != nil {
return err
}
defer outFile.Close()
tw := tar.NewWriter(outFile)
defer tw.Close()
// Collect all files
var files []string
err = filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
})
if err != nil {
return err
}
// Add files to tar
for _, file := range files {
relPath, err := filepath.Rel(inputDir, file)
if err != nil {
return err
}
// Read file
data, err := os.ReadFile(file)
if err != nil {
return err
}
// Write tar header
hdr := &tar.Header{
Name: relPath,
Mode: 0600,
Size: int64(len(data)),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
// Write file data
if _, err := tw.Write(data); err != nil {
return err
}
fmt.Printf("Added: %s\n", relPath)
}
return nil
}
func createEncryptedCBT(inputDir, outputPath, password string) error {
// Create temporary unencrypted tar
tmpTar := outputPath + ".tmp"
tmpFile, err := os.Create(tmpTar)
if err != nil {
return err
}
tw := tar.NewWriter(tmpFile)
// Collect all files
var files []string
err = filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, path)
}
return nil
})
if err != nil {
tmpFile.Close()
os.Remove(tmpTar)
return err
}
// Add files to tar
for _, file := range files {
relPath, err := filepath.Rel(inputDir, file)
if err != nil {
tw.Close()
tmpFile.Close()
os.Remove(tmpTar)
return err
}
// Read file
data, err := os.ReadFile(file)
if err != nil {
tw.Close()
tmpFile.Close()
os.Remove(tmpTar)
return err
}
// Write tar header
hdr := &tar.Header{
Name: relPath,
Mode: 0600,
Size: int64(len(data)),
}
if err := tw.WriteHeader(hdr); err != nil {
tw.Close()
tmpFile.Close()
os.Remove(tmpTar)
return err
}
// Write file data
if _, err := tw.Write(data); err != nil {
tw.Close()
tmpFile.Close()
os.Remove(tmpTar)
return err
}
fmt.Printf("Added: %s\n", relPath)
}
tw.Close()
tmpFile.Close()
// Read the tar file
tarData, err := os.ReadFile(tmpTar)
if err != nil {
os.Remove(tmpTar)
return err
}
fmt.Println("Encrypting...")
// Encrypt
key := deriveKey(password)
encrypted, err := encryptAES(tarData, key)
if err != nil {
os.Remove(tmpTar)
return err
}
// Write encrypted file
err = os.WriteFile(outputPath, encrypted, 0644)
os.Remove(tmpTar)
return err
}
func deriveKey(password string) []byte {
hash := sha256.Sum256([]byte(password))
return hash[:]
}
func encryptAES(plaintext []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Generate IV
iv := make([]byte, aes.BlockSize)
if _, err := rand.Read(iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
ciphertext := make([]byte, len(plaintext))
stream.XORKeyStream(ciphertext, plaintext)
// Prepend IV to ciphertext
return append(iv, ciphertext...), nil
}