From a7dcb30c7a465d9fce72b7fc3e605470b2b59814 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Tue, 16 Dec 2025 08:06:09 -0500 Subject: feat(deploy): Complete mini-PaaS deployment system (t-266) - Add Omni/Deploy/ with Manifest, Deployer, Systemd, Caddy modules - Manifest CLI: show, update, add-service, list, rollback commands - Deployer: polls S3 manifest, pulls closures, manages systemd units - Caddy integration for dynamic reverse proxy routes - bild: auto-cache to S3, outputs STORE_PATH for push.sh - push.sh: supports both NixOS and service deploys - Biz.nix: simplified to base OS + deployer only - Services (podcastitlater-web/worker) now deployer-managed - Documentation: README.md with operations guide --- Omni/Bild.hs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 11 deletions(-) (limited to 'Omni/Bild.hs') diff --git a/Omni/Bild.hs b/Omni/Bild.hs index e1f5c46..1ebeb05 100644 --- a/Omni/Bild.hs +++ b/Omni/Bild.hs @@ -249,7 +249,7 @@ move args = do forM_ skippedNamespaces <| \ns -> LogC.updateLineState ns LogC.Skipped action runWithManager <| do - pipelineBuild isTest isLoud 8 jobs (cpus nproc) namespaces analyzeOne + pipelineBuild isTest isLoud (not noCache) 8 jobs (cpus nproc) namespaces analyzeOne |> Timeout.timeout (toMillis minutes) +> \case Nothing -> @@ -285,6 +285,7 @@ move args = do Just n -> n isTest = args `Cli.has` Cli.longOption "test" isLoud = args `Cli.has` Cli.longOption "loud" + noCache = args `Cli.has` Cli.longOption "no-cache" putJSON = Aeson.encode .> ByteString.Lazy.toStrict .> Char8.putStrLn -- | Don't try to build stuff that isn't part of the git repo. @@ -363,6 +364,7 @@ Options: --test, -t Run tests on a target after building --loud, -l Show all output from compiler --plan, -p Print the build plan as JSON, don't build + --no-cache Skip signing and pushing to S3 binary cache --time N Set timeout to N minutes, 0 means never timeout [default: 10] --jobs N, -j N Build up to N jobs at once [default: 6] --cpus N, -c N Allocate up to N cpu cores per job (default: (nproc-4)/jobs) @@ -1297,8 +1299,8 @@ pipelineAnalysisWorker coord@Coordinator {..} analyzeFn = loop else modifyTVar' coStates (Map.insert ns (TSWaitingForDeps target pendingDeps)) loop -pipelineBuildWorker :: Bool -> Bool -> Int -> Int -> Coordinator -> IO () -pipelineBuildWorker andTest loud jobs cpus coord@Coordinator {..} = loop +pipelineBuildWorker :: Bool -> Bool -> Bool -> Int -> Int -> Coordinator -> IO () +pipelineBuildWorker andTest loud andCache jobs cpus coord@Coordinator {..} = loop where loop = do remaining <- readTVarIO coRemaining @@ -1319,7 +1321,7 @@ pipelineBuildWorker andTest loud jobs cpus coord@Coordinator {..} = loop Nothing -> loop Just target -> do LogC.updateLineState ns LogC.Building - exitCode <- pipelineBuildOne andTest loud jobs cpus target + exitCode <- pipelineBuildOne andTest loud andCache jobs cpus target atomically <| do modifyTVar' coStates (Map.insert ns (TSComplete target exitCode)) modifyTVar' coResults (exitCode :) @@ -1342,8 +1344,8 @@ promoteWaiters Coordinator {..} completedNs = do else modifyTVar' coStates (Map.insert ns (TSWaitingForDeps target deps')) _ -> pure () -pipelineBuildOne :: Bool -> Bool -> Int -> Int -> Target -> IO Exit.ExitCode -pipelineBuildOne andTest loud jobs cpus target@Target {..} = do +pipelineBuildOne :: Bool -> Bool -> Bool -> Int -> Int -> Target -> IO Exit.ExitCode +pipelineBuildOne andTest loud andCache jobs cpus target@Target {..} = do root <- getCoderoot result <- case compiler of CPython -> case out of @@ -1392,14 +1394,50 @@ pipelineBuildOne andTest loud jobs cpus target@Target {..} = do nixBuild loud jobs cpus target Sbcl -> proc loud namespace (toNixFlag compiler) compilerFlags - pure (fst result) - -pipelineBuild :: Bool -> Bool -> Int -> Int -> Int -> [Namespace] -> (Namespace -> IO (Maybe Target)) -> IO [Exit.ExitCode] -pipelineBuild andTest loud analysisWorkers buildWorkers cpus namespaces analyzeFn = do + let exitCode = fst result + when (andCache && isSuccess exitCode) <| do + storePath <- Dir.canonicalizePath (nixdir outname out) + cacheStorePath loud namespace storePath + pure exitCode + +cacheStorePath :: Bool -> Namespace -> FilePath -> IO () +cacheStorePath loud ns storePath = do + mKeyPath <- Env.lookupEnv "NIX_CACHE_KEY" + case mKeyPath of + Nothing -> Log.warn ["cache", "NIX_CACHE_KEY not set, skipping"] + Just keyPath -> do + let s3Url = "s3://omni-nix-cache?profile=digitalocean&scheme=https&endpoint=nyc3.digitaloceanspaces.com" + LogC.updateLine ns "signing..." + (signExit, _, signErr) <- + Process.readProcessWithExitCode + "nix" + ["store", "sign", "--key-file", keyPath, storePath] + "" + case signExit of + Exit.ExitSuccess -> do + LogC.updateLine ns "pushing to cache..." + (pushExit, _, pushErr) <- + Process.readProcessWithExitCode + "nix" + ["copy", "--to", s3Url, storePath] + "" + case pushExit of + Exit.ExitSuccess -> do + loud ?| Log.good ["cache", "pushed", Text.pack storePath] + Text.IO.putStrLn <| "STORE_PATH=" <> Text.pack storePath + Exit.ExitFailure _ -> do + Log.fail ["cache", "push failed", Text.pack storePath] + loud ?| putStrLn pushErr + Exit.ExitFailure _ -> do + Log.fail ["cache", "sign failed", Text.pack storePath] + loud ?| putStrLn signErr + +pipelineBuild :: Bool -> Bool -> Bool -> Int -> Int -> Int -> [Namespace] -> (Namespace -> IO (Maybe Target)) -> IO [Exit.ExitCode] +pipelineBuild andTest loud andCache analysisWorkers buildWorkers cpus namespaces analyzeFn = do root <- getCoderoot coord <- initCoordinator root namespaces let spawnAnalysis = replicateM analysisWorkers (Async.async (pipelineAnalysisWorker coord analyzeFn)) - let spawnBuild = replicateM buildWorkers (Async.async (pipelineBuildWorker andTest loud buildWorkers cpus coord)) + let spawnBuild = replicateM buildWorkers (Async.async (pipelineBuildWorker andTest loud andCache buildWorkers cpus coord)) threads <- (<>) spawnBuild let waitLoop = do remaining <- readTVarIO (coRemaining coord) -- cgit v1.2.3