-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proof Of Concept Repl Bots via nix-shell #32
base: main
Are you sure you want to change the base?
Changes from 7 commits
0fadbed
aa151cd
c912eb4
cf806c7
84a387a
8aa8125
6e091bd
198eb93
92e44cb
e514ce9
5ff5f30
0b6553d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
#!/usr/bin/env bash | ||
set -euxo pipefail | ||
|
||
nix build .#docker | ||
image=$(docker load -i result | sed -n 's#^Loaded image: \([a-zA-Z0-9\.\/\-\:]*\)#\1#p') | ||
docker push $image | ||
nix build .#deploy-bot | ||
./result |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ pkgs }: | ||
let | ||
mkImage = pkg: cmd: | ||
pkgs.dockerTools.buildLayeredImage { | ||
name = "ghcr.io/cofree-coffee/${pkg.name}-repl"; | ||
created = "now"; | ||
tag = "latest"; | ||
contents = [ | ||
pkg | ||
]; | ||
config = { | ||
Cmd = cmd; | ||
}; | ||
}; | ||
|
||
mkPath = xs: | ||
with pkgs.lib; | ||
with pkgs.lib.attrsets; | ||
let paths = mapAttrsToList (name: image: "ln -s ${image} $out/${name}") xs; | ||
in "mkdir -p $out" + (foldl' (a: b: a + ";" + b) "" paths); | ||
|
||
images = | ||
{ | ||
python = mkImage pkgs.python3 [ "python" "-iq" ]; | ||
node = mkImage pkgs.nodejs [ "node" "-i" ]; | ||
ghci = mkImage pkgs.ghc [ "ghci" ]; | ||
}; | ||
in | ||
pkgs.runCommand "repls" { } (mkPath images) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JonathanLorimer for REPL bots I am building docker images with nix. Do you have any thoughts on how we can design this derivation to be more scalable for adding more languages? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I honestly think these could all just be systemd jobs inside nix shells though... systemd.services.python-repl = {
description = "Python repl";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
Type = "notify";
ExecStart = "nix-shell -p python3 --command "python -iq"";
};
}; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you could abstract the python specific stuff in a nix module and then your nixos configuration would just do this: sercives.language-repls = [
{name = "python"; package = pkgs.python3; binName = "python3"; opts = "-iq";}
...
]; you wouldn't even need the nix shell bit above if you did direct interpolation: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JonathanLorimer I don't think that's a good idea. The point of containerization is to isolate the server from untrusted code execution, so that if someone runs |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
module CofreeBot.Bot.Behaviors | ||
( module Calculator | ||
, module CoinFlip | ||
, module GHCI | ||
, module Hello | ||
, module Repl | ||
) where | ||
|
||
import CofreeBot.Bot.Behaviors.Calculator | ||
as Calculator | ||
import CofreeBot.Bot.Behaviors.CoinFlip | ||
as CoinFlip | ||
import CofreeBot.Bot.Behaviors.GHCI as GHCI | ||
import CofreeBot.Bot.Behaviors.Hello as Hello | ||
import CofreeBot.Bot.Behaviors.Repl as Repl |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
module CofreeBot.Bot.Behaviors.Repl | ||
( module Util | ||
, module GHCI | ||
, module Node | ||
, module Python | ||
, replConfigs | ||
) where | ||
|
||
import CofreeBot.Bot.Behaviors.Repl.Util as Util | ||
import CofreeBot.Bot.Behaviors.Repl.GHCI as GHCI | ||
import CofreeBot.Bot.Behaviors.Repl.Node as Node | ||
import CofreeBot.Bot.Behaviors.Repl.Python as Python | ||
import System.IO | ||
import System.Process.Typed | ||
|
||
replConfigs :: Repls (ProcessConfig Handle Handle ()) | ||
replConfigs = Repls | ||
{ python = pythonConfig | ||
, ghci = ghciConfig | ||
, node = nodeConfig | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module CofreeBot.Bot.Behaviors.Repl.GHCI | ||
( ghciBot | ||
, ghciConfig | ||
) where | ||
|
||
import CofreeBot.Bot | ||
import CofreeBot.Bot.Behaviors.Repl.Util | ||
import CofreeBot.Utils | ||
import Data.Profunctor | ||
import System.IO | ||
import System.Process.Typed | ||
|
||
ghciBot' :: Process Handle Handle () -> ReplBot | ||
ghciBot' = replBot "ghci: " | ||
|
||
ghciBot :: Process Handle Handle () -> ReplBot | ||
ghciBot p = | ||
dimap (distinguish (/= "ghci: :q")) indistinct | ||
$ pureStatelessBot (const $ ["I'm Sorry Dave"]) | ||
\/ ghciBot' p | ||
|
||
ghciConfig :: ProcessConfig Handle Handle () | ||
ghciConfig = replConfig "docker run -i --rm haskell 2>&1" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module CofreeBot.Bot.Behaviors.Repl.Node | ||
( nodeBot | ||
, nodeConfig | ||
) where | ||
|
||
import CofreeBot.Bot.Behaviors.Repl.Util | ||
import System.IO | ||
import System.Process.Typed | ||
|
||
nodeBot :: Process Handle Handle () -> ReplBot | ||
nodeBot = replBot "node: " | ||
|
||
nodeConfig :: ProcessConfig Handle Handle () | ||
nodeConfig = replConfig "nix-shell -p nodejs --run 'node -i' 2>&1" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i need to get this command running in a docker container. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module CofreeBot.Bot.Behaviors.Repl.Python | ||
( pythonBot | ||
, pythonConfig | ||
) where | ||
|
||
import CofreeBot.Bot.Behaviors.Repl.Util | ||
import System.IO | ||
import System.Process.Typed | ||
|
||
pythonBot :: Process Handle Handle () -> ReplBot | ||
pythonBot = replBot "python: " | ||
|
||
pythonConfig :: ProcessConfig Handle Handle () | ||
pythonConfig = replConfig "nix-shell -p python3 --run 'python -iq' 2>&1" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{-# LANGUAGE NumDecimals #-} | ||
{-# LANGUAGE DeriveFoldable #-} | ||
{-# LANGUAGE DeriveTraversable #-} | ||
module CofreeBot.Bot.Behaviors.Repl.Util where | ||
|
||
import CofreeBot.Bot | ||
import Control.Exception (bracket) | ||
import Control.Monad | ||
import Control.Monad.Loops ( whileM ) | ||
import Data.Attoparsec.Text as A | ||
import Data.Foldable | ||
import qualified Data.Text as T | ||
import GHC.Conc ( threadDelay ) | ||
import System.IO | ||
import System.Process.Typed | ||
|
||
type ReplBot = Bot IO () T.Text [T.Text] | ||
|
||
hGetOutput :: Handle -> IO String | ||
hGetOutput handle = whileM (hReady handle) (hGetChar handle) | ||
|
||
replBot :: T.Text -> Process Handle Handle () -> ReplBot | ||
replBot prompt p = | ||
mapMaybeBot (either (const Nothing) Just . parseOnly (replInputParser prompt)) | ||
$ Bot | ||
$ \i s -> do | ||
hPutStrLn (getStdin p) $ T.unpack i | ||
hFlush (getStdin p) | ||
void $ threadDelay 1e6 | ||
o <- hGetOutput (getStdout p) | ||
pure $ BotAction (pure $ T.pack o) s | ||
|
||
replConfig :: String -> ProcessConfig Handle Handle () | ||
replConfig = setStdin createPipe . setStdout createPipe . shell | ||
|
||
replInputParser :: T.Text -> Parser T.Text | ||
replInputParser prompt = do | ||
void $ string prompt | ||
T.pack <$> many1 anyChar | ||
|
||
data Repls a = Repls | ||
{ python :: a | ||
, ghci :: a | ||
, node :: a | ||
} deriving (Functor, Foldable, Traversable) | ||
|
||
withProcesses :: Repls (ProcessConfig i o e) -> (Repls (Process i o e) -> IO r) -> IO r | ||
withProcesses cfgs = bracket (traverse startProcess cfgs) (traverse_ stopProcess) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JonathanLorimer We are building these scripts for building deploy scripts for the bot and the repl images. We tried to make these apps rather then packages with
pkgs.runCommand
but werent able to rundocker load
. Do you know of any tricks to perform side effects in a flake output or is that off the table?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also we need to get these images onto the server and loaded into docker. I don't want to put them on the github docker hub because they are going to take up too much space as we add more images. Can we modify our github deploy action to sync them onto the server directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the scripts, I would suggest moving them to a separate file parameterized by their dependencies.
I would also suggest using
writeShellApplication
https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/trivial-builders.nix#L274-L305 it will set a bunch of sane defaults as well as the shebang, and you can specifysed
as a runtime input. It will also run shellcheck in the check phase.