chore: Significantly restructure folder layout

This moves the various projects from "type-based" folders (such as
"services" or "tools") into more appropriate semantic folders (such as
"nix", "ops" or "web").

Deprecated projects (nixcon-demo & gotest) which only existed for
testing/demonstration purposes have been removed.

(Note: *all* builds are broken with this commit)
This commit is contained in:
Vincent Ambo 2019-12-20 20:18:41 +00:00
parent e52eed3cd4
commit 03bfe08e1d
110 changed files with 1 additions and 998 deletions

View file

@ -0,0 +1,97 @@
diff --git a/cmd.c b/cmd.c
index 63f0ae5..b37b79d 100644
--- a/cmd.c
+++ b/cmd.c
@@ -39,29 +39,7 @@ static void atom_fn(void)
static void about_fn(void)
{
- if (ctx.repo) {
- size_t path_info_len = ctx.env.path_info ? strlen(ctx.env.path_info) : 0;
- if (!ctx.qry.path &&
- ctx.qry.url[strlen(ctx.qry.url) - 1] != '/' &&
- (!path_info_len || ctx.env.path_info[path_info_len - 1] != '/')) {
- char *currenturl = cgit_currenturl();
- char *redirect = fmtalloc("%s/", currenturl);
- cgit_redirect(redirect, true);
- free(currenturl);
- free(redirect);
- } else if (ctx.repo->readme.nr)
- cgit_print_repo_readme(ctx.qry.path);
- else if (ctx.repo->homepage)
- cgit_redirect(ctx.repo->homepage, false);
- else {
- char *currenturl = cgit_currenturl();
- char *redirect = fmtalloc("%s../", currenturl);
- cgit_redirect(redirect, false);
- free(currenturl);
- free(redirect);
- }
- } else
- cgit_print_site_readme();
+ cgit_print_repo_readme(ctx.qry.path);
}
static void blame_fn(void)
diff --git a/ui-shared.c b/ui-shared.c
index 739505a..c7c3754 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -95,29 +95,23 @@ const char *cgit_loginurl(void)
char *cgit_repourl(const char *reponame)
{
- if (ctx.cfg.virtual_root)
- return fmtalloc("%s%s/", ctx.cfg.virtual_root, reponame);
- else
- return fmtalloc("?r=%s", reponame);
+ // my cgit instance *only* serves the depot, hence that's the only value ever
+ // needed.
+ return fmtalloc("/");
}
char *cgit_fileurl(const char *reponame, const char *pagename,
const char *filename, const char *query)
{
struct strbuf sb = STRBUF_INIT;
- char *delim;
- if (ctx.cfg.virtual_root) {
- strbuf_addf(&sb, "%s%s/%s/%s", ctx.cfg.virtual_root, reponame,
- pagename, (filename ? filename:""));
- delim = "?";
- } else {
- strbuf_addf(&sb, "?url=%s/%s/%s", reponame, pagename,
- (filename ? filename : ""));
- delim = "&";
+ strbuf_addf(&sb, "%s%s/%s", ctx.cfg.virtual_root,
+ pagename, (filename ? filename:""));
+
+ if (query) {
+ strbuf_addf(&sb, "%s%s", "?", query);
}
- if (query)
- strbuf_addf(&sb, "%s%s", delim, query);
+
return strbuf_detach(&sb, NULL);
}
@@ -245,9 +239,6 @@ static char *repolink(const char *title, const char *class, const char *page,
html(" href='");
if (ctx.cfg.virtual_root) {
html_url_path(ctx.cfg.virtual_root);
- html_url_path(ctx.repo->url);
- if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
- html("/");
if (page) {
html_url_path(page);
html("/");
@@ -957,8 +948,6 @@ static void print_header(void)
html("<td class='main'>");
if (ctx.repo) {
- cgit_index_link("index", NULL, NULL, NULL, NULL, 0, 1);
- html(" : ");
cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
if (ctx.env.authenticated) {
html("</td><td class='form'>");

View file

@ -0,0 +1,13 @@
diff --git a/config.h b/config.h
index 65ab1e3..cde470f 100644
--- a/config.h
+++ b/config.h
@@ -327,7 +327,7 @@
/* CONFIGURE: A list of index filenames to check. The files are searched
** for in this order.
*/
-#define INDEX_NAMES "index.html", "index.htm", "index.xhtml", "index.xht", "Default.htm", "index.cgi"
+#define INDEX_NAMES "cgit.cgi"
/* CONFIGURE: If this is defined then thttpd will automatically generate
** index pages for directories that don't have an explicit index file.

69
web/cgit-taz/default.nix Normal file
View file

@ -0,0 +1,69 @@
# This derivation configures a 'cgit' instance to serve repositories
# from a different source.
#
# In the first round this will just serve my GitHub repositories until
# I'm happy with the display.
{ pkgs, ... }:
with pkgs.third_party;
let
# Patched version of cgit that builds repository URLs correctly
# (since only one repository is served)
monocgit = cgit.overrideAttrs(old: {
patches = old.patches ++ [ ./cgit_depot_url.patch ];
});
cgitConfig = writeText "cgitrc" ''
# Global configuration
virtual-root=/
enable-http-clone=1
readme=:README.md
about-filter=${monocgit}/lib/cgit/filters/about-formatting.sh
source-filter=${monocgit}/lib//cgit/filters/syntax-highlighting.py
# Repository configuration
repo.url=depot
repo.path=/git/depot/.git
repo.desc=tazjin's personal monorepo
repo.owner=tazjin <mail@tazj.in>
repo.clone-url=https://git.tazj.in ssh://source.developers.google.com:2022/p/tazjins-infrastructure/r/depot
repo.enable-remote-branches=1
'';
thttpdConfig = writeText "thttpd.conf" ''
port=8080
dir=${monocgit}/cgit
nochroot
novhost
logfile=/dev/stdout
cgipat=**.cgi
'';
# Patched version of thttpd that serves cgit.cgi as the index and
# sets the environment variable for pointing cgit at the correct
# configuration.
#
# Things are done this way because recompilation of thttpd is much
# faster than cgit and I don't want to wait long when iterating on
# config.
thttpdConfigPatch = writeText "thttpd_cgit_conf.patch" ''
diff --git a/libhttpd.c b/libhttpd.c
index c6b1622..eef4b73 100644
--- a/libhttpd.c
+++ b/libhttpd.c
@@ -3055,4 +3055,6 @@ make_envp( httpd_conn* hc )
envn = 0;
+ // force cgit to load the correct configuration
+ envp[envn++] = "CGIT_CONFIG=${cgitConfig}";
envp[envn++] = build_env( "PATH=%s", CGI_PATH );
#ifdef CGI_LD_LIBRARY_PATH
'';
thttpdCgit = thttpd.overrideAttrs(old: {
patches = [ ./cgit_idx.patch thttpdConfigPatch ];
});
in writeShellScriptBin "cgit-launch" ''
exec ${thttpdCgit}/bin/thttpd -D -C ${thttpdConfig}
# ''

24
web/tazblog/blog/Main.hs Normal file
View file

@ -0,0 +1,24 @@
-- | Main module for the blog's web server
module Main where
import Control.Applicative ((<$>), (<*>))
import Server (runBlog)
import System.Environment (getEnv)
data MainOptions
= MainOptions
{ blogPort :: Int,
resourceDir :: String
}
readOpts :: IO MainOptions
readOpts =
MainOptions
<$> (fmap read $ getEnv "PORT")
<*> getEnv "RESOURCE_DIR"
main :: IO ()
main = do
opts <- readOpts
putStrLn ("tazblog starting on port " ++ (show $ blogPort opts))
runBlog (blogPort opts) (resourceDir opts)

18
web/tazblog/default.nix Normal file
View file

@ -0,0 +1,18 @@
# Build configuration for the blog using plain Nix.
#
# tazblog.nix was generated using cabal2nix.
{ pkgs, ... }:
let
inherit (pkgs.third_party) writeShellScriptBin haskell;
tazblog = haskell.packages.ghc865.callPackage ./tazblog.nix {};
wrapper = writeShellScriptBin "tazblog" ''
export PORT=8000
export RESOURCE_DIR=${./static}
exec ${tazblog}/bin/tazblog
'';
in wrapper.overrideAttrs(_: {
allowSubstitutes = true;
meta.enableCI = true;
})

11
web/tazblog/shell.nix Normal file
View file

@ -0,0 +1,11 @@
{ pkgs ? (import ../../default.nix {}).third_party.nixpkgs }:
let tazblog = import ./tazblog.nix;
depNames = with builtins; filter (
p: hasAttr p pkgs.haskellPackages
) (attrNames (functionArgs tazblog));
ghc = pkgs.ghc.withPackages(p: map (x: p."${x}") depNames);
in pkgs.stdenv.mkDerivation {
name = "shell";
buildInputs = [ ghc pkgs.hlint ];
}

141
web/tazblog/src/Blog.hs Normal file
View file

@ -0,0 +1,141 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Blog where
import BlogStore
import Data.Text (Text, pack)
import qualified Data.Text as T
import Data.Text.Lazy (fromStrict)
import Data.Time
import Text.Blaze.Html (preEscapedToHtml)
import Text.Hamlet
import Text.Markdown
blogTitle :: Text = "tazjin's blog"
repoURL :: Text = "https://bitbucket.org/tazjin/tazblog-haskell"
mailTo :: Text = "mailto:mail@tazj.in"
twitter :: Text = "https://twitter.com/tazjin"
replace :: Eq a => a -> a -> [a] -> [a]
replace x y = map (\z -> if z == x then y else z)
-- |After this date all entries are Markdown
markdownCutoff :: Day
markdownCutoff = fromGregorian 2013 04 28
blogTemplate :: Text -> Html -> Html
blogTemplate t_append body =
[shamlet|
$doctype 5
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content=#{blogTitle}#{t_append}>
<link rel="stylesheet" type="text/css" href="/static/blog.css" media="all">
<link rel="alternate" type="application/rss+xml" title="RSS-Feed" href="/rss.xml">
<title>#{blogTitle}#{t_append}
<body>
<header>
<h1>
<a href="/" .unstyled-link>#{blogTitle}
<hr>
^{body}
^{showFooter}
|]
showFooter :: Html
showFooter =
[shamlet|
<footer>
<p .footer>Served without any dynamic languages.
<p .footer>
<a href=#{repoURL} .uncoloured-link>
|
<a href=#{twitter} .uncoloured-link>Twitter
|
<a href=#{mailTo} .uncoloured-link>Mail
<p .lod>
_ಠ
|]
isEntryMarkdown :: Entry -> Bool
isEntryMarkdown e = edate e > markdownCutoff
renderEntryMarkdown :: Text -> Html
renderEntryMarkdown = markdown def {msXssProtect = False} . fromStrict
renderEntries :: [Entry] -> Maybe Html -> Html
renderEntries entries pageLinks =
[shamlet|
$forall entry <- entries
<article>
<h2 .inline>
<a href=#{linkElems entry} .unstyled-link>
#{title entry}
<aside .date>
#{pack $ formatTime defaultTimeLocale "%Y-%m-%d" $ edate entry}
$if (isEntryMarkdown entry)
^{renderEntryMarkdown $ text entry}
$else
^{preEscapedToHtml $ text entry}
<hr>
$maybe links <- pageLinks
^{links}
|]
where
linkElems Entry {..} = "/" ++ show entryId
showLinks :: Maybe Int -> Html
showLinks (Just i) =
[shamlet|
$if ((>) i 1)
<div .navigation>
<a href=#{nLink $ succ i} .uncoloured-link>Earlier
|
<a href=#{nLink $ pred i} .uncoloured-link>Later
$elseif ((<=) i 1)
^{showLinks Nothing}
|]
where
nLink page = T.concat ["/?page=", show' page]
showLinks Nothing =
[shamlet|
<div .navigation>
<a href="/?page=2" .uncoloured-link>Earlier
|]
renderEntry :: Entry -> Html
renderEntry e@Entry {..} =
[shamlet|
<article>
<h2 .inline>
#{title}
<aside .date>
#{pack $ formatTime defaultTimeLocale "%Y-%m-%d" edate}
$if (isEntryMarkdown e)
^{renderEntryMarkdown text}
$else
^{preEscapedToHtml $ text}
<hr>
|]
showError :: Text -> Text -> Html
showError title err =
blogTemplate (": " <> title)
[shamlet|
<p>:(
<p>#{err}
<hr>
|]

View file

@ -0,0 +1,182 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-- |This module implements fetching of individual blog entries from
-- DNS. Yes, you read that correctly.
--
-- Each blog post is stored as a set of records in a designated DNS
-- zone. For the production blog, this zone is `blog.tazj.in.`.
--
-- A top-level record at `_posts` contains a list of all published
-- post IDs.
--
-- For each of these post IDs, there is a record at `_meta.$postID`
-- that contains the title and number of post chunks.
--
-- For each post chunk, there is a record at `_$chunkID.$postID` that
-- contains a base64-encoded post fragment.
--
-- This module implements logic for assembling a post out of these
-- fragments and caching it based on the TTL of its `_meta` record.
module BlogStore
( BlogCache,
EntryId (..),
Entry (..),
withCache,
listEntries,
getEntry,
show'
)
where
import Control.Applicative ((<$>), (<*>))
import Control.Monad (mzero)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Aeson ((.:), FromJSON (..), Value (Object), decodeStrict)
import Data.ByteString.Base64 (decodeLenient)
import Data.Either (fromRight)
import Data.List (sortBy)
import Data.Text as T (Text, concat, pack)
import Data.Text.Encoding (decodeUtf8', encodeUtf8)
import Data.Time (Day)
import Network.DNS (DNSError, lookupTXT)
import qualified Network.DNS.Resolver as R
newtype EntryId = EntryId {unEntryId :: Integer}
deriving (Eq, Ord, FromJSON)
instance Show EntryId where
show = show . unEntryId
data Entry
= Entry
{ entryId :: EntryId,
author :: Text,
title :: Text,
text :: Text,
edate :: Day
}
deriving (Eq, Ord, Show)
-- | Wraps a DNS resolver with caching configured. For the initial
-- version of this, all caching of entries is done by the resolver
-- (i.e. no pre-assembled versions of entries are cached).
data BlogCache = BlogCache R.Resolver Text
data StoreError
= PostNotFound EntryId
| DNS DNSError
| InvalidMetadata
| InvalidChunk
| InvalidPosts
deriving (Show)
type Offset = Int
type Count = Int
withCache :: Text -> (BlogCache -> IO a) -> IO a
withCache zone f = do
let conf =
R.defaultResolvConf
{ R.resolvCache = Just R.defaultCacheConf,
R.resolvConcurrent = True
}
seed <- R.makeResolvSeed conf
R.withResolver seed (\r -> f $ BlogCache r zone)
listEntries :: MonadIO m => BlogCache -> Offset -> Count -> m [Entry]
listEntries cache offset count = liftIO $ do
posts <- postList cache
entries <- mapM (entryFromDNS cache) $ take count $ drop offset $ fromRight (error "no posts") posts
-- TODO: maybe don't just drop broken entries
return
$ fromRight (error "no entries")
$ sequence entries
getEntry :: MonadIO m => BlogCache -> EntryId -> m (Maybe Entry)
getEntry cache eid = liftIO $ entryFromDNS cache eid >>= \case
Left _ -> return Nothing -- TODO: ??
Right entry -> return $ Just entry
show' :: Show a => a -> Text
show' = pack . show
-- * DNS fetching implementation
type Chunk = Integer
-- | Represents the metadata stored for each post in the _meta record.
data Meta = Meta Integer Text Day
deriving (Show)
instance FromJSON Meta where
parseJSON (Object v) =
Meta
<$> v
.: "c"
<*> v
.: "t"
<*> v
.: "d"
parseJSON _ = mzero
entryMetadata :: BlogCache -> EntryId -> IO (Either StoreError Meta)
entryMetadata (BlogCache r z) (EntryId eid) =
let domain = encodeUtf8 ("_meta." <> show' eid <> "." <> z)
record = lookupTXT r domain
toMeta rrdata = case decodeStrict $ decodeLenient rrdata of
Nothing -> Left InvalidMetadata
Just m -> Right m
in record >>= \case
(Left err) -> return $ Left $ DNS err
(Right [bs]) -> return $ toMeta bs
_ -> return $ Left InvalidMetadata
entryChunk :: BlogCache -> EntryId -> Chunk -> IO (Either StoreError Text)
entryChunk (BlogCache r z) (EntryId eid) c =
let domain = encodeUtf8 ("_" <> show' c <> "." <> show' eid <> "." <> z)
record = lookupTXT r domain
toChunk rrdata = case decodeUtf8' $ decodeLenient rrdata of
Left _ -> Left InvalidChunk
Right chunk -> Right chunk
in record >>= \case
(Left err) -> return $ Left $ DNS err
(Right [bs]) -> return $ toChunk bs
_ -> return $ Left InvalidChunk
fetchAssembleChunks :: BlogCache -> EntryId -> Meta -> IO (Either StoreError Text)
fetchAssembleChunks cache eid (Meta n _ _) = do
chunks <- mapM (entryChunk cache eid) [0 .. (n - 1)]
return $ fmap T.concat $ sequence chunks
entryFromDNS :: BlogCache -> EntryId -> IO (Either StoreError Entry)
entryFromDNS cache eid = do
meta <- entryMetadata cache eid
case meta of
Left err -> return $ Left err
Right meta -> do
chunks <- fetchAssembleChunks cache eid meta
let (Meta _ t d) = meta
return
$ either Left
( \text -> Right $ Entry
{ entryId = eid,
author = "tazjin",
title = t,
text = text,
edate = d
}
)
chunks
postList :: BlogCache -> IO (Either StoreError [EntryId])
postList (BlogCache r z) =
let domain = encodeUtf8 ("_posts." <> z)
record = lookupTXT r domain
toPosts =
fmap (sortBy (flip compare))
. mapM (maybe (Left InvalidPosts) Right . decodeStrict)
in either (Left . DNS) toPosts <$> record

48
web/tazblog/src/RSS.hs Normal file
View file

@ -0,0 +1,48 @@
{-# LANGUAGE RecordWildCards #-}
module RSS
( renderFeed
)
where
import BlogStore
import Data.Maybe (fromJust)
import qualified Data.Text as T
import Data.Time (UTCTime (..), getCurrentTime, secondsToDiffTime)
import Network.URI (URI, parseURI)
import Text.RSS
createChannel :: UTCTime -> [ChannelElem]
createChannel now =
[ Language "en",
Copyright "Vincent Ambo",
WebMaster "mail@tazj.in",
ChannelPubDate now
]
createRSS :: UTCTime -> [Item] -> RSS
createRSS t =
let link = fromJust $ parseURI "https://tazj.in"
in RSS "tazjin's blog" link "tazjin's blog feed" (createChannel t)
createItem :: Entry -> Item
createItem Entry {..} =
[ Title "tazjin's blog",
Link $ entryLink entryId,
Description $ T.unpack text,
PubDate $ UTCTime edate $ secondsToDiffTime 0
]
entryLink :: EntryId -> URI
entryLink i =
let url = "http://tazj.in/" ++ "/" ++ show i
in fromJust $ parseURI url
createItems :: [Entry] -> [Item]
createItems = map createItem
createFeed :: [Entry] -> IO RSS
createFeed e = getCurrentTime >>= (\t -> return $ createRSS t $ createItems e)
renderFeed :: [Entry] -> IO String
renderFeed e = fmap (showXML . rssToXML) (createFeed e)

81
web/tazblog/src/Server.hs Normal file
View file

@ -0,0 +1,81 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Server where
import Blog
import BlogStore
import Control.Applicative (optional)
import Control.Monad (msum)
import Control.Monad.IO.Class (liftIO)
import Data.Maybe (maybe)
import qualified Data.Text as T
import Happstack.Server hiding (Session)
import RSS
pageSize :: Int
pageSize = 3
tmpPolicy :: BodyPolicy
tmpPolicy = defaultBodyPolicy "/tmp" 0 200000 1000
runBlog :: Int -> String -> IO ()
runBlog port respath =
withCache "blog.tazj.in." $ \cache ->
simpleHTTP nullConf {port = port} $ tazblog cache respath
tazblog :: BlogCache -> String -> ServerPart Response
tazblog cache resDir =
msum
[ -- legacy language-specific routes
dir "de" $ blogHandler cache,
dir "en" $ blogHandler cache,
dir "static" $ staticHandler resDir,
blogHandler cache,
staticHandler resDir,
notFound $ toResponse $ showError "Not found" "Page not found"
]
blogHandler :: BlogCache -> ServerPart Response
blogHandler cache =
msum
[ path $ \(eId :: Integer) -> showEntry cache $ EntryId eId,
nullDir >> showIndex cache,
dir "rss" $ nullDir >> showRSS cache,
dir "rss.xml" $ nullDir >> showRSS cache
]
staticHandler :: String -> ServerPart Response
staticHandler resDir = do
setHeaderM "cache-control" "max-age=630720000"
setHeaderM "expires" "Tue, 20 Jan 2037 04:20:42 GMT"
serveDirectory DisableBrowsing [] resDir
showEntry :: BlogCache -> EntryId -> ServerPart Response
showEntry cache eId = do
entry <- getEntry cache eId
tryEntry entry
tryEntry :: Maybe Entry -> ServerPart Response
tryEntry Nothing = notFound $ toResponse $ showError "Not found" "Blog entry not found"
tryEntry (Just entry) = ok $ toResponse $ blogTemplate eTitle $ renderEntry entry
where
eTitle = T.append ": " (title entry)
offset :: Maybe Int -> Int
offset = maybe 0 (pageSize *)
showIndex :: BlogCache -> ServerPart Response
showIndex cache = do
(page :: Maybe Int) <- optional $ lookRead "page"
entries <- listEntries cache (offset page) pageSize
ok $ toResponse $ blogTemplate ""
$ renderEntries entries (Just $ showLinks page)
showRSS :: BlogCache -> ServerPart Response
showRSS cache = do
entries <- listEntries cache 0 4
feed <- liftIO $ renderFeed entries
setHeaderM "content-type" "text/xml"
ok $ toResponse feed

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -0,0 +1,35 @@
body {
margin: 40px auto;
max-width: 650px;
line-height: 1.6;
font-size: 18px;
color: #383838;
padding: 0 10px
}
h1, h2, h3 {
line-height: 1.2
}
.footer {
text-align: right;
}
.lod {
text-align: center;
}
.unstyled-link {
color: inherit;
text-decoration: none;
}
.uncoloured-link {
color: inherit;
}
.date {
text-align: right;
font-style: italic;
float: right;
}
.inline {
display: inline;
}
.navigation {
text-align: center;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,69 @@
==================================================================
https://keybase.io/tazjin
--------------------------------------------------------------------
I hereby claim:
* I am an admin of http://tazj.in
* I am tazjin (https://keybase.io/tazjin) on keybase.
* I have a public key with fingerprint DCF3 4CFA C1AC 44B8 7E26 3331 36EE 3481 4F6D 294A
To claim this, I am signing this object:
{
"body": {
"key": {
"fingerprint": "dcf34cfac1ac44b87e26333136ee34814f6d294a",
"host": "keybase.io",
"key_id": "36EE34814F6D294A",
"uid": "2268b75a56bb9693d3ef077bc1217900",
"username": "tazjin"
},
"service": {
"hostname": "tazj.in",
"protocol": "http:"
},
"type": "web_service_binding",
"version": 1
},
"ctime": 1397644545,
"expire_in": 157680000,
"prev": "4973fdda56a6cfa726a813411c915458c652be45dd19283f7a4ae4f9c217df14",
"seqno": 4,
"tag": "signature"
}
with the aforementioned key, yielding the PGP signature:
-----BEGIN PGP MESSAGE-----
Version: GnuPG v2.0.22 (GNU/Linux)
owGbwMvMwMWY9pU1Q3bHF2vG0wdeJTEE+8WyVSsl5adUKllVK2Wngqm0zLz01KKC
osy8EiUrpZTkNGOT5LTEZMPEZBOTJAvzVCMzY2NjQ2Oz1FRjEwtDkzSzFCNLk0Ql
HaWM/GKQDqAxSYnFqXqZ+UAxICc+MwUoamzm6gpW72bmAlTvCJQrBUsYGZlZJJmb
JpqaJSVZmlkapxinphmYmyclGxoZmlsaGIAUFqcW5SXmpgJVlyRWZWXmKdXqKAHF
yjKTU0EuBlmMJK8HVKCjVFCUX5KfnJ8DFMwoKSmwAukpqSwAKSpPTYqHao9PysxL
AXoYqKEstag4Mz9PycoQqDK5JBNknqGxpbmZiYmpiamOUmpFQWZRanwmSIWpuZmF
ARCArEktAxppYmlunJaSAvRFohkwtMyNzBItDI1NDA2TLQ2Bui2SzUyNklJNTFNS
DC2NLIzTzBNNElNN0iyTgZ5MSTM0UQJ5qDAvX8nKBOjMxHSgkcWZ6XmJJaVFqUq1
nUwyLAyMXAxsrEygKGPg4hSARWSZH/8/0573HMdvfH5XxeayYZ2efPb8bw730i1/
WBU3qru5pKlf3xKmeK5ihtKeT6VXGm3usV2reZWyvO/0joi83oT9P80s88Q6U/vb
vmycHnB7e110v/3OZadu/Sx6+uXk/ZeCR8u+p/+6dNc8XWqX/68t06pnrGKU/BfU
F7X5S/HUy4ysvyZN+v1Jj6NtMvvN1EvPpCpv3kz2tGU1EzpZFfl8Xujq1OopuxZJ
l5kvDlgZ78ezdLZ1+aOlixbsXra4/3fdbZ8XnQX1DatzV18+e2rmMcPKm6qngqIf
Xp8oKTAz+Mg1v6gHP0wLN/Mf3JKjYHnX5U6L/KIvkbsLArtES0r7w1iWZ3OvvSPr
fW6heune1tOb7j3vP+1XeOyV2ekr6pPO3bdrv9X25HbTaqs7z06f0v35fmtQ3uUZ
Z35eLYmaEmb/x/u3vFh6GsvMDocpCTpPlHa0z+xzOGbhzLFO18v21Zd9ISG3Hqtd
F7jaLlWa2W+TsytNnXudVrfCBSbl8zNMfuk2e0Z8i9ix3PmEVa3rTEfhde3qwgtY
dy8rUbzzd5d9ccF63btqO/VMb4oe04x4uCLB5RD3p+8+s77o/T4WP2cFw+0cviX6
StlJX5f+U3Or3fZY7dUfPcmMJZ/eSs7m+1d5IUbs3jI27olHFzGVvTcsu7w79aOK
SxmXvnEIUwZXgP6BL4LrPDY1rN2V0q1cZj1/efj880rzeu6+OQYA
=xHfH
-----END PGP MESSAGE-----
And finally, I am proving ownership of this host by posting or
appending to this document.
View my publicly-auditable identity here: https://keybase.io/tazjin
==================================================================

39
web/tazblog/tazblog.cabal Normal file
View file

@ -0,0 +1,39 @@
Name: tazblog
Version: 6.0.0
Synopsis: Tazjin's Blog
License: MIT
Author: Vincent Ambo
Maintainer: mail@tazj.in
Category: Web blog
Build-type: Simple
cabal-version: >= 1.10
library
hs-source-dirs: src
default-language: Haskell2010
ghc-options: -W
exposed-modules: Blog, BlogStore, Server, RSS
build-depends: aeson,
base,
bytestring,
happstack-server,
text,
blaze-html,
dns,
old-locale,
time,
base64-bytestring,
network,
network-uri,
rss,
shakespeare,
markdown
executable tazblog
hs-source-dirs: blog
main-is: Main.hs
default-language: Haskell2010
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends: base,
tazblog,
network

30
web/tazblog/tazblog.nix Normal file
View file

@ -0,0 +1,30 @@
{ mkDerivation, aeson, base, base64-bytestring, blaze-html , bytestring, dns
, happstack-server, markdown, network, network-uri, old-locale, rss
, shakespeare, stdenv, text, time }:
mkDerivation {
pname = "tazblog";
version = "6.0.0";
src = ./.;
isLibrary = true;
isExecutable = true;
libraryHaskellDepends = [
aeson
base
base64-bytestring
blaze-html
bytestring
dns
happstack-server
markdown
network
network-uri
old-locale
rss
shakespeare
text
time
];
executableHaskellDepends = [ base network ];
description = "Tazjin's Blog";
license = stdenv.lib.licenses.mit;
}