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