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, }) } }