module Propellor.Property.Network where
import Propellor.Base
import Propellor.Property.File
import Data.Char
type Interface = String
type InterfaceOptions = [(String, String)]
ifUp :: Interface -> Property DebianLike
ifUp iface = tightenTargets $ cmdProperty "ifup" [iface]
`assume` MadeChange
cleanInterfacesFile :: Property DebianLike
cleanInterfacesFile = interfaceFileContains interfacesFile
[ "source-directory interfaces.d"
, ""
, "# The loopback network interface"
, "auto lo"
, "iface lo inet loopback"
]
[]
`describe` ("clean " ++ interfacesFile)
dhcp :: Interface -> Property DebianLike
dhcp iface = dhcp' iface mempty
dhcp' :: Interface -> InterfaceOptions -> Property DebianLike
dhcp' iface options = interfaceFileContains (interfaceDFile iface)
[ "auto " ++ iface
, "iface " ++ iface ++ " inet dhcp"
] options
`describe` ("dhcp " ++ iface)
`requires` interfacesDEnabled
newtype Gateway = Gateway IPAddr
static :: Interface -> IPAddr -> Maybe Gateway -> Property DebianLike
static iface addr gateway = static' iface addr gateway mempty
static' :: Interface -> IPAddr -> Maybe Gateway -> InterfaceOptions -> Property DebianLike
static' iface addr gateway options =
interfaceFileContains (interfaceDFile iface) headerlines options'
`describe` ("static IP address for " ++ iface)
`requires` interfacesDEnabled
where
headerlines =
[ "auto " ++ iface
, "iface " ++ iface ++ " " ++ inet ++ " static"
]
options' = catMaybes
[ Just $ ("address", val addr)
, case gateway of
Just (Gateway gaddr) ->
Just ("gateway", val gaddr)
Nothing -> Nothing
] ++ options
inet = case addr of
IPv4 _ -> "inet"
IPv6 _ -> "inet6"
preserveStatic :: Interface -> Property DebianLike
preserveStatic iface = tightenTargets $
check (not <$> doesFileExist f) setup
`describe` desc
`requires` interfacesDEnabled
where
f = interfaceDFile iface
desc = "static " ++ iface
setup :: Property DebianLike
setup = property' desc $ \o -> do
ls <- liftIO $ lines <$> readProcess "ip"
["-o", "addr", "show", iface, "scope", "global"]
stanzas <- liftIO $ concat <$> mapM mkstanza ls
ensureProperty o $ hasContent f $ ("auto " ++ iface) : stanzas
mkstanza ipline = case words ipline of
(_:iface':"inet":addr:_) | iface' == iface -> do
gw <- getgateway
return $ catMaybes
[ Just $ "iface " ++ iface ++ " inet static"
, Just $ "\taddress " ++ addr
, ("\tgateway " ++) <$> gw
]
_ -> return []
getgateway = do
rs <- lines <$> readProcess "ip"
["route", "show", "scope", "global", "dev", iface]
return $ case words <$> headMaybe rs of
Just ("default":"via":gw:_) -> Just gw
_ -> Nothing
ipv6to4 :: Property DebianLike
ipv6to4 = tightenTargets $ interfaceFileContains (interfaceDFile "sit0")
[ "auto sit0"
, "iface sit0 inet6 static"
]
[ ("address", "2002:5044:5531::1")
, ("netmask", "64")
, ("gateway", "::192.88.99.1")
]
`describe` "ipv6to4"
`requires` interfacesDEnabled
`onChange` ifUp "sit0"
interfacesFile :: FilePath
interfacesFile = "/etc/network/interfaces"
interfaceDFile :: Interface -> FilePath
interfaceDFile i = "/etc/network/interfaces.d" </> escapeInterfaceDName i
escapeInterfaceDName :: Interface -> FilePath
escapeInterfaceDName = filter (\c -> isAscii c && (isAlphaNum c || c `elem` "_-"))
interfacesDEnabled :: Property DebianLike
interfacesDEnabled = tightenTargets $
containsLine interfacesFile "source-directory interfaces.d"
`describe` "interfaces.d directory enabled"
interfaceFileContains :: FilePath -> [String] -> InterfaceOptions -> Property DebianLike
interfaceFileContains f headerlines options = tightenTargets $ hasContent f $
warning : headerlines ++ map fmt options
where
fmt (k, v) = "\t" ++ k ++ " " ++ v
warning = "# Deployed by propellor, do not edit."