|
1 | 1 | module NixVms (NixVms (..), production) where |
2 | 2 |
|
3 | 3 | import Context |
| 4 | +import Control.Concurrent (forkIO) |
4 | 5 | import Cradle |
5 | 6 | import Data.Aeson qualified as Aeson |
6 | 7 | import Data.Map.Strict qualified as Map |
| 8 | +import Data.String.AnsiEscapeCodes.Strip.Text (stripAnsiEscapeCodes) |
7 | 9 | import Data.String.Conversions (cs) |
8 | 10 | import Data.String.Interpolate (i) |
9 | 11 | import Data.Text qualified as T |
10 | 12 | import Data.Text.IO qualified as T |
11 | 13 | import Network.Socket.Free (getFreePort) |
| 14 | +import Options (Verbosity (..), VmName (..)) |
12 | 15 | import State |
13 | 16 | import StdLib |
14 | 17 | import System.Directory (createDirectoryIfMissing, listDirectory) |
15 | 18 | import System.Environment (getEnvironment) |
16 | 19 | import System.FilePath (takeDirectory, (</>)) |
17 | | -import System.IO (IOMode (..), openFile) |
| 20 | +import System.IO (Handle, IOMode (..), openFile) |
18 | 21 | import System.IO qualified |
19 | 22 | import System.Process (CreateProcess (..), ProcessHandle, StdStream (..), createProcess, proc) |
20 | 23 | import Utils |
@@ -47,8 +50,8 @@ listVmsImpl ctx = do |
47 | 50 | Left err -> error err |
48 | 51 | Right (parsed :: [Text]) -> pure $ map VmName parsed |
49 | 52 |
|
50 | | -buildAndRunImpl :: Context -> VmName -> IO ProcessHandle |
51 | | -buildAndRunImpl ctx vmName = do |
| 53 | +buildAndRunImpl :: Context -> Verbosity -> VmName -> IO ProcessHandle |
| 54 | +buildAndRunImpl ctx verbosity vmName = do |
52 | 55 | vmExecutable <- logStep "Building NixOS config..." $ do |
53 | 56 | moduleExtensions <- getModuleExtensions ctx vmName |
54 | 57 | (Cradle.StdoutTrimmed drvPathJson) <- |
@@ -85,24 +88,7 @@ buildAndRunImpl ctx vmName = do |
85 | 88 | case files of |
86 | 89 | [file] -> pure $ cs outPath </> "bin" </> file |
87 | 90 | files -> error $ "expected one vm script: " <> show files |
88 | | - |
89 | | - logStep "Starting VM..." $ do |
90 | | - storageDir <- getStateDir ctx vmName |
91 | | - let nixDiskImage = storageDir </> cs (vmNameToText vmName) </> "image.qcow2" |
92 | | - createDirectoryIfMissing True (takeDirectory nixDiskImage) |
93 | | - stdoutHandle <- openFile (storageDir </> "./stdout.log") WriteMode |
94 | | - stderrHandle <- openFile (storageDir </> "./stderr.log") WriteMode |
95 | | - (_, _, _, ph) <- do |
96 | | - parentEnvironment <- |
97 | | - getEnvironment |
98 | | - <&> Map.fromList |
99 | | - createProcess |
100 | | - (proc vmExecutable []) |
101 | | - { env = Just $ Map.toList $ Map.insert "NIX_DISK_IMAGE" nixDiskImage parentEnvironment, |
102 | | - std_out = UseHandle stdoutHandle, |
103 | | - std_err = UseHandle stderrHandle |
104 | | - } |
105 | | - pure ph |
| 91 | + logStep "Starting VM..." $ runVm ctx verbosity vmName vmExecutable |
106 | 92 |
|
107 | 93 | nixStandardFlags :: [Text] |
108 | 94 | nixStandardFlags = |
@@ -148,6 +134,40 @@ toNixString s = "\"" <> T.concatMap escapeChar (cs s) <> "\"" |
148 | 134 | '\\' -> "\\\\" |
149 | 135 | c -> T.singleton c |
150 | 136 |
|
| 137 | +runVm :: Context -> Verbosity -> VmName -> FilePath -> IO ProcessHandle |
| 138 | +runVm ctx verbosity vmName vmExecutable = do |
| 139 | + storageDir <- getStateDir ctx vmName |
| 140 | + let nixDiskImage = storageDir </> cs (vmNameToText vmName) </> "image.qcow2" |
| 141 | + createDirectoryIfMissing True (takeDirectory nixDiskImage) |
| 142 | + parentEnvironment <- getEnvironment <&> Map.fromList |
| 143 | + let mkProc stdout stdin = |
| 144 | + (System.Process.proc vmExecutable []) |
| 145 | + { env = Just $ Map.toList $ Map.insert "NIX_DISK_IMAGE" nixDiskImage parentEnvironment, |
| 146 | + std_out = stdout, |
| 147 | + std_err = stdin |
| 148 | + } |
| 149 | + proc <- case verbosity of |
| 150 | + DefaultVerbosity -> do |
| 151 | + stdoutHandle <- openFile (storageDir </> "./stdout.log") WriteMode |
| 152 | + stderrHandle <- openFile (storageDir </> "./stderr.log") WriteMode |
| 153 | + pure $ mkProc (UseHandle stdoutHandle) (UseHandle stderrHandle) |
| 154 | + Verbose -> pure $ mkProc CreatePipe CreatePipe |
| 155 | + (_, stdout, stderr, ph) <- createProcess proc |
| 156 | + case verbosity of |
| 157 | + DefaultVerbosity -> pure () |
| 158 | + Verbose -> do |
| 159 | + (Just stdout, Just stderr) <- pure (stdout, stderr) |
| 160 | + _ <- forkIO $ streamHandles "qemu" stdout System.IO.stdout |
| 161 | + _ <- forkIO $ streamHandles "qemu" stderr System.IO.stderr |
| 162 | + pure () |
| 163 | + pure ph |
| 164 | + |
| 165 | +streamHandles :: Text -> Handle -> Handle -> IO () |
| 166 | +streamHandles prefix input output = do |
| 167 | + chunk <- T.hGetLine input |
| 168 | + T.hPutStrLn output $ prefix <> "> " <> stripAnsiEscapeCodes chunk |
| 169 | + streamHandles prefix input output |
| 170 | + |
151 | 171 | sshIntoHostImpl :: (Cradle.Output o) => Context -> VmName -> [Text] -> IO o |
152 | 172 | sshIntoHostImpl ctx vmName command = do |
153 | 173 | vmKeyPath <- getStateFile ctx vmName "vmkey" |
|
0 commit comments