484 lines
15 KiB
Go
484 lines
15 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,
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
}
|
|
}
|
|
|
|
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 {
|
|
return nil, err, ErrLoadPrivateKeyHomedir
|
|
}
|
|
|
|
privateKeyBytes, err := os.ReadFile(filepath.Join(homeDir, ".local/share/eternity/private.key"))
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
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 {
|
|
return nil, err, ErrLoadPrivateKeyGenerate
|
|
}
|
|
|
|
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
|
if err != nil {
|
|
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 {
|
|
return nil, err, ErrLoadPrivateKeyFolderCreate
|
|
}
|
|
|
|
err = os.WriteFile(filepath.Join(homeDir, ".local/share/eternity/private.key"), privateKeyFile, 0600)
|
|
if err != nil {
|
|
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 {
|
|
return nil, nil, ErrLoadPrivateKeyEmptyBlock
|
|
}
|
|
|
|
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, err, ErrLoadPrivateKeyParse
|
|
}
|
|
|
|
ed25519PrivateKey, ok := privateKey.(ed25519.PrivateKey)
|
|
if !ok {
|
|
return nil, nil, ErrLoadPrivateKeyNotEd25519
|
|
}
|
|
|
|
return ed25519PrivateKey, nil, nil
|
|
}
|
|
|
|
// LoadPrivateKeyHandleError handles errors related to LoadPrivateKey
|
|
func LoadPrivateKeyHandleError(err error, vagueError error, logger *lib.Logger) {
|
|
switch {
|
|
case errors.Is(vagueError, ErrLoadPrivateKeyHomedir):
|
|
logger.LogFunc(lib.Log{
|
|
Level: "FATAL",
|
|
Content: "Failed to get home directory: " + err.Error(),
|
|
Prompt: false,
|
|
})
|
|
case errors.Is(vagueError, ErrLoadPrivateKeyRead):
|
|
logger.LogFunc(lib.Log{
|
|
Level: "FATAL",
|
|
Content: "Failed to read private key: " + err.Error(),
|
|
Prompt: false,
|
|
})
|
|
case errors.Is(vagueError, ErrLoadPrivateKeyGenerate):
|
|
logger.LogFunc(lib.Log{
|
|
Level: "FATAL",
|
|
Content: "Failed to generate private key: " + err.Error(),
|
|
Prompt: false,
|
|
})
|
|
case errors.Is(vagueError, ErrLoadPrivateKeyMarshal):
|
|
logger.LogFunc(lib.Log{
|
|
Level: "FATAL",
|
|
Content: "Failed to marshal private key: " + err.Error(),
|
|
Prompt: false,
|
|
})
|
|
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,
|
|
})
|
|
case errors.Is(vagueError, ErrLoadPrivateKeyWrite):
|
|
logger.LogFunc(lib.Log{
|
|
Level: "FATAL",
|
|
Content: "Failed to write private key to disk: " + err.Error(),
|
|
Prompt: false,
|
|
})
|
|
case errors.Is(vagueError, ErrLoadPrivateKeyEmptyBlock):
|
|
logger.LogFunc(lib.Log{
|
|
Level: "FATAL",
|
|
Content: "Blocks returned by pem.Decode are empty",
|
|
Prompt: false,
|
|
})
|
|
case errors.Is(vagueError, ErrLoadPrivateKeyParse):
|
|
logger.LogFunc(lib.Log{
|
|
Level: "FATAL",
|
|
Content: "Failed to parse private key: " + err.Error(),
|
|
Prompt: false,
|
|
})
|
|
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,
|
|
})
|
|
}
|
|
}
|