eternity/common/main.go

485 lines
15 KiB
Go
Raw Normal View History

package common
import (
"bufio"
"errors"
"fmt"
"os"
"strings"
2024-10-02 10:07:12 -07:00
"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,
}
2024-11-02 00:02:50 -07:00
// Command to create an eternity.json file
func CreateEternityJson(logger *lib.Logger, dir string) error {
logger.LogFunc(lib.Log{
Level: "INFO",
Content: "Creating eternity.json",
Prompt: false,
})
// Ensure there is not an existing eternity.json before creating one
if _, err := os.Stat(filepath.Join(dir, "eternity.json")); err == nil {
return errors.New("eternity.json already exists")
} else if !os.IsNotExist(err) {
return err
}
file, err := os.Create(filepath.Join(dir, "eternity.json"))
if err != nil {
return err
}
// Add the actual JSON to the file
_, err = file.WriteString(`{
"version": "0.1",
"build": {
"buildType": "unrestricted",
"buildDir": "build",
"outputDir": "dist",
"hooks": {
"preBuild": "hooks/pre-build.sh",
"postBuild": "hooks/post-build.sh"
}
},
"repository": {
"repoDir": "repo",
"repoName": "eternity-repo",
"repoURL": "https://example.com/repo"
}
}`)
if err != nil {
return err
}
// If an error occurs during the creation, return the error
return nil
}
// 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,
})
}
}
2024-11-02 00:02:50 -07:00
var ErrLoadPrivateKeyHomedir = errors.New("failed to get home directory")
var ErrLoadPrivateKeyRead = errors.New("failed to read private key")
var ErrLoadPrivateKeyGenerate = errors.New("failed to generate private key")
var ErrLoadPrivateKeyMarshal = errors.New("failed to marshal private key")
var ErrLoadPrivateKeyFolderCreate = errors.New("failed to create folder")
var ErrLoadPrivateKeyWrite = errors.New("failed to write private key")
var ErrLoadPrivateKeyEmptyBlock = errors.New("blocks returned by pem.Decode are empty")
var ErrLoadPrivateKeyParse = errors.New("failed to parse private key")
var ErrLoadPrivateKeyNotEd25519 = 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 {
2024-11-02 00:02:50 -07:00
return nil, err, ErrLoadPrivateKeyHomedir
}
privateKeyBytes, err := os.ReadFile(filepath.Join(homeDir, ".local/share/eternity/private.key"))
if err != nil {
if !os.IsNotExist(err) {
2024-11-02 00:02:50 -07:00
return nil, err, ErrLoadPrivateKeyRead
} 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 {
2024-11-02 00:02:50 -07:00
return nil, err, ErrLoadPrivateKeyGenerate
}
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
2024-11-02 00:02:50 -07:00
return nil, err, ErrLoadPrivateKeyMarshal
}
privateKeyFile := pem.EncodeToMemory(&pem.Block{
Type: "ED25519 PRIVATE KEY",
Bytes: privateKeyBytes,
})
err = os.MkdirAll(filepath.Join(homeDir, ".local/share/eternity"), 0700)
if err != nil {
2024-11-02 00:02:50 -07:00
return nil, err, ErrLoadPrivateKeyFolderCreate
}
err = os.WriteFile(filepath.Join(homeDir, ".local/share/eternity/private.key"), privateKeyFile, 0600)
if err != nil {
2024-11-02 00:02:50 -07:00
return nil, err, ErrLoadPrivateKeyWrite
}
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 {
2024-11-02 00:02:50 -07:00
return nil, nil, ErrLoadPrivateKeyEmptyBlock
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
2024-11-02 00:02:50 -07:00
return nil, err, ErrLoadPrivateKeyParse
}
ed25519PrivateKey, ok := privateKey.(ed25519.PrivateKey)
if !ok {
2024-11-02 00:02:50 -07:00
return nil, nil, ErrLoadPrivateKeyNotEd25519
}
return ed25519PrivateKey, nil, nil
}
// LoadPrivateKeyHandleError handles errors related to LoadPrivateKey
func LoadPrivateKeyHandleError(err error, vagueError error, logger *lib.Logger) {
switch {
2024-11-02 00:02:50 -07:00
case errors.Is(vagueError, ErrLoadPrivateKeyHomedir):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to get home directory: " + err.Error(),
Prompt: false,
})
2024-11-02 00:02:50 -07:00
case errors.Is(vagueError, ErrLoadPrivateKeyRead):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to read private key: " + err.Error(),
Prompt: false,
})
2024-11-02 00:02:50 -07:00
case errors.Is(vagueError, ErrLoadPrivateKeyGenerate):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to generate private key: " + err.Error(),
Prompt: false,
})
2024-11-02 00:02:50 -07:00
case errors.Is(vagueError, ErrLoadPrivateKeyMarshal):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to marshal private key: " + err.Error(),
Prompt: false,
})
2024-11-02 00:02:50 -07:00
case errors.Is(vagueError, ErrLoadPrivateKeyFolderCreate):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to create eternity config folder (are you missing permissions?): " + err.Error(),
Prompt: false,
})
2024-11-02 00:02:50 -07:00
case errors.Is(vagueError, ErrLoadPrivateKeyWrite):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to write private key to disk: " + err.Error(),
Prompt: false,
})
2024-11-02 00:02:50 -07:00
case errors.Is(vagueError, ErrLoadPrivateKeyEmptyBlock):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Blocks returned by pem.Decode are empty",
Prompt: false,
})
2024-11-02 00:02:50 -07:00
case errors.Is(vagueError, ErrLoadPrivateKeyParse):
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Failed to parse private key: " + err.Error(),
Prompt: false,
})
2024-11-02 00:02:50 -07:00
case errors.Is(vagueError, ErrLoadPrivateKeyNotEd25519):
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,
})
}
}