diff --git a/installers/linux/packages/.gitignore b/installers/linux/packages/.gitignore new file mode 100644 index 000000000..20efed4de --- /dev/null +++ b/installers/linux/packages/.gitignore @@ -0,0 +1,3 @@ +elm-stuff +work +out diff --git a/installers/linux/packages/.vscode/settings.json b/installers/linux/packages/.vscode/settings.json new file mode 100644 index 000000000..aa329955c --- /dev/null +++ b/installers/linux/packages/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "elmLS.elmFormatPath": "elm-format-hack" +} diff --git a/installers/linux/packages/README.md b/installers/linux/packages/README.md new file mode 100644 index 000000000..2d98beec1 --- /dev/null +++ b/installers/linux/packages/README.md @@ -0,0 +1,3 @@ +To run the packaging script: `npx elm-pages run src/Main.elm`. + +This will put the results in the `out` folder. diff --git a/installers/linux/packages/elm.json b/installers/linux/packages/elm.json new file mode 100644 index 000000000..9f809a4a5 --- /dev/null +++ b/installers/linux/packages/elm.json @@ -0,0 +1,60 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "dillonkearns/elm-cli-options-parser": "3.2.0", + "dillonkearns/elm-pages": "10.2.2", + "elm/bytes": "1.0.8", + "elm/core": "1.0.5", + "elm/json": "1.1.3", + "elmcraft/core-extra": "2.2.0", + "wolfadex/elm-ansi": "3.0.0" + }, + "indirect": { + "Chadtech/elm-bool-extra": "2.4.2", + "avh4/elm-color": "1.0.0", + "danfishgold/base64-bytes": "1.1.0", + "danyx23/elm-mimetype": "4.0.1", + "dillonkearns/elm-bcp47-language-tag": "2.0.0", + "dillonkearns/elm-date-or-date-time": "2.0.0", + "dillonkearns/elm-form": "3.0.1", + "elm/browser": "1.0.2", + "elm/file": "1.0.5", + "elm/html": "1.0.0", + "elm/http": "2.0.0", + "elm/parser": "1.1.0", + "elm/random": "1.0.0", + "elm/regex": "1.0.0", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.4", + "elm-community/basics-extra": "4.1.0", + "elm-community/list-extra": "8.7.0", + "elm-community/maybe-extra": "5.3.0", + "fredcy/elm-parseint": "2.0.1", + "jluckyiv/elm-utc-date-strings": "1.0.0", + "justinmimbs/date": "4.1.0", + "mdgriffith/elm-codegen": "5.2.0", + "miniBill/elm-codec": "2.3.0", + "miniBill/elm-unicode": "1.1.1", + "noahzgordon/elm-color-extra": "1.0.2", + "robinheghan/fnv1a": "1.0.0", + "robinheghan/murmur3": "1.0.0", + "rtfeldman/elm-css": "18.0.0", + "rtfeldman/elm-hex": "1.0.0", + "rtfeldman/elm-iso8601-date-strings": "1.1.4", + "stil4m/elm-syntax": "7.3.9", + "stil4m/structured-writer": "1.0.3", + "the-sett/elm-pretty-printer": "3.1.1", + "the-sett/elm-syntax-dsl": "6.0.3" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} diff --git a/installers/linux/packages/src/Main.elm b/installers/linux/packages/src/Main.elm new file mode 100644 index 000000000..4d98be44c --- /dev/null +++ b/installers/linux/packages/src/Main.elm @@ -0,0 +1,191 @@ +module Main exposing (run) + +import Ansi.Color +import BackendTask exposing (BackendTask) +import BackendTask.Do as Do +import FatalError exposing (FatalError) +import Json.Encode +import Packages exposing (Arch(..), Package) +import Pages.Script as Script exposing (Script) + + +run : Script +run = + Script.withoutCliOptions task + + +task : BackendTask FatalError () +task = + logExec "๐Ÿ‘ท Creating temporary work directory" "mkdir" [ "-p", "work" ] <| \_ -> + logExec "๐Ÿ‘ท Creating output directory" "mkdir" [ "-p", "out" ] <| \_ -> + Do.each Packages.packages + (\({ name, version, url, arch } as package) -> + let + fullName : String + fullName = + name ++ "-" ++ version ++ "-" ++ archToDebian arch + + binaryPath : String + binaryPath = + "work/" ++ fullName + in + Do.log (Ansi.Color.fontColor Ansi.Color.brightYellow ("๐Ÿƒ " ++ fullName)) <| \_ -> + logExecs " ๐Ÿ”ฝ Downloading" + [ ( "curl", [ url, "-sSL", "-o", binaryPath ] ) + , ( "chmod", [ "+x", binaryPath ] ) + ] + <| \_ -> + logExec " ๐Ÿ•ต๏ธ Verifying hash" "sha256sum" [ binaryPath ] <| \actualHash -> + let + expectedHash : String + expectedHash = + package.hash ++ " " ++ binaryPath + in + if String.trim actualHash == expectedHash then + Do.do (prepareDeb { package = package, binaryPath = binaryPath }) <| \_ -> + Do.noop + + else + BackendTask.fail + (FatalError.fromString + ("Invalid hash:\n expected: " + ++ expectedHash + ++ ",\n got: " + ++ String.trim actualHash + ) + ) + ) + <| \_ -> + logExec "๐Ÿงน Cleaning up temporary work directory" "rm" [ "-r", "work" ] <| \_ -> + Do.noop + + +archToDebian : Arch -> String +archToDebian arch = + case arch of + X86_64 -> + "amd64" + + Arm64 -> + "aarch64" + + +prepareDeb : { package : Package, binaryPath : String } -> BackendTask FatalError () +prepareDeb { package, binaryPath } = + let + revision : String + revision = + "1" + + debName : String + debName = + package.name ++ "_" ++ package.version ++ "-" ++ revision + + debPath : String + debPath = + "work/" ++ debName + in + logExecs " ๐ŸŒ€ ๐Ÿ“ Preparing folders for .deb " + [ ( "rm", [ "-rf", debPath ] ) + , ( "mkdir", [ "-p", debPath ++ "/usr/local/bin" ] ) + , ( "cp", [ binaryPath, debPath ++ "/usr/local/bin/" ++ package.name ] ) + , ( "mkdir", [ debPath ++ "/DEBIAN" ] ) + ] + <| \_ -> + logCyan " ๐ŸŒ€ ๐Ÿ“ฐ Writing DEBIAN/control file" <| \_ -> + Do.allowFatal + (Script.writeFile + { path = debPath ++ "/DEBIAN/control" + , body = controlFile { package = package, revision = revision } + } + ) + <| \_ -> + logExecs " ๐ŸŒ€ ๐Ÿ“ฆ Creating the package" + [ ( "dpkg-deb", [ "--root-owner-group", "--build", debPath, "out/" ++ debName ++ "_" ++ archToDebian package.arch ++ ".deb" ] ) ] + <| \_ -> + Do.noop + + +controlFile : { package : Package, revision : String } -> String +controlFile { package, revision } = + [ "Package: " ++ package.name + , "Version: " ++ package.version ++ "-" ++ revision + , "Section: base" + , "Priority: optional" + , "Architecture: " ++ archToDebian package.arch + + -- , "Depends:" + , "Maintainer: " ++ package.maintainer + , "Description: " ++ package.description + , "" + ] + |> String.join "\n" + + +logExec : String -> String -> List String -> (String -> BackendTask FatalError a) -> BackendTask FatalError a +logExec msg cmd args k = + logCyan msg <| \_ -> + Do.log (formatCmd cmd args) <| \_ -> + Do.command cmd args k + + +logExecs : String -> List ( String, List String ) -> (() -> BackendTask FatalError a) -> BackendTask FatalError a +logExecs msg cmds k = + logCyan msg <| \_ -> + Do.each cmds + (\( cmd, args ) -> + Do.log (formatCmd cmd args) <| \_ -> + Do.exec cmd args <| \_ -> + Do.noop + ) + <| \_ -> + k () + + +logCyan : String -> ((() -> BackendTask FatalError a) -> BackendTask FatalError a) +logCyan msg = + Do.log (Ansi.Color.fontColor Ansi.Color.cyan msg) + + +formatCmd : String -> List String -> String +formatCmd cmd args = + String.join " " <| + " " + :: Ansi.Color.fontColor Ansi.Color.green (escapeMaybe cmd) + :: List.map + (\arg -> + let + escaped : String + escaped = + escapeMaybe arg + in + if String.startsWith "\"" escaped then + Ansi.Color.fontColor Ansi.Color.yellow escaped + + else if String.startsWith "-" escaped then + Ansi.Color.fontColor + (Ansi.Color.rgb + { blue = 0xB0 + , green = 0xB0 + , red = 0xB0 + } + ) + escaped + + else + Ansi.Color.fontColor Ansi.Color.brightWhite escaped + ) + args + + +escapeMaybe : String -> String +escapeMaybe input = + let + escaped = + input |> Json.Encode.string |> Json.Encode.encode 0 + in + if not (String.contains " " input) && escaped == "\"" ++ input ++ "\"" then + input + + else + escaped diff --git a/installers/linux/packages/src/Packages.elm b/installers/linux/packages/src/Packages.elm new file mode 100644 index 000000000..7715a35d8 --- /dev/null +++ b/installers/linux/packages/src/Packages.elm @@ -0,0 +1,221 @@ +module Packages exposing (Arch(..), Package, packages) + + +type alias Package = + { name : String + , version : String + , url : String + , arch : Arch + , maintainer : String + , description : String + , hash : String + } + + +type Arch + = X86_64 + | Arm64 + + +arches : List Arch +arches = + [ X86_64 + , Arm64 + ] + + +type alias Hashes = + { x86_64 : String + , arm64 : String + } + + +packages : List Package +packages = + [ lamdera + { lamderaVersion = "1.3.2" + , elmVersion = "0.19.1" + , hashes = + { x86_64 = "15a69bfa98155651749e31c68d05a04fcf48bdccb86bce77b7c8872f545cecfa" + , arm64 = "68a16bbbd2ed0ee19c36112a4c2d0abca66cf17465747e55adf2596b0921f8d7" + } + } + , elmFormat + { version = "0.8.7" + , hashes = + { x86_64 = "44344c7b6f838dc5d9495dfe4253280a698c2251ee8cfa29b6d1a032b6efb13b" + , arm64 = "fe99b3925201598121aeea6b31b55bd3ab6dad743bce27082d8e01e723bd160e" + } + } + , elmFormat + { version = "0.8.8" + , hashes = + { x86_64 = "ee749898a07871e5dcbe7adf77a6c3d95de2fcde2e15de30e4fa7457faf05a71" + , arm64 = "0be0046a81432e6e16340b8093cafa35a454e84956522d53f6a28f815dceac23" + } + } + , elmTestRs + { version = "3.0" + , hashes = + { x86_64 = "c72702d32a2a9e051667febeeef486a1794798d0770be1a9da95895e10b6db0f" + , arm64 = "bf468f39f9a9700f7ca0ed29719f9c9051e4b3796976ae0752a5186fdb8f0449" + } + } + , elmTestRs + { version = "3.0.1" + , hashes = + { x86_64 = "3d99e394f2a90ddf5fcb579b7c9c822b62c2a71c5621cb9e2c5d5b37f8a9d5a7" + , arm64 = "c3ca8a90c0c7cb0fbeeab8f164271854cd449fdab5f30894fb09fba2e575b595" + } + } + , elmJson + { version = "0.2.13" + , hashes = + { x86_64 = "83cbab79f6c237d3f96b69baf519bdd7634d0e0373a390594d37591c0295f965" + , arm64 = "" + } + } + ] + |> List.concatMap + (\f -> + List.filterMap + (\arch -> + f arch + |> Maybe.map + (\data -> + { name = data.name + , version = data.version + , url = data.url + , description = data.description + , maintainer = data.maintainer + , hash = getHash arch data.hashes + , arch = arch + } + ) + ) + arches + ) + + +type alias PackageData = + { name : String + , version : String + , url : String + , description : String + , maintainer : String + , hashes : Hashes + } + + +lamdera : { lamderaVersion : String, elmVersion : String, hashes : Hashes } -> Arch -> Maybe PackageData +lamdera { lamderaVersion, elmVersion, hashes } arch = + let + lamderaArch : String + lamderaArch = + case arch of + X86_64 -> + "x86_64" + + Arm64 -> + "arm64" + + fullName : String + fullName = + "lamdera-" ++ lamderaVersion ++ "-linux-" ++ lamderaArch + + url : String + url = + "https://static.lamdera.com/bin/" ++ fullName + in + { name = "lamdera" + , version = lamderaVersion ++ "-" ++ elmVersion + , url = url + , description = "A delightful platform for full-stack web apps" + , maintainer = "Mario Rogic " + , hashes = hashes + } + |> Just + + +elmFormat : { version : String, hashes : Hashes } -> Arch -> Maybe PackageData +elmFormat { version, hashes } arch = + let + elmFormatArch : String + elmFormatArch = + case arch of + X86_64 -> + "x64" + + Arm64 -> + "aarch64" + + url : String + url = + "https://github.com/avh4/elm-format/releases/download/" ++ version ++ "/elm-format-" ++ version ++ "-linux-" ++ elmFormatArch ++ ".tgz" + in + { name = "elm-format" + , version = version + , url = url + , description = "Formats Elm source code according to a standard set of rules based on the official Elm Style Guide" + , maintainer = "Aaron VonderHaar " + , hashes = hashes + } + |> Just + + +elmTestRs : { version : String, hashes : Hashes } -> Arch -> Maybe PackageData +elmTestRs { version, hashes } arch = + let + elmTestRsArch : String + elmTestRsArch = + case arch of + X86_64 -> + "" + + Arm64 -> + "-arm-64" + + url : String + url = + "https://github.com/mpizenberg/elm-test-rs/releases/download/v" ++ version ++ "/elm-test-rs_linux" ++ elmTestRsArch ++ ".tar.gz" + in + { name = "elm-test-rs" + , version = version + , url = url + , description = "Fast and portable executable to run your Elm tests" + , maintainer = "Matthieu Pizenberg " + , hashes = hashes + } + |> Just + + +elmJson : { version : String, hashes : Hashes } -> Arch -> Maybe PackageData +elmJson { version, hashes } arch = + case arch of + X86_64 -> + let + url : String + url = + "https://github.com/zwilias/elm-json/releases/download/v" ++ version ++ "/elm-json-v" ++ version ++ "-x86_64-unknown-linux-musl.tar.gz" + in + { name = "elm-json" + , version = version + , url = url + , description = "Install, upgrade and uninstall Elm dependencies" + , maintainer = "Ilias Van Peer " + , hashes = hashes + } + |> Just + + Arm64 -> + Nothing + + +getHash : Arch -> Hashes -> String +getHash arch hashes = + case arch of + X86_64 -> + hashes.x86_64 + + Arm64 -> + hashes.arm64