module Propellor.Property.Apache where
import Propellor.Base
import qualified Propellor.Property.File as File
import qualified Propellor.Property.Apt as Apt
import qualified Propellor.Property.Service as Service
import qualified Propellor.Property.LetsEncrypt as LetsEncrypt
installed :: Property NoInfo
installed = Apt.installed ["apache2"]
restarted :: Property NoInfo
restarted = Service.restarted "apache2"
reloaded :: Property NoInfo
reloaded = Service.reloaded "apache2"
type ConfigLine = String
type ConfigFile = [ConfigLine]
siteEnabled :: Domain -> ConfigFile -> RevertableProperty NoInfo
siteEnabled domain cf = siteEnabled' domain cf <!> siteDisabled domain
siteEnabled' :: Domain -> ConfigFile -> Property NoInfo
siteEnabled' domain cf = combineProperties ("apache site enabled " ++ domain)
[ siteAvailable domain cf
`requires` installed
`onChange` reloaded
, check (not <$> isenabled)
(cmdProperty "a2ensite" ["--quiet", domain])
`requires` installed
`onChange` reloaded
]
where
isenabled = boolSystem "a2query" [Param "-q", Param "-s", Param domain]
siteDisabled :: Domain -> Property NoInfo
siteDisabled domain = combineProperties
("apache site disabled " ++ domain)
(map File.notPresent (siteCfg domain))
`onChange` (cmdProperty "a2dissite" ["--quiet", domain] `assume` MadeChange)
`requires` installed
`onChange` reloaded
siteAvailable :: Domain -> ConfigFile -> Property NoInfo
siteAvailable domain cf = combineProperties ("apache site available " ++ domain) $
map (`File.hasContent` (comment:cf)) (siteCfg domain)
where
comment = "# deployed with propellor, do not modify"
modEnabled :: String -> RevertableProperty NoInfo
modEnabled modname = enable <!> disable
where
enable = check (not <$> isenabled)
(cmdProperty "a2enmod" ["--quiet", modname])
`describe` ("apache module enabled " ++ modname)
`requires` installed
`onChange` reloaded
disable = check isenabled
(cmdProperty "a2dismod" ["--quiet", modname])
`describe` ("apache module disabled " ++ modname)
`requires` installed
`onChange` reloaded
isenabled = boolSystem "a2query" [Param "-q", Param "-m", Param modname]
listenPorts :: [Port] -> Property NoInfo
listenPorts ps = "/etc/apache2/ports.conf" `File.hasContent` map portline ps
`onChange` restarted
where
portline (Port n) = "Listen " ++ show n
siteCfg :: Domain -> [FilePath]
siteCfg domain =
[ "/etc/apache2/sites-available/" ++ domain
, "/etc/apache2/sites-available/" ++ domain ++ ".conf"
]
multiSSL :: Property NoInfo
multiSSL = check (doesDirectoryExist "/etc/apache2/conf.d") $
"/etc/apache2/conf.d/ssl" `File.hasContent`
[ "NameVirtualHost *:443"
, "SSLStrictSNIVHostCheck off"
]
`describe` "apache SNI enabled"
`onChange` reloaded
allowAll :: ConfigLine
allowAll = unlines
[ "<IfVersion < 2.4>"
, "Order allow,deny"
, "allow from all"
, "</IfVersion>"
, "<IfVersion >= 2.4>"
, "Require all granted"
, "</IfVersion>"
]
iconDir :: ConfigLine
iconDir = unlines
[ "<Directory \"/usr/share/apache2/icons\">"
, "Options Indexes MultiViews"
, "AllowOverride None"
, allowAll
, " </Directory>"
]
type WebRoot = FilePath
virtualHost :: Domain -> Port -> WebRoot -> RevertableProperty NoInfo
virtualHost domain (Port p) docroot = virtualHost' domain (Port p) docroot []
virtualHost' :: Domain -> Port -> WebRoot -> [ConfigLine] -> RevertableProperty NoInfo
virtualHost' domain (Port p) docroot addedcfg = siteEnabled domain $
[ "<VirtualHost *:"++show p++">"
, "ServerName "++domain++":"++show p
, "DocumentRoot " ++ docroot
, "ErrorLog /var/log/apache2/error.log"
, "LogLevel warn"
, "CustomLog /var/log/apache2/access.log combined"
, "ServerSignature On"
]
++ addedcfg ++
[ "</VirtualHost>"
]
httpsVirtualHost :: Domain -> WebRoot -> LetsEncrypt.AgreeTOS -> RevertableProperty NoInfo
httpsVirtualHost domain docroot letos = httpsVirtualHost' domain docroot letos []
httpsVirtualHost' :: Domain -> WebRoot -> LetsEncrypt.AgreeTOS -> [ConfigLine] -> RevertableProperty NoInfo
httpsVirtualHost' domain docroot letos addedcfg = setup <!> teardown
where
setup = setuphttp
`requires` modEnabled "rewrite"
`requires` modEnabled "ssl"
`before` setuphttps
teardown = siteDisabled domain
setuphttp = siteEnabled' domain $
("IncludeOptional " ++ sslconffile "*")
: vhost (Port 80)
[ "RewriteEngine On"
, "RewriteRule ^/.well-known/(.*) - [L]"
, "RewriteRule ^/(.*) https://" ++ domain ++ "/$1 [L,R,NE]"
]
setuphttps = LetsEncrypt.letsEncrypt letos domain docroot
`onChange` combineProperties (domain ++ " ssl cert installed")
[ File.dirExists (takeDirectory cf)
, File.hasContent cf sslvhost
`onChange` reloaded
, reloaded
]
where
cf = sslconffile "letsencrypt"
sslvhost = vhost (Port 443)
[ "SSLEngine on"
, "SSLCertificateFile " ++ LetsEncrypt.certFile domain
, "SSLCertificateKeyFile " ++ LetsEncrypt.privKeyFile domain
, "SSLCertificateChainFile " ++ LetsEncrypt.chainFile domain
]
sslconffile s = "/etc/apache2/sites-available/ssl/" ++ domain ++ "/" ++ s ++ ".conf"
vhost (Port p) ls =
[ "<VirtualHost *:"++show p++">"
, "ServerName "++domain++":"++show p
, "DocumentRoot " ++ docroot
, "ErrorLog /var/log/apache2/error.log"
, "LogLevel warn"
, "CustomLog /var/log/apache2/access.log combined"
, "ServerSignature On"
] ++ ls ++ addedcfg ++
[ "</VirtualHost>"
]