Compare commits
14
Commits
05943c902e
..
v0.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5c11ed181 | ||
|
|
36c9b880a7 | ||
|
|
c25ead672b | ||
|
|
4a6e3b2454 | ||
|
|
b739294638 | ||
|
|
27f5b8e23c | ||
|
|
48eb27e218 | ||
|
|
94554ad6e4 | ||
|
|
1ccde9e5fc | ||
|
|
999c9e8c9e | ||
|
|
eff74cecb4 | ||
|
|
e9934276d5 | ||
|
|
72ffa08e18 | ||
|
|
2b85c0fa93 |
@@ -0,0 +1,27 @@
|
|||||||
|
name: build
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
container:
|
||||||
|
image: node:24-bookworm
|
||||||
|
env:
|
||||||
|
USER: node
|
||||||
|
HOME: /home/node
|
||||||
|
NIX_CONFIG: experimental-features = nix-command flakes
|
||||||
|
volumes:
|
||||||
|
- /var/lib/pbri/docker/nix_runner_nix:/nix
|
||||||
|
- /var/lib/pbri/docker/nix_runner_etc:/etc/nix
|
||||||
|
- /var/lib/pbri/docker/nix_runner_home_node:/home/node
|
||||||
|
options: --user 1000:1000
|
||||||
|
steps:
|
||||||
|
- name: Prep nix env
|
||||||
|
run: |
|
||||||
|
if [ ! -f ~/.nix-profile/etc/profile.d/nix.sh ]; then
|
||||||
|
curl -L https://nixos.org/nix/install | bash -s -- --no-daemon
|
||||||
|
fi
|
||||||
|
. ~/.nix-profile/etc/profile.d/nix.sh
|
||||||
|
env >> "$GITHUB_ENV"
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- run: nix build --log-format raw --print-build-logs .#yore
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
name: deliver
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
jobs:
|
||||||
|
deliver:
|
||||||
|
container:
|
||||||
|
image: node:24-bookworm
|
||||||
|
env:
|
||||||
|
USER: node
|
||||||
|
HOME: /home/node
|
||||||
|
NIX_CONFIG: experimental-features = nix-command flakes
|
||||||
|
volumes:
|
||||||
|
- /var/lib/pbri/docker/nix_runner_nix:/nix
|
||||||
|
- /var/lib/pbri/docker/nix_runner_etc:/etc/nix
|
||||||
|
- /var/lib/pbri/docker/nix_runner_home_node:/home/node
|
||||||
|
options: --user 1000:1000
|
||||||
|
steps:
|
||||||
|
- name: Prep nix env
|
||||||
|
run: |
|
||||||
|
if [ ! -f ~/.nix-profile/etc/profile.d/nix.sh ]; then
|
||||||
|
curl -L https://nixos.org/nix/install | bash -s -- --no-daemon
|
||||||
|
fi
|
||||||
|
. ~/.nix-profile/etc/profile.d/nix.sh
|
||||||
|
env >> "$GITHUB_ENV"
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Get image meta
|
||||||
|
run: nix build .#yore-meta -o /tmp/yore-meta
|
||||||
|
- name: Version check
|
||||||
|
run: |
|
||||||
|
VERSION=$(cat /tmp/yore-meta/version)
|
||||||
|
[ "$GITHUB_REF_NAME" = v"$VERSION" ]
|
||||||
|
- run: nix build --log-format raw --print-build-logs .#image -o image.tar.gz
|
||||||
|
- run: gunzip -c image.tar.gz > image.tar
|
||||||
|
- run: nix run .#crane -- auth login git.pbrinkmeier.de -u paul -p "$PASSWORD"
|
||||||
|
env:
|
||||||
|
PASSWORD: "${{ secrets.REGISTRY_PASSWORD }}"
|
||||||
|
- run: nix run .#crane -- push image.tar $(cat /tmp/yore-meta/image-tag)
|
||||||
@@ -25,7 +25,7 @@ You can also use it to run a database for development by choosing a `$COMMAND` t
|
|||||||
```
|
```
|
||||||
BASE_DIR=./pgdata scripts/with-db psql "dbname=yore-test port=5433 user=yore-test"
|
BASE_DIR=./pgdata scripts/with-db psql "dbname=yore-test port=5433 user=yore-test"
|
||||||
# cabal repl with env vars set
|
# cabal repl with env vars set
|
||||||
BASE_DIR=./pgdata scripts/with-db scripts/dev
|
BASE_DIR=./pgdata scripts/with-db cabal run
|
||||||
```
|
```
|
||||||
|
|
||||||
By setting `$BASE_DIR` you can persist the database for later runs.
|
By setting `$BASE_DIR` you can persist the database for later runs.
|
||||||
|
|||||||
+35
-13
@@ -17,10 +17,10 @@ import Control.Monad.Trans.Except (ExceptT (..), catchE, runExceptT)
|
|||||||
import Data.Bifunctor (Bifunctor (..))
|
import Data.Bifunctor (Bifunctor (..))
|
||||||
import Data.Proxy (Proxy (..))
|
import Data.Proxy (Proxy (..))
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Time (Day, LocalTime (..), TimeOfDay (..), ZonedTime (..), addDays, toGregorian)
|
import Data.Time (Day, LocalTime (..), TimeOfDay (..), ZonedTime (..), addDays, nominalDiffTimeToSeconds, toGregorian)
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Network.HTTP.Types (Status (..))
|
import Network.HTTP.Types (Status (..))
|
||||||
import Network.Wai (Request (..))
|
import Network.Wai (Middleware, Request (..), responseStatus)
|
||||||
import Servant
|
import Servant
|
||||||
( Capture
|
( Capture
|
||||||
, Get
|
, Get
|
||||||
@@ -60,9 +60,12 @@ import Yore.Scrape (Issue (..), getIssuesByDay)
|
|||||||
import Yore.Servant (GetSendfile, HTML, Sendfile (..))
|
import Yore.Servant (GetSendfile, HTML, Sendfile (..))
|
||||||
import Yore.Time (addYears, getCurrentDay, getSecondsUntilMidnight)
|
import Yore.Time (addYears, getCurrentDay, getSecondsUntilMidnight)
|
||||||
|
|
||||||
|
import Data.Fixed (Pico)
|
||||||
|
import Data.Time.Clock.POSIX (getPOSIXTime)
|
||||||
import qualified Envy
|
import qualified Envy
|
||||||
import qualified Yore.DB as DB
|
import qualified Yore.DB as DB
|
||||||
import qualified Yore.Log as Log
|
import qualified Yore.Log as Log
|
||||||
|
import System.IO (hSetBuffering, BufferMode (..), stdout)
|
||||||
|
|
||||||
data ConfigT f = Config
|
data ConfigT f = Config
|
||||||
{ yorePort :: f =@@ Int ? 3000
|
{ yorePort :: f =@@ Int ? 3000
|
||||||
@@ -77,10 +80,12 @@ deriving instance Show Config
|
|||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
|
hSetBuffering stdout NoBuffering
|
||||||
|
|
||||||
cfg <-
|
cfg <-
|
||||||
Envy.load @ConfigT >>= \case
|
Envy.load @ConfigT >>= \case
|
||||||
Left err -> do
|
Left errs -> do
|
||||||
Log.error $ printf "failed to read config: %s" err
|
forM_ errs $ Log.error . printf "failed to read config: %s"
|
||||||
exitFailure
|
exitFailure
|
||||||
Right c ->
|
Right c ->
|
||||||
pure c
|
pure c
|
||||||
@@ -112,24 +117,29 @@ doIndex cfg db =
|
|||||||
|
|
||||||
runServer :: Config -> DB -> IO ()
|
runServer :: Config -> DB -> IO ()
|
||||||
runServer cfg db =
|
runServer cfg db =
|
||||||
Warp.runSettings settings $ serve (Proxy @API) $ hoistServer (Proxy @API) nt $ server cfg db
|
Warp.runSettings settings $ loggerMiddleware $ serve (Proxy @API) $ hoistServer (Proxy @API) nt $ server cfg db
|
||||||
where
|
where
|
||||||
settings =
|
settings =
|
||||||
foldr
|
foldr
|
||||||
($)
|
($)
|
||||||
Warp.defaultSettings
|
Warp.defaultSettings
|
||||||
[ Warp.setLogger logger
|
[ Warp.setPort cfg.yorePort
|
||||||
, Warp.setPort cfg.yorePort
|
|
||||||
, Warp.setBeforeMainLoop $ Log.info $ printf "listening on port %d" cfg.yorePort
|
, Warp.setBeforeMainLoop $ Log.info $ printf "listening on port %d" cfg.yorePort
|
||||||
, Warp.setOnException onException
|
, Warp.setOnException onException
|
||||||
]
|
]
|
||||||
|
|
||||||
logger req status _ = do
|
logger :: Request -> Status -> Pico -> IO ()
|
||||||
Log.info $
|
logger req status s = do
|
||||||
printf
|
Log.info $ printf "%d %s %ss" (statusCode status) (requestLine req) (formatMetric s)
|
||||||
"%d %s"
|
|
||||||
(statusCode status)
|
loggerMiddleware :: Middleware
|
||||||
(requestLine req)
|
loggerMiddleware app req respond = do
|
||||||
|
begin <- getPOSIXTime
|
||||||
|
app req $ \res -> do
|
||||||
|
rr <- respond res
|
||||||
|
end <- getPOSIXTime
|
||||||
|
logger req (responseStatus res) (nominalDiffTimeToSeconds (end - begin))
|
||||||
|
pure rr
|
||||||
|
|
||||||
onException mbReq ex = do
|
onException mbReq ex = do
|
||||||
Log.error $ printf "unhandled exception%s: %s%s" (maybe "" ((" in " ++) . requestLine) mbReq) (show ex)
|
Log.error $ printf "unhandled exception%s: %s%s" (maybe "" ((" in " ++) . requestLine) mbReq) (show ex)
|
||||||
@@ -141,6 +151,18 @@ runServer cfg db =
|
|||||||
(BS8.unpack $ requestMethod req)
|
(BS8.unpack $ requestMethod req)
|
||||||
(BS8.unpack $ rawPathInfo req <> rawQueryString req)
|
(BS8.unpack $ rawPathInfo req <> rawQueryString req)
|
||||||
|
|
||||||
|
formatMetric :: Pico -> String
|
||||||
|
formatMetric x = go (-9) prefixes
|
||||||
|
where
|
||||||
|
prefixes = ["p", "n", "u", "m", "", "k", "M", "G"]
|
||||||
|
|
||||||
|
go :: Int -> [String] -> String
|
||||||
|
go _ [] = show x
|
||||||
|
go e (p : ps)
|
||||||
|
| x < 10 ^^ e =
|
||||||
|
show @Int (floor $ x * 10 ^^ (-e + 3)) ++ p
|
||||||
|
| otherwise = go (e + 3) ps
|
||||||
|
|
||||||
nt :: ExceptT Error IO a -> Handler a
|
nt :: ExceptT Error IO a -> Handler a
|
||||||
nt action = MkHandler $ do
|
nt action = MkHandler $ do
|
||||||
res <- runExceptT action
|
res <- runExceptT action
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
\restrict VcBcXJr0fe2fRf2im7J0PCentZxDeYMChdNcReN5LtHeQrg2nXyh5MelIBAiGsO
|
\restrict CAC30PXzQJosjH02Spia1vzj9Ui3LB1Cq2HI8r0A4eu33HP8ngxbbM2SzRfTj0c
|
||||||
|
|
||||||
-- Dumped from database version 17.6
|
-- Dumped from database version 17.6
|
||||||
-- Dumped by pg_dump version 17.6
|
-- Dumped by pg_dump version 17.6
|
||||||
@@ -129,7 +129,7 @@ ALTER TABLE ONLY yore.day_file
|
|||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|
||||||
\unrestrict VcBcXJr0fe2fRf2im7J0PCentZxDeYMChdNcReN5LtHeQrg2nXyh5MelIBAiGsO
|
\unrestrict CAC30PXzQJosjH02Spia1vzj9Ui3LB1Cq2HI8r0A4eu33HP8ngxbbM2SzRfTj0c
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|||||||
Generated
+4
-4
@@ -64,11 +64,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1756064767,
|
"lastModified": 1759790870,
|
||||||
"narHash": "sha256-SaJK9VYc/oF5aBSMSW+We8TggER4+uTAHvc2ztnLpYo=",
|
"narHash": "sha256-rEsaO38v2wvAThPzgbYPgnv29mu6GxTYLjgci3GYaO4=",
|
||||||
"ref": "main",
|
"ref": "main",
|
||||||
"rev": "af16429d827816cf9bec69026e13f50acc3d2691",
|
"rev": "4db509c1b52ee7b5012abfef5e11e62beab27a16",
|
||||||
"revCount": 65,
|
"revCount": 66,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.pbrinkmeier.de/paul/opium"
|
"url": "https://git.pbrinkmeier.de/paul/opium"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,11 +26,18 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# For building the package, we use only the files not ignored by Git as inputs.
|
# For building the package, we use only the files not ignored by Git as inputs.
|
||||||
# Also, flake.nix and flake.lock are not included to avoid rebuilds when
|
# Also, flake.nix, flake.lock and .gitea are not included to avoid rebuilds when
|
||||||
# working on them.
|
# working on them.
|
||||||
src = pkgs.lib.cleanSourceWith {
|
src = pkgs.lib.cleanSourceWith {
|
||||||
src = gitignore.lib.gitignoreSource ./.;
|
src = ./.;
|
||||||
filter = path: type: builtins.elem (builtins.baseNameOf path) [ "flake.nix" "flake.lock" ];
|
filter = gitignore.lib.gitignoreFilterWith {
|
||||||
|
basePath = ./.;
|
||||||
|
extraRules = ''
|
||||||
|
flake.nix
|
||||||
|
flake.lock
|
||||||
|
.gitea
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
yore = pkgs.lib.pipe
|
yore = pkgs.lib.pipe
|
||||||
@@ -51,16 +58,37 @@
|
|||||||
# Return only the bin folder to curb image size
|
# Return only the bin folder to curb image size
|
||||||
compose.justStaticExecutables
|
compose.justStaticExecutables
|
||||||
];
|
];
|
||||||
|
|
||||||
|
image =
|
||||||
|
pkgs.dockerTools.buildImage {
|
||||||
|
name = "git.pbrinkmeier.de/paul/yore";
|
||||||
|
tag = yore.version;
|
||||||
|
copyToRoot = [ pkgs.cacert ];
|
||||||
|
config.Cmd = [ "${entrypoint}/bin/run" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
entrypoint = pkgs.writeShellApplication {
|
||||||
|
name = "run";
|
||||||
|
runtimeInputs = [ pkgs.coreutils pkgs.dbmate yore ];
|
||||||
|
text = ''
|
||||||
|
mkdir -p /workspace/db
|
||||||
|
cp -r ${./db}/* /workspace/db
|
||||||
|
cd /workspace
|
||||||
|
dbmate up
|
||||||
|
|
||||||
|
yore
|
||||||
|
'';
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
packages = rec {
|
packages = rec {
|
||||||
docker =
|
|
||||||
pkgs.dockerTools.buildImage {
|
|
||||||
name = "git.pbrinkmeier.de/paul/yore";
|
|
||||||
tag = yore.version;
|
|
||||||
config.Cmd = [ "${yore}/bin/yore" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
default = yore;
|
default = yore;
|
||||||
|
inherit yore image;
|
||||||
|
yore-meta = pkgs.runCommand "yore-meta" {} ''
|
||||||
|
mkdir -p $out
|
||||||
|
echo -n ${yore.version} > $out/version
|
||||||
|
echo -n ${image.drvAttrs.imageName}:${image.drvAttrs.imageTag} > $out/image-tag
|
||||||
|
'';
|
||||||
|
|
||||||
|
|
||||||
opium_ = opium.packages.${system}.opium;
|
opium_ = opium.packages.${system}.opium;
|
||||||
};
|
};
|
||||||
@@ -82,5 +110,11 @@
|
|||||||
overrides = addOpium;
|
overrides = addOpium;
|
||||||
}).env;
|
}).env;
|
||||||
};
|
};
|
||||||
|
apps = {
|
||||||
|
crane = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pkgs.crane}/bin/crane";
|
||||||
|
};
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-4
@@ -56,13 +56,13 @@ type t ? d = Optional t d
|
|||||||
load
|
load
|
||||||
:: forall (m :: ConfigVariant -> Type)
|
:: forall (m :: ConfigVariant -> Type)
|
||||||
. (Generic (m Value), GFromEnv (Rep (m Spec)) (Rep (m Value)))
|
. (Generic (m Value), GFromEnv (Rep (m Spec)) (Rep (m Value)))
|
||||||
=> IO (Either String (m Value))
|
=> IO (Either [String] (m Value))
|
||||||
load = do
|
load = do
|
||||||
env <- getEnvironment
|
env <- getEnvironment
|
||||||
pure $ to <$> gFromEnv @(Rep (m Spec)) @(Rep (m Value)) Proxy env
|
pure $ to <$> gFromEnv @(Rep (m Spec)) @(Rep (m Value)) Proxy env
|
||||||
|
|
||||||
class GFromEnv spec value where
|
class GFromEnv spec value where
|
||||||
gFromEnv :: Proxy (spec c) -> [(String, String)] -> Either String (value c)
|
gFromEnv :: Proxy (spec c) -> [(String, String)] -> Either [String] (value c)
|
||||||
|
|
||||||
instance
|
instance
|
||||||
(GFromEnv i o)
|
(GFromEnv i o)
|
||||||
@@ -77,7 +77,15 @@ instance
|
|||||||
gFromEnv Proxy env = M1 <$> gFromEnv @i @o Proxy env
|
gFromEnv Proxy env = M1 <$> gFromEnv @i @o Proxy env
|
||||||
|
|
||||||
instance (GFromEnv i1 o1, GFromEnv i2 o2) => GFromEnv (i1 :*: i2) (o1 :*: o2) where
|
instance (GFromEnv i1 o1, GFromEnv i2 o2) => GFromEnv (i1 :*: i2) (o1 :*: o2) where
|
||||||
gFromEnv Proxy env = (:*:) <$> gFromEnv @i1 @o1 Proxy env <*> gFromEnv @i2 @o2 Proxy env
|
gFromEnv Proxy env =
|
||||||
|
case (eLhs, eRhs) of
|
||||||
|
(Left errs1, Right _) -> Left errs1
|
||||||
|
(Right _, Left errs2) -> Left errs2
|
||||||
|
(Left errs1, Left errs2) -> Left $ errs1 ++ errs2
|
||||||
|
(Right lhs, Right rhs) -> Right $ lhs :*: rhs
|
||||||
|
where
|
||||||
|
eLhs = gFromEnv @i1 @o1 Proxy env
|
||||||
|
eRhs = gFromEnv @i2 @o2 Proxy env
|
||||||
|
|
||||||
instance
|
instance
|
||||||
(KnownSymbol sym, EnvVarSpec s t)
|
(KnownSymbol sym, EnvVarSpec s t)
|
||||||
@@ -86,7 +94,7 @@ instance
|
|||||||
(M1 S meta2 (Rec0 t))
|
(M1 S meta2 (Rec0 t))
|
||||||
where
|
where
|
||||||
gFromEnv Proxy env =
|
gFromEnv Proxy env =
|
||||||
M1 . K1 <$> decodeEnvVar @s @t Proxy varName (lookup varName env)
|
M1 . K1 <$> first (: []) (decodeEnvVar @s @t Proxy varName (lookup varName env))
|
||||||
where
|
where
|
||||||
varName = selectorNameToEnvVarName $ symbolVal $ Proxy @sym
|
varName = selectorNameToEnvVarName $ symbolVal $ Proxy @sym
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
cabal-version: 3.4
|
cabal-version: 3.4
|
||||||
|
|
||||||
name: yore
|
name: yore
|
||||||
version: 0
|
version: 0.0.3
|
||||||
author: Paul Brinkmeier
|
author: Paul Brinkmeier
|
||||||
maintainer: hallo@pbrinkmeier.de
|
maintainer: hallo@pbrinkmeier.de
|
||||||
copyright: 2023 Paul Brinkmeier
|
copyright: 2023 Paul Brinkmeier
|
||||||
|
|||||||
Reference in New Issue
Block a user