eternity/common/main.go
2024-10-02 18:07:12 +01:00

441 lines
14 KiB
Go

package common
import (
"bufio"
"errors"
"fmt"
"os"
"strings"
"git.oreonproject.org/oreonproject/eternity/lib"
"crypto/ed25519"
"crypto/x509"
"encoding/pem"
"path/filepath"
"github.com/fatih/color"
)
var DefaultLogger = lib.Logger{
LogFunc: func(log lib.Log) string {
var severityPretty string
switch log.Level {
case "INFO":
severityPretty = color.GreenString("[INFO]")
case "WARN":
severityPretty = color.YellowString("[WARN]")
case "ERROR":
severityPretty = color.HiYellowString("[ERROR]")
case "CRITICAL":
severityPretty = color.HiRedString("[CRITICAL]")
case "FATAL":
fmt.Println(color.RedString("[FATAL]"), log.Content)
os.Exit(1)
default:
severityPretty = color.WhiteString("[" + strings.ToUpper(log.Level) + "]")
}
fmt.Println(severityPretty, log.Content)
if log.Prompt {
fmt.Print(": ")
reader := bufio.NewReader(os.Stdin)
userInput, err := reader.ReadString('\n')
if err != nil {
fmt.Println(color.RedString("[FATAL]"), "Failed to read user input:", err)
os.Exit(1)
} else {
return userInput[:len(userInput)-1]
}
}
return ""
},
PromptSupported: true,
StdoutSupported: true,
Stdout: os.Stdout,
}
// EternityJsonHandleError handles errors related to the parsing of eternity.json
func EternityJsonHandleError(err error, vagueError error, logger *lib.Logger) {
switch {
case errors.Is(vagueError, lib.ErrEternityJsonOpenError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to open eternity.json: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrEternityJsonReadError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to reading eternity.json: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrEternityJsonParseError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to parse eternity.json: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrEternityJsonMapError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to map eternity.json: " + err.Error(),
Prompt: false,
})
}
}
// BuildEPKRemoveTemporaryDirectory removes the temporary directory created by BuildEPK
func BuildEPKRemoveTemporaryDirectory(tempDir string, logger *lib.Logger) {
err := os.RemoveAll(tempDir)
if err != nil {
logger.LogFunc(lib.Log{
Level: "ERROR",
Content: "Failed to cleanup temporary directory: " + err.Error(),
Prompt: false,
})
}
}
// BuildEPKHandleError handles errors related to the building of EPKs
func BuildEPKHandleError(tempDir string, err error, vagueError error, logger *lib.Logger) {
switch {
case errors.Is(vagueError, lib.ErrBuildEPKChrootError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Chroot containers are not yet supported",
Prompt: false,
})
case errors.Is(vagueError, lib.ErrBuildEPKUnrestrictedError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Unrestricted builds are not yet supported",
Prompt: false,
})
case errors.Is(vagueError, lib.ErrBuildEPKTemporaryDirectoryError):
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to create temporary directory: " + err.Error(),
})
case errors.Is(vagueError, lib.ErrBuildEPKCreateHooksError):
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to create hooks directory: " + err.Error(),
})
case errors.Is(vagueError, lib.ErrBuildEPKCopyHooksError):
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to copy hooks: " + err.Error(),
})
case errors.Is(vagueError, lib.ErrBuildEPKBuildShError):
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to create build.sh: " + err.Error(),
})
case errors.Is(vagueError, lib.ErrBuildEPKWritingBuildShError):
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to write to build.sh: " + err.Error(),
})
case errors.Is(vagueError, lib.ErrBuildEPKTargetRootError):
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to create target root: " + err.Error(),
})
case errors.Is(vagueError, lib.ErrBuildEPKExecutingBuildShError):
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to start BubbleWrap container and run build.sh in it: " + err.Error(),
})
case errors.Is(vagueError, lib.ErrBuildEPKCountingFilesError):
BuildEPKRemoveTemporaryDirectory(tempDir, logger)
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to count files in target root: " + err.Error(),
})
case errors.Is(vagueError, lib.ErrBuildEPKBadBuildType):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Invalid build type: " + err.Error(),
})
}
}
// These errors are in the wrong order due to this function being rewritten.
// Oh well, not like it matters.
// PackageEPKHandleError handles errors related to the packaging of EPKs
func PackageEPKHandleError(err error, vagueError error, logger *lib.Logger) {
switch {
case errors.Is(vagueError, lib.ErrPackageEPKCreateDistDirError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to create dist directory: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKMoveToDistError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to move to dist directory: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKTarError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to create Tar archive: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKJSONMarshal):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to marshal JSON: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKCreateCompressionWriterError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to create compression writer: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKCompressCloseError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to close compression writer: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKCannotOpenFile):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to open file for writing: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKCannotWriteFile):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to write to file: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKCannotCloseFile):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to close file: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKFailedToSeek):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to move file cursor: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrPackageEPKFailedToWriteHash):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to generate hash: " + err.Error(),
Prompt: false,
})
}
}
var LoadPrivateKeyHomedirError = errors.New("failed to get home directory")
var LoadPrivateKeyReadError = errors.New("failed to read private key")
var LoadPrivateKeyGenerateError = errors.New("failed to generate private key")
var LoadPrivateKeyMarshalError = errors.New("failed to marshal private key")
var LoadPrivateKeyFolderCreateError = errors.New("failed to create folder")
var LoadPrivateKeyWriteError = errors.New("failed to write private key")
var LoadPrivateKeyEmptyBlockError = errors.New("blocks returned by pem.Decode are empty")
var LoadPrivateKeyParseError = errors.New("failed to parse private key")
var LoadPrivateKeyNotEd25519Error = errors.New("private key is not ed25519")
// LoadPrivateKey loads the private key from $HOME/.local/share/eternity/private.key
func LoadPrivateKey(logger *lib.Logger) (ed25519.PrivateKey, error, error) {
// Attempt to read the key from $HOME/.local/share/eternity/private.key
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, err, LoadPrivateKeyHomedirError
}
privateKeyBytes, err := os.ReadFile(filepath.Join(homeDir, ".local/share/eternity/private.key"))
if err != nil {
if !os.IsNotExist(err) {
return nil, err, LoadPrivateKeyReadError
} else {
response := logger.LogFunc(lib.Log{
Level: "INFO",
Content: "You need to add a private key to sign EPKs. Would you like to generate one now? (y/n)",
Prompt: true,
})
if strings.ToLower(response) == "y" {
_, privateKey, err := ed25519.GenerateKey(nil)
if err != nil {
return nil, err, LoadPrivateKeyGenerateError
}
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return nil, err, LoadPrivateKeyMarshalError
}
privateKeyFile := pem.EncodeToMemory(&pem.Block{
Type: "ED25519 PRIVATE KEY",
Bytes: privateKeyBytes,
})
err = os.MkdirAll(filepath.Join(homeDir, ".local/share/eternity"), 0700)
if err != nil {
return nil, err, LoadPrivateKeyFolderCreateError
}
err = os.WriteFile(filepath.Join(homeDir, ".local/share/eternity/private.key"), privateKeyFile, 0600)
if err != nil {
return nil, err, LoadPrivateKeyWriteError
}
logger.LogFunc(lib.Log{
Level: "INFO",
Content: "Private key written to disk",
Prompt: false,
})
return privateKey, nil, nil
} else if strings.ToLower(response) == "n" {
logger.LogFunc(lib.Log{
Level: "INFO",
Content: "Understood. Please add a ed25519 private key to " + filepath.Join(homeDir, ".local/share/eternity/private.key") + " in order to sign EPKs and repositories",
Prompt: false,
})
os.Exit(1)
}
}
}
block, _ := pem.Decode(privateKeyBytes)
if block == nil {
return nil, nil, LoadPrivateKeyEmptyBlockError
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err, LoadPrivateKeyParseError
}
ed25519PrivateKey, ok := privateKey.(ed25519.PrivateKey)
if !ok {
return nil, nil, LoadPrivateKeyNotEd25519Error
}
return ed25519PrivateKey, nil, nil
}
// LoadPrivateKeyHandleError handles errors related to LoadPrivateKey
func LoadPrivateKeyHandleError(err error, vagueError error, logger *lib.Logger) {
switch {
case errors.Is(vagueError, LoadPrivateKeyHomedirError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to get home directory: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, LoadPrivateKeyReadError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to read private key: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, LoadPrivateKeyGenerateError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to generate private key: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, LoadPrivateKeyMarshalError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to marshal private key: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, LoadPrivateKeyFolderCreateError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to create eternity config folder (are you missing permissions?): " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, LoadPrivateKeyWriteError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to write private key to disk: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, LoadPrivateKeyEmptyBlockError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Blocks returned by pem.Decode are empty",
Prompt: false,
})
case errors.Is(vagueError, LoadPrivateKeyParseError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to parse private key: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, LoadPrivateKeyNotEd25519Error):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Private key is not a ed25519 key",
Prompt: false,
})
}
}
func GenerateRepositoryHandleError(err error, vagueError error, logger *lib.Logger) {
switch {
case errors.Is(vagueError, lib.ErrGenerateRepositoryStatError):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to stat directory: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrGenerateRepositoryNotDirectory):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Specified path is not a directory",
Prompt: false,
})
case errors.Is(vagueError, lib.ErrGenerateRepositoryFailedToWalk):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to walk directory: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrGenerateRepositoryCannotMarshalJSON):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to marshal JSON: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrGenerateRepositoryCannotOpenFile):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to open file for writing: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrGenerateRepositoryCannotWriteFile):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to write to file: " + err.Error(),
Prompt: false,
})
case errors.Is(vagueError, lib.ErrGenerateRepositoryCannotCloseFile):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to close file: " + err.Error(),
Prompt: false,
})
}
}