From 9172b796122bf9558873ad4a2356d4f9d817d3e2 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Mon, 31 Mar 2014 15:40:16 -0400 Subject: propellor spin --- Propellor/CmdLine.hs | 62 ++++++++++++++++++++++++++++++++++++++-------------- README | 29 +++++++++++++++--------- 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/Propellor/CmdLine.hs b/Propellor/CmdLine.hs index ef825d92..7b82d281 100644 --- a/Propellor/CmdLine.hs +++ b/Propellor/CmdLine.hs @@ -70,38 +70,47 @@ spin host = do url <- getUrl void $ gitCommit [Param "--allow-empty", Param "-a", Param "-m", Param "propellor spin"] void $ boolSystem "git" [Param "push"] - privdata <- gpgDecrypt (privDataFile host) - withBothHandles createProcessSuccess (proc "ssh" [user, bootstrapcmd url]) $ \(toh, fromh) -> do + go url =<< gpgDecrypt (privDataFile host) + where + go url privdata = withBothHandles createProcessSuccess (proc "ssh" [user, bootstrapcmd]) $ \(toh, fromh) -> do + let finish = do + senddata toh (privDataFile host) privDataMarker privdata + hClose toh + + -- Display remaining output. + void $ tryIO $ forever $ + showremote =<< hGetLine fromh + hClose fromh status <- getstatus fromh `catchIO` error "protocol error" case status of + HaveKeyRing -> finish NeedKeyRing -> do d <- w82s . BL.unpack . B64.encode <$> BL.readFile keyring senddata toh keyring keyringMarker d - HaveKeyRing -> noop - senddata toh (privDataFile host) privDataMarker privdata - hClose toh - - -- Display remaining output. - void $ tryIO $ forever $ - showremote =<< hGetLine fromh - hClose fromh - - where + finish + NeedGitClone -> do + hClose toh + hClose fromh + sendGitClone host url + go url privdata + user = "root@"++host - bootstrapcmd url = shellWrap $ intercalate " && " + + bootstrapcmd = shellWrap $ intercalate " && " [ intercalate " ; " [ "if [ ! -d " ++ localdir ++ " ]" , "then " ++ intercalate " && " [ "apt-get -y install git" - , "git clone " ++ url ++ " " ++ localdir + , "echo " ++ toMarked statusMarker (show NeedGitClone) ] , "fi" ] , "cd " ++ localdir - , "make pull build" + , "make build" , "./propellor --boot " ++ host ] + getstatus :: Handle -> IO BootStrapStatus getstatus h = do l <- hGetLine h @@ -110,6 +119,7 @@ spin host = do showremote l getstatus h Just status -> return status + showremote s = putStrLn s senddata toh f marker s = do putStr $ "Sending " ++ f ++ " (" ++ show (length s) ++ " bytes) to " ++ host ++ "..." @@ -118,7 +128,27 @@ spin host = do hFlush toh putStrLn "done" -data BootStrapStatus = HaveKeyRing | NeedKeyRing +sendGitClone :: HostName -> String -> IO () +sendGitClone host url = do + putStrLn $ "Pushing git repository to " ++ host + withTmpFile "gitbundle" $ \tmp _ -> do + -- TODO: ssh connection caching, or better push method + -- with less connections. + void $ boolSystem "git" [Param "bundle", Param "create", File tmp, Param "HEAD"] + void $ boolSystem "scp" [File tmp, Param ("root@"++host++":"++remotebundle)] + void $ boolSystem "ssh" [Param ("root@"++host), Param unpackcmd] + where + remotebundle = "/usr/local/propellor.git" + unpackcmd = shellWrap $ intercalate " && " + [ "git clone " ++ remotebundle ++ " " ++ localdir + , "cd " ++ localdir + , "git checkout -b master" + , "git remote rm origin" + , "git remote add origin " ++ url + , "rm -f " ++ remotebundle + ] + +data BootStrapStatus = HaveKeyRing | NeedKeyRing | NeedGitClone deriving (Read, Show, Eq) type Marker = String diff --git a/README b/README index c46131b8..ce9769c0 100644 --- a/README +++ b/README @@ -6,10 +6,13 @@ properties, taking action as necessary when a property is not yet met. The design is intentionally very minimal. -Propellor lives in a git repository, and so to set it up it's cloned -to a system, and "make" can be used to pull down any new changes, -and compile and run propellor. This can be done by a cron job, or -a local propellor on your laptop can ssh in and run it. +Propellor lives in a git repository. You'll typically want to have +the repository checked out on a laptop, in order to make changes and push +them out to hosts. Each host will also have a clone of the repository, +and in that clone "make" can be used to build and run propellor. +This can be done by a cron job (which propellor can set up), +or a remote host can be triggered to update by running propellor +on your laptop: propellor --spin $host Properties are defined using Haskell. Edit config.hs to get started. @@ -26,9 +29,15 @@ and so it's easy to factor out things like classes of hosts as desired. ## bootstrapping and private data To bootstrap propellor on a new host, use: propellor --spin $host -This looks up the git repository's remote.origin.url (or remote.deploy.url -if available) and logs into the host, clones the url (if not already -done), and sets up and runs propellor in /usr/local/propellor + +That clones the local git repository to the remote host (securely over ssh +and without needing any central server!), if it doesn't already have +a clone. + +The repository on the remote host will have its origin set to the local git +repository's remote.origin.url (or remote.deploy.url if available). +This way, when propellor is run on the remote host, it can contact +whatever central git repository you're using. Private data such as passwords, ssh private keys, etc should not be checked into a propellor git repository in the clear, unless you want to restrict @@ -43,10 +52,10 @@ for available fields. ## using git://... securely -It's often easiest to deploy propellor to a host by cloning a git:// or -http:// repository rather than by cloning over ssh://. To avoid a MITM +It's often easiest for a remote host to use a git:// or http:// +url to its origin repository, rather than ssh://. So, to avoid a MITM attack, propellor checks that the top commit in the git repository is gpg -signed by a trusted gpg key, and refuses to deploy it otherwise. +signed by a trusted gpg key, and refuses to deploy it otherwise. This is only done when privdata/keyring.gpg exists. To set it up: -- cgit v1.2.3