2024-08-28 12:10:50 -07:00
package common
import (
2024-09-01 12:23:37 -07:00
"bufio"
2024-08-28 12:10:50 -07:00
"errors"
"fmt"
"os"
"strings"
2024-10-02 10:07:12 -07:00
"git.oreonproject.org/oreonproject/eternity/lib"
2024-09-19 09:13:59 -07:00
"crypto/ed25519"
"crypto/x509"
"encoding/pem"
"path/filepath"
2024-08-28 12:10:50 -07:00
"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 ( ": " )
2024-09-01 12:23:37 -07:00
reader := bufio . NewReader ( os . Stdin )
userInput , err := reader . ReadString ( '\n' )
2024-08-28 12:10:50 -07:00
if err != nil {
fmt . Println ( color . RedString ( "[FATAL]" ) , "Failed to read user input:" , err )
os . Exit ( 1 )
} else {
2024-09-01 12:23:37 -07:00
return userInput [ : len ( userInput ) - 1 ]
2024-08-28 12:10:50 -07:00
}
}
return ""
} ,
PromptSupported : true ,
2024-09-19 09:13:59 -07:00
StdoutSupported : true ,
Stdout : os . Stdout ,
2024-08-28 12:10:50 -07:00
}
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
}
2024-08-28 12:10:50 -07:00
// 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 ( ) ,
} )
2024-09-01 12:23:37 -07:00
case errors . Is ( vagueError , lib . ErrBuildEPKBadBuildType ) :
logger . LogFunc ( lib . Log {
Level : "FATAL" ,
Content : "Invalid build type: " + err . Error ( ) ,
} )
2024-08-28 12:10:50 -07:00
}
}
// 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" )
2024-08-28 12:10:50 -07:00
// 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
2024-08-28 12:10:50 -07:00
}
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
2024-08-28 12:10:50 -07:00
} 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
2024-08-28 12:10:50 -07:00
}
privateKeyBytes , err := x509 . MarshalPKCS8PrivateKey ( privateKey )
if err != nil {
2024-11-02 00:02:50 -07:00
return nil , err , ErrLoadPrivateKeyMarshal
2024-08-28 12:10:50 -07:00
}
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
2024-08-28 12:10:50 -07:00
}
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
2024-08-28 12:10:50 -07:00
}
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
2024-08-28 12:10:50 -07:00
}
privateKey , err := x509 . ParsePKCS8PrivateKey ( block . Bytes )
if err != nil {
2024-11-02 00:02:50 -07:00
return nil , err , ErrLoadPrivateKeyParse
2024-08-28 12:10:50 -07:00
}
ed25519PrivateKey , ok := privateKey . ( ed25519 . PrivateKey )
if ! ok {
2024-11-02 00:02:50 -07:00
return nil , nil , ErrLoadPrivateKeyNotEd25519
2024-08-28 12:10:50 -07:00
}
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 ) :
2024-08-28 12:10:50 -07:00
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 ) :
2024-08-28 12:10:50 -07:00
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 ) :
2024-08-28 12:10:50 -07:00
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 ) :
2024-08-28 12:10:50 -07:00
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 ) :
2024-08-28 12:10:50 -07:00
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 ) :
2024-08-28 12:10:50 -07:00
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 ) :
2024-08-28 12:10:50 -07:00
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 ) :
2024-08-28 12:10:50 -07:00
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 ) :
2024-08-28 12:10:50 -07:00
logger . LogFunc ( lib . Log {
Level : "FATAL" ,
Content : "Private key is not a ed25519 key" ,
Prompt : false ,
} )
}
}
2024-09-01 12:23:37 -07:00
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 ,
} )
}
}