2016-02-14 18:10:21 +09:00
|
|
|
{- This file is part of Vervis.
|
|
|
|
-
|
2019-01-15 07:03:49 +09:00
|
|
|
- Written in 2016, 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
|
2016-02-14 18:10:21 +09:00
|
|
|
-
|
|
|
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
|
|
|
-
|
|
|
|
- The author(s) have dedicated all copyright and related and neighboring
|
|
|
|
- rights to this software to the public domain worldwide. This software is
|
|
|
|
- distributed without any warranty.
|
|
|
|
-
|
|
|
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
|
|
|
- with this software. If not, see
|
|
|
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
|
|
-}
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
2016-02-14 18:10:21 +09:00
|
|
|
|
2016-02-23 17:45:03 +09:00
|
|
|
module Vervis.Application
|
2016-02-13 12:35:30 +09:00
|
|
|
( getApplicationDev
|
|
|
|
, appMain
|
|
|
|
, develMain
|
|
|
|
, makeFoundation
|
|
|
|
, makeLogWare
|
|
|
|
-- * for DevelMain
|
|
|
|
, getApplicationRepl
|
|
|
|
, shutdownApp
|
|
|
|
-- * for GHCI
|
|
|
|
, handler
|
|
|
|
, db
|
2016-02-14 18:10:21 +09:00
|
|
|
)
|
|
|
|
where
|
2016-02-13 12:35:30 +09:00
|
|
|
|
2018-04-01 04:22:37 +09:00
|
|
|
import Control.Monad.Logger (liftLoc, runLoggingT, logInfo, logError)
|
2016-02-13 12:35:30 +09:00
|
|
|
import Database.Persist.Postgresql (createPostgresqlPool, pgConnStr,
|
|
|
|
pgPoolSize, runSqlPool)
|
2018-05-26 19:27:05 +09:00
|
|
|
import Graphics.SVGFonts.Fonts (lin2)
|
|
|
|
import Graphics.SVGFonts.ReadFont (loadFont)
|
2016-02-23 17:45:03 +09:00
|
|
|
import Vervis.Import
|
2016-02-13 12:35:30 +09:00
|
|
|
import Language.Haskell.TH.Syntax (qLocation)
|
2019-01-19 10:44:21 +09:00
|
|
|
import Network.HTTP.Client (newManager)
|
|
|
|
import Network.HTTP.Client.TLS (tlsManagerSettings)
|
2016-02-13 12:35:30 +09:00
|
|
|
import Network.Wai (Middleware)
|
|
|
|
import Network.Wai.Handler.Warp (Settings, defaultSettings,
|
|
|
|
defaultShouldDisplayException,
|
|
|
|
runSettings, setHost,
|
|
|
|
setOnException, setPort, getPort)
|
|
|
|
import Network.Wai.Middleware.RequestLogger (Destination (Logger),
|
|
|
|
IPAddrSource (..),
|
|
|
|
OutputFormat (..), destination,
|
|
|
|
mkRequestLogger, outputFormat)
|
|
|
|
import System.Log.FastLogger (defaultBufSize, newStdoutLoggerSet,
|
|
|
|
toLogStr)
|
2016-03-10 07:27:25 +09:00
|
|
|
import Yesod.Default.Main (LogFunc)
|
2018-03-04 06:33:59 +09:00
|
|
|
import Yesod.Mail.Send (runMailer)
|
2016-02-13 12:35:30 +09:00
|
|
|
|
2018-04-05 09:03:27 +09:00
|
|
|
import qualified Data.Text as T (unpack)
|
2019-03-03 04:13:51 +09:00
|
|
|
import qualified Data.HashMap.Strict as M (empty)
|
2018-04-05 09:03:27 +09:00
|
|
|
|
2019-02-08 12:13:56 +09:00
|
|
|
import Database.Persist.Schema.PostgreSQL (schemaBackend)
|
|
|
|
|
2019-04-16 23:27:50 +09:00
|
|
|
import Control.Concurrent.ResultShare
|
2019-03-11 11:01:41 +09:00
|
|
|
import Data.KeyFile
|
2019-04-18 19:38:01 +09:00
|
|
|
import Yesod.MonadSite
|
2019-04-16 23:27:50 +09:00
|
|
|
|
2019-04-18 19:38:01 +09:00
|
|
|
import Control.Concurrent.Local
|
2019-02-09 06:54:22 +09:00
|
|
|
import Web.Hashids.Local
|
|
|
|
|
2019-02-08 12:13:56 +09:00
|
|
|
import Vervis.ActorKey (generateActorKey, actorKeyRotator)
|
2019-04-16 23:27:50 +09:00
|
|
|
import Vervis.Federation
|
2019-02-09 06:54:22 +09:00
|
|
|
import Vervis.KeyFile (isInitialSetup)
|
2019-04-16 23:27:50 +09:00
|
|
|
import Vervis.RemoteActorStore
|
2019-01-15 07:03:49 +09:00
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
-- Import all relevant handler modules here.
|
|
|
|
-- Don't forget to add new modules to your cabal file!
|
2016-02-23 17:45:03 +09:00
|
|
|
import Vervis.Handler.Common
|
2016-04-21 09:32:22 +09:00
|
|
|
import Vervis.Handler.Git
|
2016-05-25 06:48:21 +09:00
|
|
|
import Vervis.Handler.Group
|
2016-02-23 17:45:03 +09:00
|
|
|
import Vervis.Handler.Home
|
2019-01-19 10:44:21 +09:00
|
|
|
import Vervis.Handler.Inbox
|
2016-03-07 09:42:06 +09:00
|
|
|
import Vervis.Handler.Key
|
2016-02-23 17:45:03 +09:00
|
|
|
import Vervis.Handler.Person
|
|
|
|
import Vervis.Handler.Project
|
2016-02-27 14:41:36 +09:00
|
|
|
import Vervis.Handler.Repo
|
2016-05-29 22:17:55 +09:00
|
|
|
import Vervis.Handler.Role
|
2016-05-25 06:48:21 +09:00
|
|
|
import Vervis.Handler.Sharer
|
2016-05-01 07:32:22 +09:00
|
|
|
import Vervis.Handler.Ticket
|
2016-06-04 15:57:54 +09:00
|
|
|
import Vervis.Handler.Wiki
|
2016-08-08 20:05:19 +09:00
|
|
|
import Vervis.Handler.Workflow
|
2016-02-13 12:35:30 +09:00
|
|
|
|
2016-09-01 01:51:02 +09:00
|
|
|
import Vervis.Migration (migrateDB)
|
2016-03-06 20:58:48 +09:00
|
|
|
import Vervis.Ssh (runSsh)
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
-- This line actually creates our YesodDispatch instance. It is the second half
|
|
|
|
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
|
|
|
|
-- comments there for more details.
|
|
|
|
mkYesodDispatch "App" resourcesApp
|
|
|
|
|
2016-03-10 07:27:25 +09:00
|
|
|
loggingFunction :: App -> LogFunc
|
|
|
|
loggingFunction app = messageLoggerSource app (appLogger app)
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
-- | This function allocates resources (such as a database connection pool),
|
|
|
|
-- performs initialization and returns a foundation datatype value. This is also
|
|
|
|
-- the place to put your migrate statements to have automatic database
|
|
|
|
-- migrations handled by Yesod.
|
|
|
|
makeFoundation :: AppSettings -> IO App
|
|
|
|
makeFoundation appSettings = do
|
|
|
|
-- Some basic initializations: HTTP connection manager, logger, and static
|
|
|
|
-- subsite.
|
2019-01-19 10:44:21 +09:00
|
|
|
appHttpManager <- newManager tlsManagerSettings
|
2016-02-13 12:35:30 +09:00
|
|
|
appLogger <- newStdoutLoggerSet defaultBufSize >>= makeYesodLogger
|
|
|
|
appStatic <-
|
|
|
|
(if appMutableStatic appSettings then staticDevel else static)
|
|
|
|
(appStaticDir appSettings)
|
|
|
|
|
2018-02-25 18:28:55 +09:00
|
|
|
appMailQueue <-
|
2018-03-04 06:33:59 +09:00
|
|
|
case appMail appSettings of
|
2018-02-25 18:28:55 +09:00
|
|
|
Nothing -> return Nothing
|
|
|
|
Just _ -> Just <$> newChan
|
|
|
|
|
2018-05-26 19:27:05 +09:00
|
|
|
appSvgFont <-
|
|
|
|
if appLoadFontFromLibData appSettings
|
2018-12-05 12:41:19 +09:00
|
|
|
then lin2
|
2018-05-26 19:27:05 +09:00
|
|
|
else loadFont "data/LinLibertineCut.svg"
|
|
|
|
|
2019-02-07 19:34:33 +09:00
|
|
|
appActorKeys <-
|
|
|
|
newTVarIO =<<
|
|
|
|
(,,) <$> generateActorKey <*> generateActorKey <*> pure True
|
2019-01-15 07:08:44 +09:00
|
|
|
|
2019-03-10 00:40:02 +09:00
|
|
|
appInstanceMutex <- newInstanceMutex
|
2019-03-03 04:13:51 +09:00
|
|
|
|
2019-04-18 19:38:01 +09:00
|
|
|
appActorFetchShare <- newResultShare actorFetchShareAction
|
2019-04-16 23:27:50 +09:00
|
|
|
|
2019-04-26 07:46:27 +09:00
|
|
|
appActivities <-
|
|
|
|
case appInboxDebugReportLength appSettings of
|
|
|
|
Nothing -> return Nothing
|
|
|
|
Just n -> Just . (n,) <$> newTVarIO mempty
|
2019-01-19 10:44:21 +09:00
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
-- We need a log function to create a connection pool. We need a connection
|
|
|
|
-- pool to create our foundation. And we need our foundation to get a
|
|
|
|
-- logging function. To get out of this loop, we initially create a
|
|
|
|
-- temporary foundation without a real connection pool, get a log function
|
|
|
|
-- from there, and then create the real foundation.
|
2019-02-09 06:54:22 +09:00
|
|
|
let mkFoundation
|
|
|
|
appConnPool
|
|
|
|
appCapSignKey
|
2019-03-29 12:25:32 +09:00
|
|
|
appHashidsContext =
|
2019-02-09 06:54:22 +09:00
|
|
|
App {..}
|
2016-02-13 12:35:30 +09:00
|
|
|
-- The App {..} syntax is an example of record wild cards. For more
|
|
|
|
-- information, see:
|
|
|
|
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
|
2019-02-08 12:13:56 +09:00
|
|
|
tempFoundation =
|
|
|
|
mkFoundation
|
|
|
|
(error "connPool forced in tempFoundation")
|
|
|
|
(error "capSignKey forced in tempFoundation")
|
2019-03-29 12:25:32 +09:00
|
|
|
(error "hashidsContext forced in tempFoundation")
|
2016-03-10 07:27:25 +09:00
|
|
|
logFunc = loggingFunction tempFoundation
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
-- Create the database connection pool
|
|
|
|
pool <- flip runLoggingT logFunc $ createPostgresqlPool
|
|
|
|
(pgConnStr $ appDatabaseConf appSettings)
|
|
|
|
(pgPoolSize $ appDatabaseConf appSettings)
|
|
|
|
|
2019-02-08 12:13:56 +09:00
|
|
|
setup <- isInitialSetup pool schemaBackend
|
2019-03-11 11:01:41 +09:00
|
|
|
loadMode <- determineKeyFileLoadMode setup
|
|
|
|
|
|
|
|
capSignKey <- loadKeyFile loadMode $ appCapabilitySigningKeyFile appSettings
|
|
|
|
hashidsSalt <- loadKeyFile loadMode $ appHashidsSaltFile appSettings
|
2019-02-09 06:54:22 +09:00
|
|
|
let hashidsCtx = hashidsContext hashidsSalt
|
2019-02-08 12:13:56 +09:00
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
-- Perform database migration using our application's logging settings.
|
2016-09-01 01:51:02 +09:00
|
|
|
--runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc
|
2018-04-01 04:22:37 +09:00
|
|
|
flip runLoggingT logFunc $
|
|
|
|
flip runSqlPool pool $ do
|
2019-05-25 21:44:09 +09:00
|
|
|
let hLocal = appInstanceHost appSettings
|
|
|
|
r <- migrateDB hLocal hashidsCtx
|
2018-04-01 04:22:37 +09:00
|
|
|
case r of
|
|
|
|
Left err -> do
|
2018-04-05 09:03:27 +09:00
|
|
|
let msg = "DB migration failed: " <> err
|
2018-04-01 04:22:37 +09:00
|
|
|
$logError msg
|
2018-04-05 09:03:27 +09:00
|
|
|
error $ T.unpack msg
|
2019-04-16 23:27:50 +09:00
|
|
|
Right (_from, _to) -> do
|
|
|
|
$logInfo "DB migration success"
|
|
|
|
fixRunningDeliveries
|
|
|
|
deleteUnusedURAs
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
-- Return the foundation
|
2019-03-29 12:25:32 +09:00
|
|
|
return $ mkFoundation pool capSignKey hashidsCtx
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
|
|
|
-- applying some additional middlewares.
|
|
|
|
makeApplication :: App -> IO Application
|
|
|
|
makeApplication foundation = do
|
|
|
|
logWare <- makeLogWare foundation
|
|
|
|
-- Create the WAI application and apply middlewares
|
|
|
|
appPlain <- toWaiAppPlain foundation
|
|
|
|
return $ logWare $ defaultMiddlewaresNoLogging appPlain
|
|
|
|
|
|
|
|
makeLogWare :: App -> IO Middleware
|
|
|
|
makeLogWare foundation =
|
|
|
|
mkRequestLogger def
|
|
|
|
{ outputFormat =
|
|
|
|
if appDetailedRequestLogging $ appSettings foundation
|
|
|
|
then Detailed True
|
|
|
|
else Apache
|
|
|
|
(if appIpFromHeader $ appSettings foundation
|
|
|
|
then FromFallback
|
|
|
|
else FromSocket)
|
|
|
|
, destination = Logger $ loggerSet $ appLogger foundation
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
-- | Warp settings for the given foundation value.
|
|
|
|
warpSettings :: App -> Settings
|
|
|
|
warpSettings foundation =
|
|
|
|
setPort (appPort $ appSettings foundation)
|
|
|
|
$ setHost (appHost $ appSettings foundation)
|
|
|
|
$ setOnException (\_req e ->
|
2016-03-10 07:27:25 +09:00
|
|
|
when (defaultShouldDisplayException e) $ loggingFunction
|
2016-02-13 12:35:30 +09:00
|
|
|
foundation
|
|
|
|
$(qLocation >>= liftLoc)
|
|
|
|
"yesod"
|
|
|
|
LevelError
|
|
|
|
(toLogStr $ "Exception from Warp: " ++ show e))
|
|
|
|
defaultSettings
|
|
|
|
|
|
|
|
-- | For yesod devel, return the Warp settings and WAI Application.
|
|
|
|
getApplicationDev :: IO (Settings, Application)
|
|
|
|
getApplicationDev = do
|
|
|
|
settings <- getAppSettings
|
|
|
|
foundation <- makeFoundation settings
|
|
|
|
wsettings <- getDevSettings $ warpSettings foundation
|
|
|
|
app <- makeApplication foundation
|
|
|
|
return (wsettings, app)
|
|
|
|
|
|
|
|
getAppSettings :: IO AppSettings
|
2019-05-25 00:45:36 +09:00
|
|
|
getAppSettings = loadYamlSettings [configSettingsYml] [] useEnv
|
2016-02-13 12:35:30 +09:00
|
|
|
|
|
|
|
-- | main function for use by yesod devel
|
|
|
|
develMain :: IO ()
|
|
|
|
develMain = develMainHelper getApplicationDev
|
|
|
|
|
2019-01-15 07:08:44 +09:00
|
|
|
actorKeyPeriodicRotator :: App -> IO ()
|
|
|
|
actorKeyPeriodicRotator app =
|
2019-02-07 19:34:33 +09:00
|
|
|
actorKeyRotator (appActorKeyRotation $ appSettings app) (appActorKeys app)
|
2019-01-15 07:08:44 +09:00
|
|
|
|
2019-04-18 19:38:01 +09:00
|
|
|
deliveryRunner :: App -> IO ()
|
|
|
|
deliveryRunner app =
|
|
|
|
let interval = appDeliveryRetryFreq $ appSettings app
|
|
|
|
in runWorker (periodically interval retryOutboxDelivery) app
|
|
|
|
|
2016-03-10 07:27:25 +09:00
|
|
|
sshServer :: App -> IO ()
|
|
|
|
sshServer foundation =
|
|
|
|
runSsh
|
|
|
|
(appSettings foundation)
|
|
|
|
(appConnPool foundation)
|
|
|
|
(loggingFunction foundation)
|
|
|
|
|
2018-02-25 18:28:55 +09:00
|
|
|
mailer :: App -> IO ()
|
|
|
|
mailer foundation =
|
2018-03-04 06:33:59 +09:00
|
|
|
case (appMail $ appSettings foundation, appMailQueue foundation) of
|
2018-02-25 18:28:55 +09:00
|
|
|
(Nothing , Nothing) -> return ()
|
|
|
|
(Nothing , Just _) -> error "Mail queue unnecessarily created"
|
|
|
|
(Just _ , Nothing) -> error "Mail queue wasn't created"
|
2018-03-04 06:33:59 +09:00
|
|
|
(Just mail, Just queue) ->
|
2018-02-25 18:28:55 +09:00
|
|
|
runMailer
|
2018-03-04 06:33:59 +09:00
|
|
|
mail
|
|
|
|
-- (appConnPool foundation)
|
2018-02-25 18:28:55 +09:00
|
|
|
(loggingFunction foundation)
|
|
|
|
(readChan queue)
|
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
-- | The @main@ function for an executable running this site.
|
|
|
|
appMain :: IO ()
|
|
|
|
appMain = do
|
|
|
|
-- Get the settings from all relevant sources
|
2019-05-25 00:45:36 +09:00
|
|
|
settings <- loadYamlSettingsArgs
|
2016-03-06 20:58:48 +09:00
|
|
|
-- Fall back to compile-time values, set to [] to require values at
|
|
|
|
-- runtime
|
2016-02-13 12:35:30 +09:00
|
|
|
[configSettingsYmlValue]
|
|
|
|
|
2016-03-06 20:58:48 +09:00
|
|
|
-- Allow environment variables to override
|
2016-02-13 12:35:30 +09:00
|
|
|
useEnv
|
|
|
|
|
|
|
|
-- Generate the foundation from the settings
|
|
|
|
foundation <- makeFoundation settings
|
|
|
|
|
|
|
|
-- Generate a WAI Application from the foundation
|
|
|
|
app <- makeApplication foundation
|
|
|
|
|
2019-01-15 07:08:44 +09:00
|
|
|
-- Run actor signature key periodic generation thread
|
|
|
|
forkCheck $ actorKeyPeriodicRotator foundation
|
|
|
|
|
2019-04-18 19:38:01 +09:00
|
|
|
-- Run periodic activity delivery retry runner
|
2019-04-19 04:50:31 +09:00
|
|
|
when (appFederation $ appSettings foundation) $
|
|
|
|
forkCheck $ deliveryRunner foundation
|
2019-04-18 19:38:01 +09:00
|
|
|
|
2018-02-25 18:28:55 +09:00
|
|
|
-- Run SSH server
|
2019-01-15 07:03:49 +09:00
|
|
|
forkCheck $ sshServer foundation
|
2016-03-06 20:58:48 +09:00
|
|
|
|
2018-02-25 18:28:55 +09:00
|
|
|
-- Run mailer if SMTP is enabled
|
2019-01-15 07:03:49 +09:00
|
|
|
forkCheck $ mailer foundation
|
2018-02-25 18:28:55 +09:00
|
|
|
|
2016-02-13 12:35:30 +09:00
|
|
|
-- Run the application with Warp
|
|
|
|
runSettings (warpSettings foundation) app
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------
|
|
|
|
-- Functions for DevelMain.hs (a way to run the app from GHCi)
|
|
|
|
--------------------------------------------------------------
|
|
|
|
getApplicationRepl :: IO (Int, App, Application)
|
|
|
|
getApplicationRepl = do
|
|
|
|
settings <- getAppSettings
|
|
|
|
foundation <- makeFoundation settings
|
|
|
|
wsettings <- getDevSettings $ warpSettings foundation
|
|
|
|
app1 <- makeApplication foundation
|
|
|
|
return (getPort wsettings, foundation, app1)
|
|
|
|
|
|
|
|
shutdownApp :: App -> IO ()
|
|
|
|
shutdownApp _ = return ()
|
|
|
|
|
|
|
|
|
|
|
|
---------------------------------------------
|
|
|
|
-- Functions for use in development with GHCi
|
|
|
|
---------------------------------------------
|
|
|
|
|
|
|
|
-- | Run a handler
|
|
|
|
handler :: Handler a -> IO a
|
|
|
|
handler h = getAppSettings >>= makeFoundation >>= flip unsafeHandler h
|
|
|
|
|
|
|
|
-- | Run DB queries
|
2019-05-23 18:12:24 +09:00
|
|
|
db :: ReaderT SqlBackend (HandlerFor App) a -> IO a
|
2016-02-13 12:35:30 +09:00
|
|
|
db = handler . runDB
|