Compare commits

..

1 Commits

Author SHA1 Message Date
Collin J. Doering 364a51f21a
WIP: pulumi POC deployment implementation 2021-11-21 11:39:43 -05:00
40 changed files with 2603 additions and 4654 deletions

View File

@ -1,21 +0,0 @@
local ci = import '.drone/ci.libsonnet';
[
ci.guix.pipeline("validate").withTrigger(ci.trigger.new().withEvent(["push", "pull_request", "tag"])).withSteps([
ci.guix.stepTimeMachine("build", "build -f guix.scm"),
ci.promoteStep("staging"),
ci.promoteStep("production"),
]),
ci.guix.pipeline("deploy").withTrigger(ci.trigger.new().withEvent("promote")).withSteps([
ci.awsDeployStep("init", "setup"),
ci.awsDeployStep("plan").withEnv({
PLAN: "out.plan"
}).withRuntimeEnvVar({
TF_VAR_site_static_files_dir: "$(guix time-machine -C channels.scm -- build -f guix.scm | grep -e '^.*-site$')"
}),
ci.awsDeployStep("deploy").withEnv({
PLAN: "out.plan"
}),
])
]

View File

@ -1,116 +1,12 @@
---
kind: pipeline
type: docker
name: validate
name: blog-rekahsoft-ca
platform:
os: linux
arch: amd64
workspace:
path: /drone/blog-rekahsoft-ca
steps:
- name: build
pull: if-not-exists
image: docker.nexus.home.rekahsoft.ca/guix:latest
- name: build-site
image: docker.nexus.home.rekahsoft.ca/fpco/stack-build:lts-12.0
commands:
- guix time-machine -C channels.scm -- build -f guix.scm
- name: promote-staging
pull: if-not-exists
image: docker.nexus.home.rekahsoft.ca/drone/cli:1.4-alpine
commands:
- export DRONE_SERVER="${DRONE_SYSTEM_PROTO}://${DRONE_SYSTEM_HOST}"
- export DRONE_TOKEN
- DRONE_PROMOTED_PIPELINE_ID=$(drone build promote --format '{{ .Number }}' "$DRONE_REPO" "$DRONE_BUILD_NUMBER" "staging")
- "while status=\"$(drone build info --format '{{ .Status }}' $DRONE_REPO $DRONE_PROMOTED_PIPELINE_ID)\"; do\ncase \"$status\" in\n pending|running)\n sleep 30s\n ;;\n success)\n break\n ;;\n failure|error|killed)\n echo \"Promoted job with id $DRONE_PROMOTED_PIPELINE_ID failed with status '$status'.\"\n exit 1\n ;;\n *)\n echo \"Unknown pipeline status '$status'.\"\n exit 1\nesac\ndone"
environment:
DRONE_TOKEN:
from_secret: drone_token
when:
branch:
- master
event:
- push
- name: promote-production
pull: if-not-exists
image: docker.nexus.home.rekahsoft.ca/drone/cli:1.4-alpine
commands:
- export DRONE_SERVER="${DRONE_SYSTEM_PROTO}://${DRONE_SYSTEM_HOST}"
- export DRONE_TOKEN
- DRONE_PROMOTED_PIPELINE_ID=$(drone build promote --format '{{ .Number }}' "$DRONE_REPO" "$DRONE_BUILD_NUMBER" "production")
- "while status=\"$(drone build info --format '{{ .Status }}' $DRONE_REPO $DRONE_PROMOTED_PIPELINE_ID)\"; do\ncase \"$status\" in\n pending|running)\n sleep 30s\n ;;\n success)\n break\n ;;\n failure|error|killed)\n echo \"Promoted job with id $DRONE_PROMOTED_PIPELINE_ID failed with status '$status'.\"\n exit 1\n ;;\n *)\n echo \"Unknown pipeline status '$status'.\"\n exit 1\nesac\ndone"
environment:
DRONE_TOKEN:
from_secret: drone_token
when:
branch:
- master
event:
- push
node:
guix: on
trigger:
event:
- push
- pull_request
- tag
---
kind: pipeline
type: docker
name: deploy
platform:
os: linux
arch: amd64
steps:
- name: init
pull: if-not-exists
image: docker.nexus.home.rekahsoft.ca/guix:latest
commands:
- cd infra
- "guix time-machine -C ../channels.scm -- shell -m manifest.scm -- make setup ENV=\"${DRONE_DEPLOY_TO}\" "
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
- name: plan
pull: if-not-exists
image: docker.nexus.home.rekahsoft.ca/guix:latest
commands:
- export TF_VAR_site_static_files_dir="$(guix time-machine -C channels.scm -- build -f guix.scm | grep -e '^.*-site$')"
- cd infra
- "guix time-machine -C ../channels.scm -- shell -m manifest.scm -- make plan ENV=\"${DRONE_DEPLOY_TO}\" "
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
PLAN: out.plan
- name: deploy
pull: if-not-exists
image: docker.nexus.home.rekahsoft.ca/guix:latest
commands:
- cd infra
- "guix time-machine -C ../channels.scm -- shell -m manifest.scm -- make deploy ENV=\"${DRONE_DEPLOY_TO}\" "
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
PLAN: out.plan
node:
guix: on
trigger:
event:
- promote
...
- ./site build

View File

@ -1,195 +0,0 @@
{
local ci = self,
local droneStatus = ['success', 'failure'],
pipeline:: {
new()::
self.withKind("pipeline"),
withName(name)::
self + { name: name },
withKind(kind)::
self + { kind: kind },
withType(type)::
self + { type: type },
withNode(node)::
self + { node: node },
withTrigger(trigger)::
self + { trigger: trigger },
withDependsOn(n)::
self + { depends_on: n },
withNodeSelector(ns)::
self + { node_selector: ns },
withSteps(steps)::
self + if std.type(steps) == 'array'
then { steps: steps }
else { steps: [steps] },
step:: {
new(name='', image='')::
self.withName(name).withImage(image).withPullIfNotExists(),
withName(name)::
self + { name: name },
withImage(image)::
self + if image != '' then { image: image } else {},
withAlwaysPull()::
self + { pull: 'always' },
withPullIfNotExists()::
self + { pull: 'if-not-exists' },
withCommands(commands)::
self + if std.type(commands) == 'array'
then { commands: commands }
else { commands: [commands] },
withTrigger(trigger)::
self + { trigger: trigger }, // TODO: this is duplicated in pipeline object
withEnv(envs)::
self + { environment+: envs },
withRuntimeEnvVar(envs)::
local existingCmds = if std.objectHas(self, "commands") then self.commands else [];
self + {
commands: std.map(function (i) std.format('export %s="%s"', [i, envs[i]]),
std.objectFields(envs)) + existingCmds
},
withWhen(when)::
self + { when: when },
withSettings(settings)::
self + { settings: settings },
},
when:: {
new()::
self + {},
withBranch(branch)::
self + if std.type(branch) == 'array'
then { branch: branch }
else { branch: [branch] },
withEvent(e)::
self + if std.type(e) == 'array'
then { event: e }
else { event: [e] },
withStatus(s)::
self + if std.type(s) == 'array'
then { status: s }
else { status: [s] },
withStatusAll()::
self.withStatus(droneStatus),
},
},
trigger:: {
new()::
self + {},
withBranch(branch)::
self + if std.type(branch) == 'array'
then { branch: branch }
else { branch: [branch] },
withEvent(e)::
self + if std.type(e) == 'array'
then { event: e }
else { event: [e] },
withStatus(s)::
self + if std.type(s) == 'array'
then { status: s }
else { status: [s] },
withStatusAll()::
self.withStatus(droneStatus),
},
env_from_secret(dict):: {
[key]: {
from_secret: dict[key],
}
for key in std.objectFields(dict)
},
guix:: {
pipeline(name)::
ci.pipeline.new()
.withName(name)
.withType("docker")
.withNode({ "guix": "on"}),
step(name, commands, image="docker.nexus.home.rekahsoft.ca/guix:latest")::
ci.pipeline.step.new(name, image).withPullIfNotExists().withCommands(commands),
stepTimeMachine(name, commands, cwd=".", channels="channels.scm", image="docker.nexus.home.rekahsoft.ca/guix:latest")::
ci.pipeline.step.new(name, image).withPullIfNotExists().withCommands(
// Conditionally change directory
(if cwd == "."
then [] else [std.format("cd %s", cwd)]) +
// Expand provide guix commands into executable shell
std.map(function(i) std.format("guix time-machine -C %s -- %s", [channels, i]),
if std.type(commands) == 'array' then commands else [commands])),
},
promoteStep(env,
secret_name_drone_token="drone_token",
image="docker.nexus.home.rekahsoft.ca/drone/cli:1.4-alpine")::
local dronePromoteCmd(env) = [
"export DRONE_SERVER=\"${DRONE_SYSTEM_PROTO}://${DRONE_SYSTEM_HOST}\"",
"export DRONE_TOKEN",
std.format('DRONE_PROMOTED_PIPELINE_ID=$(drone build promote --format \'{{ .Number }}\' "$DRONE_REPO" "$DRONE_BUILD_NUMBER" "%s")', env),
'while status="$(drone build info --format \'{{ .Status }}\' $DRONE_REPO $DRONE_PROMOTED_PIPELINE_ID)"; do
case "$status" in
pending|running)
sleep 30s
;;
success)
break
;;
failure|error|killed)
echo "Promoted job with id $DRONE_PROMOTED_PIPELINE_ID failed with status \'$status\'."
exit 1
;;
*)
echo "Unknown pipeline status \'$status\'."
exit 1
esac
done',
];
ci.pipeline.step.new(std.format("promote-%s", env), image)
.withWhen(ci.pipeline.when.new()
.withBranch("master")
.withEvent("push"))
.withCommands(dronePromoteCmd(env))
.withEnv(ci.env_from_secret({
DRONE_TOKEN: "drone_token"
})),
awsDeployStep(name, target=name, args=[])::
ci.guix.stepTimeMachine(
name,
std.format('shell -m manifest.scm -- make %s ENV="${DRONE_DEPLOY_TO}" %s', [target, std.join(" ", args)]),
cwd="infra",
channels="../channels.scm")
.withEnv(ci.env_from_secret({
AWS_ACCESS_KEY_ID: "aws_access_key_id",
AWS_SECRET_ACCESS_KEY: "aws_secret_access_key",
})),
}

10
.envrc
View File

@ -1,10 +0,0 @@
use_guix-shell() {
CHANNEL_FILE=channels.scm
if [ -f $CHANNEL_FILE ]; then
eval "$(guix time-machine -C $CHANNEL_FILE -- shell "$@" --search-paths)"
else
eval "$(guix shell "$@" --search-paths)"
fi
}
use guix-shell -f guix.scm -Df guix.scm

3
.ghci
View File

@ -1,3 +0,0 @@
:set -fwarn-unused-binds -fwarn-unused-imports
:set -isrc
:load src/site.hs

19
.gitignore vendored
View File

@ -1,4 +1,5 @@
# Editor specific files
.*
*~
.dir-locals.el
.tern-port
@ -6,6 +7,7 @@
# Haskell
*.o
*.hi
.stack
# Hakyll
_site
@ -13,14 +15,11 @@ _cache
dist
# Terraform
infra/.terraform
infra/terraform.tfstate.d
infra/*.local.tfvars
infra/*.plan
.terraform
terraform.tfstate.d
*.local.tfvars
*.plan
# Vendored libraries
lib/MathJax
# Generated by ./bootstrap
Makefile
scripts
# Pulumi
*.pyc
infra/venv/

View File

@ -1,6 +1,6 @@
Terms of Use
This site is Copyright 2016-2023 © - Collin Doering and is distributed under
This site is Copyright 2016 © - Collin Doering and is distributed under
the following terms.
1. All rights reserved on the "#! λ Slang" name, as well as on the

88
README.md Normal file
View File

@ -0,0 +1,88 @@
# Source Code for **[#! Lambda Slang](http://www.blog.rekahsoft.ca)**
* [Features](#features)
* [Tools](#tools)
* [License](#license)
* [Building](#building)
* [Deploying](#deploying)
* [Issues](#issues)
[#! Lambda Slang](http://www.blog.rekahsoft.ca) is the personal technical blog of *Collin Doering*,
built using software that [respects our freedoms](https://www.gnu.org/philosophy/free-sw.html).
## Features <a name="features"></a>
* [Single Page Application (SPA)](http://en.wikipedia.org/wiki/Single-page_application)
* Utilizes CSS 3
* Uses HTML5 Application Cache for offline viewing of website
## Tools <a name="tools"></a>
The creation of this website was made possible by the following open source tools and libraries:
* [Hakyll][] is used to generate site from static files
* [Clay][] is used for CSS pre-processing
* [Skeleton][] is used for CSS boilerplate
* [MathJax][] is used for rendering mathematics
* [Inkscape][] and the [Gimp][] were used to create various images/artwork
* [Gnu Free Fonts][], specifically *FreeMono* is used as main font
* [Gnu Emacs][], because there is no place like home; and no greater editor!
## License <a name="license"></a>
Simply put, you're welcome to use the code used to generate this site though there are a few restrictions:
* Any images and artwork that embody the likeness of "#! Lambda Slang" are not to be distributed or
used and are strictly copyright
* The content of pages and posts can be used with attribution, providing you aren't making money off of it
Various licenses ([GPLv3][], [Creative Commons BY-NC-SA License][], and
[Creative Commons BY-NC-ND License][]) are deployed dependent on which part of the site is in
question. Please see the LICENSE file for full details.
## Building <a name="building"></a>
[Stack][] is used to manage dependencies for this project. A simple wrapper script `site` is
provided that also takes care of building the static site and offering access to hakyll
commands.
$ ./site build
$ ./site watch
## Deploying <a name="deploying"></a>
Terraform is used to deploy this site. Its configuration files are located in `./infra`. Three
workspaces are currently available, including:
- default (unused)
- staging
- production
For example, this is how to deploy the production version of the site:
$ cd infra
$ terraform workspace select production
$ terraform plan --var-file=production.tfvars --out local.plan
$ terraform apply local.plan
## Issues <a name="issues"></a>
If you have an issue while browsing [my blog](http://www.blog.rekahsoft.ca) please file a issue
in the [blog-rekahsoft-ca](https://git.rekahsoft.ca/rekahsoft/blog-rekahsoft-ca/issues) issue
tracker.
[Hakyll]: http://jaspervdj.be/hakyll/
[Clay]: http://fvisser.nl/clay/
[Skeleton]: http://www.getskeleton.com/
[JQuery]: http://jquery.com
[JQuery-address]: https://github.com/asual/jquery-address
[MathJax]: http://www.mathjax.org/
[Inkscape]: http://inkscape.org/
[Gimp]: http://www.gimp.org/
[Stack]: https://haskellstack.org
[Gnu Emacs]: http://www.gnu.org/software/emacs/
[Gnu Free Fonts]: http://www.gnu.org/software/freefont/
[GPLv3]: https://www.gnu.org/licenses/gpl.html
[Creative Commons BY-NC-SA License]: http://creativecommons.org/licenses/by-nc-sa/4.0/
[Creative Commons BY-NC-ND License]: http://creativecommons.org/licenses/by-nc-nd/4.0/

View File

@ -1,390 +0,0 @@
#+TITLE: Source Code for [[http://www.blog.rekahsoft.ca][#! Lambda Slang]]
#+AUTHOR: Collin J. Doering
#+BEGIN_EXPORT html
<p><a href="https://ci.home.rekahsoft.ca/rekahsoft-public/blog-rekahsoft-ca"><img src="https://ci.home.rekahsoft.ca/api/badges/rekahsoft-public/blog-rekahsoft-ca/status.svg?ref=refs/heads/master" alt="Build Status"></a></p>
#+END_EXPORT
#+begin_abstract
[[http://www.blog.rekahsoft.ca][#! Lambda Slang]] is the personal technical blog of *Collin Doering*, built using software that
[[https://www.gnu.org/philosophy/free-sw.html][respects our freedoms]].
#+end_abstract
* Features
- [[http://en.wikipedia.org/wiki/Single-page_application][Single Page Application (SPA)]]
- Write blog posts and pages in markdown
- Support for math markup via MathJax
- RSS/Atom feed
* Tools
The creation of this website was made possible by the following open source tools and
libraries:
- [[http://jaspervdj.be/hakyll/][Hakyll]] is used to generate site from static files
- [[http://fvisser.nl/clay/][Clay]] is used for CSS pre-processing
- [[http://www.getskeleton.com/][Skeleton]] is used for CSS boilerplate
- [[http://www.mathjax.org/][MathJax]] is used for rendering mathematics
- [[http://jquery.com][JQuery]] is used for various DOM manipulations
- [[https://guix.gnu.org/][Gnu Guix]] is used to manage development environments and packaging
- [[http://inkscape.org/][Inkscape]] and the [[http://www.gimp.org/][Gimp]] were used to create various images/artwork
- [[http://www.gnu.org/software/freefont/][Gnu Free Fonts]], specifically *FreeMono* is used as main font
- [[http://www.gnu.org/software/emacs/][Gnu Emacs]] because there is no place like home; and no greater editor!
* License
Simply put, you're welcome to use the code used to generate this site though there are a few
restrictions:
- Any images and artwork that embody the likeness of "#! Lambda Slang" are not to be distributed or
used and are strictly copyright
- The content of pages and posts can be used with attribution, providing you aren't making money off of it
Various licenses ([[https://www.gnu.org/licenses/gpl.html][GPLv3]], [[http://creativecommons.org/licenses/by-nc-sa/4.0/][Creative Commons BY-NC-SA License]], and [[http://creativecommons.org/licenses/by-nc-nd/4.0/][Creative Commons BY-NC-ND
License]]) are deployed dependent on which part of the site is in question. Please see the
[[./LICENSE][LICENSE]] file for full details.
* Repository Structure
- ~blog-rekahsoft-ca.cabal~ :: Cabal package definition.
- ~bootstrap.sh~ :: Generate development scripts, Makefile, and setup vendor links based on this literate configurationl
- ~channels.scm~ :: Guix Channel File.
- ~clay/*.hs~ :: Clay source files.
- ~css/*.css~ :: CSS source files (will be minified).
- ~drafts/*.md~ :: Draft posts.
- ~files/images/~ :: Folder for images used in posts.
- ~files/source/~ :: Folder for source code used in blog posts/pages.
- ~fonts/*.{ttf,woff,...}~ :: Font files
- ~guix.scm~ :: Guix package definition for this site-builder and its resulting site.
- ~images/*~ :: Folder for images used on pages or in templates.
- ~images-src/*~ :: Folder for the image source files (should be 1-to-1 with files in ~images/*~).
- ~infra/~ :: Infrastructure folder; contains terraform based Infrastructure As Code (IAC).
- ~infra/channels.scm~ :: Symlink to ~../channels.scm~ (can be independent if needed).
- ~infra/*.tf~ :: Terraform source files.
- ~infra/*.tfvars~ :: Terraform variables files for each environment (non-secrets).
- ~infra/Makefile~ :: Makefile used for terraform deployments.
- ~infra/manifest.scm~ :: Guix manifest that defines the necessary deployment environment.
- ~js/*.js~ :: Javascript files.
- ~lib/*~ :: Javascript libraries.
- ~LICENSE~ :: License file.
- ~pages/*.markdown~ :: Page content.
- ~posts/*.markdown~ :: Blog posts.
- ~README.org~ :: Org-mode documentation.
- ~robots.txt~ :: Robot Exclusion Protocol file.
- ~Setup.hs~ :: Cabal build script for this site.
- ~src/*.hs~ :: Hakyll powered site builder.
- ~templates/~ :: Folder for all template files.
- ~templates/default.html~ :: Entry point template (defines html document used for all pages).
- ~templates/pages/*.html~ :: Html page templates (correspond 1-to-1 with pages/*.markdown files).
- ~templates/partials/*.html~ :: Partial template files, for use within templates.
- ~templates/tag-page.html~ :: Template for creating pages about tags with a specific tag.
* Guix Development Environment
[[https://guix.gnu.org/][Gnu Guix]] is used to package this project and manage its dependencies, as well as to provide
reproducible development environments.
** Prerequisites
The only prerequisite for starting a development environment for this project is [[https://guix.gnu.org/][GNU Guix]].
Optionally, [[https://direnv.net/][direnv]] can be used to enable a non-containerized development environment that is
abridged with your existing shell.
** Quick Start
First run the bootstrap script, which uses this documentation to generate a ~Makefile~ that
can be used for development.
#+name: bootstrap
#+begin_src sh
./bootstrap.sh
#+end_src
Then run the development 'auto-watching' environment:
#+begin_src sh
make
#+end_src
This starts a containerized local development environment that uses [[https://github.com/ndmitchell/ghcid/][ghcid]] to watch haskell
sources and restart hakyll's [[*Watch][site watch]] feature when changes occur. The site will be
available at http://localhost:3000, and will automatically rebuild as site files change
(templates, post, pages, etc..).
** Start Development Environment
The development environment is defined by the following files:
- [[./channels.scm][channels.scm]] :: Specifically defines a set of available software, their versions and their build recipe.
- [[./guix.scm][guix.scm]] :: Defines the package for this site, ~blog-rekahsoft-ca~.
To start a development environment, run the following:
#+begin_src sh :mkdirp yes :tangle ./scripts/start-development-environment.sh :tangle-mode (identity #o555)
guix time-machine -C channels.scm -- shell -CN -E '^LANG$' -E '^TERM$' -f guix.scm -Df guix.scm $@
#+end_src
This uses the [[info:guix#Invoking guix time-machine][guix time-machine]] feature to ensure the development environment is reproducible
by supplying a set of guix channels, effectively pinning all software versions used. The [[info:guix#Invoking guix shell][guix
shell]] command is used within the time-machine to start a development environment in a
container (~-C~), which shares the hosts network namespace (~-N~). The environment variable
~LANG~ is passed into the container to ensure locales work as expected; without this, site
building will fail! Additionally, the environment variable ~TERM~ is passed into the
container to ensure the development shell behaves correctly. The option ~-f guix.scm~ loads
the ~blog-rekahsoft-ca~ package, and ~-Df guix.scm~ indicates that development dependencies
of the ~blog-rekahsoft-ca~ package should be included in the environment.
*** Deployment Environment
[[https://guix.gnu.org/][Gnu Guix]] is used, similar to in the [[*Start Development Environment][previous section]], to create environments with all tools
necessary for deployments, with a notable difference being a ~guix.scm~ file is not provided
or needed, as the deployment environment is used solely for its side effects.
- [[./infra/channels.scm][infra/channels.scm]] :: Symlink to [[./channels.scm][../channels.scm]] to make the guix cli workflow nicer when
in the ~infra~ directory. Technically this doesn't need to be a symlink, and could be a
different set of channels or version of channels compared to the channels file at the
top-level of the repository, however this would complicate [[*Composing Site Development and Deployment Environments][Composing Site Development and
Deployment Environments]], so its preferred that all guix environments for the project,
including the development and deployment environment use the same set of Guix channels.
- [[./infra/manifest.scm][infra/manifest.scm]] :: Defines packages required for deployment of this site.
To start a deployment environment, run the following:
#+begin_src sh :mkdirp yes :tangle ./scripts/start-deployment-environment.sh :tangle-mode (identity #o555)
cd infra
guix time-machine -C channels.scm -- shell -CN -E '^LANG$' -E '^TERM$' -E '^AWS.*$'
#+end_src
*** Composing Site Development and Deployment Environments
#+begin_src sh :mkdirp yes :tangle ./scripts/start-development-and-deployment-environment.sh :tangle-mode (identity #o555)
guix time-machine -C channels.scm -- shell -CN -E '^LANG$' -E '^TERM$' -E '^AWS.*$' -f guix.scm -Df guix.scm -m infra/manifest.scm $@
#+end_src
** Hakyll Site Commands
*** Build Site
This website is built from a collection of markdown files and templates that are processed by
pandoc and are stitched together using Hakyll. To build the html/css/jss and all other assets
required to deploy and distribute the site, the hakyll derived site-builder,
~blog-rekahsoft-ca~ must be invoked. For convenience, an alias ~site~ is provided for the
site builder as part of its guix package. Here is it being used to build the site:
#+begin_src sh
site build
#+end_src
*** Clean Site
[[*Build Site][Building the site]] has the side effect of writing a couple files/directories to disk as a
result of the build process. In some cases, its useful to start of with a clean slate and
remove any files that were generated for the site. To so so, the ~clean~ sub-command can be
used:
#+begin_src sh
site clean
#+end_src
*** Watch
During development of new content or adjustments to the site, it is useful to autocompile
upon changes to any site files (templates, pages, posts, etc..). This functionality is
provided by Hakyll.
#+begin_src sh
site watch
#+end_src
*** TODO ~site deploy~ command
#+begin_src sh
site deploy
#+end_src
** Clean up Guix Store
#+begin_src sh :mkdirp yes :tangle ./scripts/clean-guix-store.sh :tangle-mode (identity #o555)
guix gc --list-dead | grep -e '^/gnu/store/.*-blog-rekahsoft-ca-.*' | xargs guix gc -D
#+end_src
** Enhanced Watch
When making adjustments to the site builder itself, it is useful to have functionality
similar to the site content watching feature of Hakyll, but for haskell source files.
Luckily, [[https://github.com/ndmitchell/ghcid/][ghcid]] can be used for this, and is included in the projects development
dependencies, specified in the ~guix.scm~ file.
#+name: watch-all
#+begin_src sh :mkdirp yes :tangle ./scripts/watch-all.sh :tangle-mode (identity #o555)
ghcid --test _devWatch
#+end_src
* Building a Release
The software built that itself builds this blog is released as a Guix package. It is
currently not, and is not ever expected to be distributed via a channel, as it provides
little benefit to anyone except myself, and is meant to operate along with stateful data,
including the site templates, content, pages, posts, etc..
To build a release, run the following command:
#+begin_src sh :mkdirp yes :tangle ./scripts/build-release.sh :tangle-mode (identity #o555)
guix time-machine -C channels.scm -- build -f guix.scm
#+end_src
This will produce a guix package with the following three outputs:
- ~out~ :: The ~blog-rekahsoft-ca~ site builder (also available as ~site~), and ~gencss~ css
generator binaries
- ~site~ :: A build of the website made with the site builder, etc.. in the ~out~ output of
this package, using the content at the same version
- ~static~ :: License file and any other file that should be distributed (eg manual)
** Verifying a Release
To manually verify a release, any http webserver can be used to serve the ~site~ output of
the guix build. For instance, this is how Python's ~http.server~ builtin http server can be
used.
#+begin_src sh
guix shell python-wrapper -- python -m http.server -d $(guix time-machine -C channels.scm -- build -f guix.scm | grep -E '^.*-site') 3000
#+end_src
** TODO What is done with the release?
* Deploying the Site
Terraform is used to deploy this site. Its configuration files are located in ~./infra~.
Under normal conditions, all deployments occur from my internal ci/cd system. This ensures
that the deployment process is reliable, repeatable and quick. However, in the case of both
development and emergency deployments, clear documentation surrounding the deployment process
is necessary.
** Start [[*Deployment Environment][Deployment Environment]]
** Setup a Particular Environment
Three environments (terraform workspaces) are currently available, including:
- default :: unused default terraform workspace
- staging :: https://www.blog.staging.rekahsoft.ca
- production :: https://www.blog.rekahsoft.ca
#+begin_src sh
make setup ENV=<env>
#+end_src
From this point onward, any ~make~ target run will operate on the selected environment,
unless its switched with the ~workspace~ or ~setup~ targets, or manually with ~terraform~.
** See What Infrastructure Will Change
Run a terraform plan to see how the selected environments infrastructure will change.
#+begin_src sh
make plan
#+end_src
** Deploy the Site
Run a terraform apply to deploy to the selected environment.
#+begin_src sh
make deploy
#+end_src
** Working with Terraform Directly
Within a development environment, ~terraform~, its providers and all other dependencies are
available. As such, its possible to directly leverage ~terraform~ and its various operations.
This is particularly useful when debugging or adding make targets.
* TODO Writing a Blog Post
The most natural way to edit and preview a post is to use [[https://direnv.net/][direnv]] along with this repository,
which uses ~guix shell~ to transparently provide all necessary tools, including [[*Hakyll Site Commands][Hakyll Site
Commands]]. When using direnv, a containerized environment will not be used, however for
content development, this is not a concern.
#+begin_src sh
guix time-machine -C channels.scm -- shell -CN -E LANG -E TERM -f guix.scm
#+end_src
* DOING Vendor external libraries using Guix
Some ...
#+begin_src sh :mkdirp yes :tangle ./scripts/vendor-deps.sh :tangle-mode (identity #o555)
[ -h lib/MathJax ] && rm lib/MathJax
[ -e lib/MathJax ] && echo "lib/MathJax exists, but not as a symlink; please manually remove it!" && exit 1
ln -s $(guix time-machine -C channels.scm -- shell -Df guix.scm -- bash -c 'echo $GUIX_ENVIRONMENT')/share/javascript/mathjax lib/MathJax
#+end_src
* Makefile
In order to simplify running the various commands outlined throughout this document, a
~Makefile~ is defined below.
#+begin_src makefile :noweb yes :tangle Makefile :tangle-mode (identity #o444)
# THIS IS A GENERATED FILE, DO NOT EDIT!
# Instead modify README.org appropriately
.DEFAULT_GOAL := watch
.PHONY: bootstrap
bootstrap:
<<bootstrap>>
.PHONY: dev
dev:
./scripts/start-development-environment.sh
.PHONY: dev-deploy
dev-deploy:
./scripts/start-deployment-environment.sh
.PHONY: dev-all
dev-all:
./scripts/start-development-and-deployment-environment.sh
.PHONY: watch-all
watch-all:
./scripts/watch-all.sh
.PHONY: watch
watch:
./scripts/start-development-environment.sh -- <<watch-all>>
.PHONY: build
build-release:
./scripts/build-release.sh
.PHONY: vendor
vendor:
./scripts/vendor-deps.sh
.PHONY: clean
clean:
./scripts/clean-guix-store.sh
rm -rf scripts lib/MathJax Makefile
#+end_src
* Continuous Integration & Delivery
** TODO Generate ~.drone.yaml~
#+begin_src sh
drone jsonnet --stream --format
#+end_src
*Note:* currently ~drone-cli~ is not packaged for Guix, so for the time being, it can be run
with docker as follows, where ~<version>~ is the drone-cli version.
#+begin_src shell
docker run -v ${PWD}:/tmp/app -w /tmp/app --rm -it drone/cli:<versin> jsonnet --stream --format
#+end_src
* Known Issues
If you have an issue while browsing [[http://www.blog.rekahsoft.ca][my blog]] please let me know via [[https://www.blog.rekahsoft.ca/contact.html][email]].

View File

@ -61,8 +61,8 @@ executable blog-rekahsoft-ca
other-extensions: OverloadedStrings, TupleSections, FlexibleContexts
-- Other library packages from which modules are imported.
build-depends: base >=4.16 && <4.17,
hakyll >= 4.15 && <4.16,
build-depends: base >=4.11 && <4.12,
hakyll >= 4.12 && <4.13,
pandoc >= 1.13,
parsec >= 3.1,
filepath >= 1.3,
@ -89,8 +89,8 @@ executable gencss
other-extensions: OverloadedStrings
-- Other library packages from which modules are imported.
build-depends: base >=4.16 && <4.17,
clay >=0.14 && <0.15,
build-depends: base >=4.11 && <4.12,
clay >=0.13 && <0.14,
text >=1.2 && <1.3
-- Directories containing source files.

View File

@ -1,5 +0,0 @@
#!/usr/bin/env bash
guix time-machine -C channels.scm -- shell -C emacs git -- emacs -q README.org --batch --eval '(org-babel-tangle)'
./scripts/vendor-deps.sh

View File

@ -1,20 +0,0 @@
(list (channel
(name 'guix)
(url "https://git.savannah.gnu.org/git/guix.git")
(commit
"a4e9842a70775a54bbe1369881b739e7ea9a6432")
(introduction
(make-channel-introduction
"9edb3f66fd807b096b48283debdcddccfea34bad"
(openpgp-fingerprint
"BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA"))))
(channel
(name 'rekahsoft-guix)
(url "https://git.rekahsoft.ca/rekahsoft/rekahsoft-guix.git")
(commit
"e016a7e7a9eb3d27a4d6368861222e9916e27a47")
(introduction
(make-channel-introduction
"191cdaa0947657e0c85fe89ebbb8e7b1e7a8e0a4"
(openpgp-fingerprint
"F8D5 46F3 AF37 EF53 D1B6 48BE 7B4D EB93 212B 3022")))))

View File

@ -55,7 +55,7 @@ navigation = do
(backgroundPosition $ positioned (px 304) nil)
"#nav" ? do
border (px 2) solid black
border solid (px 2) black
borderRightWidth 0
borderLeftWidth 0
backgroundImage $ url "/images/diagonal-stripes.png"
@ -126,8 +126,8 @@ statusMessage :: Css
statusMessage = do
"#status" ? do
display none
border (px 1) solid black
borderTop nil solid black
border solid (px 1) black
borderTop solid nil black
borderBottomRightRadius (px 5) (px 5)
borderBottomLeftRadius (px 5) (px 5)
backgroundColor $ rgb 146 208 240

View File

@ -50,7 +50,7 @@ aPost = do
header ? do
marginBottom (em 0.8)
border (px 2) solid "#eee"
border solid (px 2) "#eee"
sym borderRadius (px 3)
sym padding (em 0.35)
paddingLeft (px 65)
@ -84,7 +84,7 @@ aPost = do
footer ? do
padding (em 0.75) nil (em 0.25) nil
borderTop (px 1) solid "#eee"
borderTop solid (px 1) "#eee"
".read-more" ? fontWeight bold
".no-teaser" ? do
@ -122,7 +122,7 @@ aPost = do
businessCard :: Css
businessCard = do
"#business-card" ? do
border (px 2) solid black
border solid (px 2) black
sym borderRadius (px 5)
sym padding (px 10)
minHeight (px 215)
@ -133,7 +133,7 @@ businessCard = do
backgroundImage $ url "/images/business-card.png"
backgroundSize cover
backgroundPosition $ placed sideCenter sideCenter
border (px 1) solid black
border solid (px 1) black
sym borderRadius (px 10)
minHeight (px 215)
minWidth (px 150)
@ -141,7 +141,7 @@ businessCard = do
marginRight (px 10)
".info" ? do
borderTop (px 2) solid black
borderTop solid (px 2) black
overflow hidden
paddingTop (px 8)
@ -185,11 +185,11 @@ srcCodeBlock = do
<> table # ".sourceCode" ** pre ? do
sym margin nil
sym padding nil
border nil none black
border none nil black
verticalAlign vAlignBaseline
td # ".lineNumbers" ? do
borderRight (px 1) solid "#AAAAAA"
borderRight solid (px 1) "#AAAAAA"
textAlign $ alignSide sideRight
color "#AAAAAA"
paddingLeft (px 8)
@ -222,7 +222,7 @@ srcCodeBlock = do
postFigures :: Css
postFigures = do
figure ? do
border (px 1) solid black
border solid (px 1) black
sym borderRadius (px 3)
clear both
@ -230,7 +230,7 @@ postFigures = do
img <? do
display block
width (pct 100)
borderBottom (px 1) solid black
borderBottom solid (px 1) black
cursor pointer
figcaption # ":before" <? do
@ -249,7 +249,7 @@ inlinePostImages = article # ".post" ? do
clear clearRight
float floatRight
width (pct 30)
border (px 1) solid black
border solid (px 1) black
sym borderRadius (px 3)
sym margin (em 1)
cursor pointer

View File

@ -32,7 +32,7 @@ makeBorderBox pad backCol =
backCol' = fromMaybe (rgba 250 250 255 165) backCol
in do
backgroundColor backCol'
border (px 1) solid "#888"
border solid (px 1) "#888"
borderRadius (px 5) (px 5) (px 5) (px 5)
-- boxShadow (px 2) (px 5) (px 2) "#888"
sym padding pad'

View File

@ -1,85 +0,0 @@
;; (C) Copyright Collin J. Doering 2021
;;
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;; File: blog-rekahsoft-ca.scm
;; Author: Collin J. Doering <collin.doering@rekahsoft.ca>
;; Date: Nov 21, 2021
(use-modules
((guix licenses) #:prefix license:)
(guix packages)
(guix build-system haskell)
(guix git-download)
(guix gexp)
(gnu packages base)
(gnu packages javascript)
(gnu packages haskell-apps)
(rekahsoft-gnu packages haskell-web)
(git))
(define %srcdir
(dirname (current-filename)))
(define %blog-rekahsoft-ca
(let ((commit (oid->string
(reference-target
(repository-head (repository-open %srcdir)))))
(revision "1"))
(package
(name "blog-rekahsoft-ca")
(version (git-version "0.0.0.0" revision commit))
(source (local-file "." "blog-rekahsoft-ca-git-checkout"
#:recursive? #t
#:select? (git-predicate %srcdir)))
(build-system haskell-build-system)
(native-inputs `(("glibc-utf8-locales" ,glibc-utf8-locales)
("make" ,gnu-make)
("ghcid" ,ghcid)))
(inputs `(("ghc-hakyll" ,ghc-hakyll)
("ghc-clay" ,ghc-clay)
("js-mathjax" ,js-mathjax)))
(outputs '("out" "site" "static"))
(arguments
`(#:phases
(modify-phases %standard-phases
(add-after 'install 'install-site-script
(lambda* (#:key outputs #:allow-other-keys)
(let ((out (assoc-ref outputs "out")))
(setenv "PATH" (string-append out "/bin:" (getenv "PATH")))
(symlink (string-append out "/bin/blog-rekahsoft-ca") (string-append out "/bin/site"))
#t)))
(add-after 'install-site-script 'build-site
(lambda* (#:key outputs #:allow-other-keys)
(let* ((out (assoc-ref outputs "out"))
(site (assoc-ref outputs "site")))
;; Copy (vendor) dependencies: MathJax
(copy-recursively (string-append (assoc-ref %build-inputs "js-mathjax")
"/share/javascript/mathjax")
"lib/MathJax" #:follow-symlinks? #t)
;; All source files are read-only and need to be adjusted to allow the
;; site to be generated at the end of the build
(for-each make-file-writable (find-files "."))
(invoke "site" "build")
(copy-recursively "_site" site)
#t))))))
(home-page "http://git.rekahsoft.ca/rekahsoft/blog-rekahsoft-ca")
(synopsis "Code, templates and content for my Hakyll powered blog at blog.rekahsoft.ca")
(description
"The code, templates and content for my Hakyll powered blog at blog.rekahsoft.ca.")
(license license:gpl3))))
%blog-rekahsoft-ca

View File

@ -1,122 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="55.391273"
height="69.249252"
id="svg6166"
version="1.1"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
sodipodi:docname="page-next.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs6168">
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Lstart"
style="overflow:visible">
<path
id="path8766"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
transform="matrix(0.8,0,0,0.8,10,0)"
inkscape:connector-curvature="0" />
</marker>
<filter
inkscape:collect="always"
id="filter9710"
color-interpolation-filters="sRGB"
x="-0.058799997"
y="-0.006681818"
width="1.1176"
height="1.0133636">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.2625"
id="feGaussianBlur9712" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="-276.5"
inkscape:cy="-116"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
showborder="true"
inkscape:showpageshadow="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="944"
inkscape:window-height="1046"
inkscape:window-x="5"
inkscape:window-y="1103"
inkscape:window-maximized="0"
inkscape:pagecheckerboard="0" />
<metadata
id="metadata6171">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-469.66731,-534.0202)">
<g
id="g9738">
<g
transform="matrix(-1.4120922,1.4120922,-1.4120922,-1.4120922,1625.951,-48.42554)"
id="g5873"
style="stroke:none;display:inline">
<path
inkscape:connector-curvature="0"
id="path6537-5-2-1-9"
d="m 635.53312,174.02289 c -8.17336,0 -16.34673,0 -24.52009,0 0,8.17336 0,16.34673 0,24.52009 8.17336,-8.17336 16.34673,-16.34673 24.52009,-24.52009 z"
style="opacity:0.73443986;fill:#000000;fill-opacity:1;stroke:none;display:inline" />
<path
inkscape:connector-curvature="0"
id="path6537-5-2-1-4-28"
d="m 636.56876,177.86442 c -7.14409,0 -14.28819,0 -21.43228,0 0,7.14409 0,14.28818 0,21.43227 7.14409,-7.14409 14.28819,-14.28818 21.43228,-21.43227 z"
style="opacity:0.73443986;fill:#000000;fill-opacity:1;stroke:none;display:inline" />
<path
inkscape:connector-curvature="0"
id="path6537-5-2-1-4-2-3"
d="m 636.78931,182.05502 c -5.96778,0 -11.93557,0 -17.90335,0 0,5.96778 0,11.93556 0,17.90334 5.96778,-5.96778 11.93557,-11.93556 17.90335,-17.90334 z"
style="opacity:0.73443986;fill:#000000;fill-opacity:1;stroke:none;display:inline" />
</g>
<rect
transform="matrix(0.71573023,0,0,0.64570643,133.51222,207.36881)"
y="512.36218"
x="535.71429"
height="94.285713"
width="10.714286"
id="rect9704"
style="opacity:0.7;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter9710)" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@ -5,17 +5,16 @@
.PHONY: default
default: deploy
SELECTED_WORKSPACE := $(shell cat .terraform/environment 2>/dev/null || echo default)
SELECTED_WORKSPACE := $(shell terraform workspace show)
ENV := $(if $(ENV),$(ENV),$(SELECTED_WORKSPACE))
.PHONY: setup
setup: init workspace
.PHONY: workspace
workspace:
setup:
ifneq ($(SELECTED_WORKSPACE),$(ENV))
ifndef CI
@terraform workspace select $(ENV)
endif
endif
.PHONY: init
init:
@ -39,7 +38,3 @@ destroy: setup
@terraform destroy \
$(if $(ENV),--var-file=$(ENV).tfvars) \
$(ARGS)
.PHONY: clean
clean:
@rm -rf .terraform

1
infra/Pulumi.dev.yaml Normal file
View File

@ -0,0 +1 @@
encryptionsalt: v1:41djbtbdfn8=:v1:nmi4l6XY2PicKzLx:b50NBop6ZJ24hPXiuLA8DOF/vwa3/g==

6
infra/Pulumi.yaml Normal file
View File

@ -0,0 +1,6 @@
name: blog-rekahsoft-ca
runtime:
name: python
options:
virtualenv: venv
description: Personal blog of Collin Doering

3
infra/__main__.py Normal file
View File

@ -0,0 +1,3 @@
"""A Python Pulumi program"""
import pulumi

View File

@ -1 +0,0 @@
../channels.scm

View File

@ -11,7 +11,7 @@ terraform {
provider "aws" {
region = var.region
version = "= 2.70.0"
version = "~> 2.15"
assume_role {
role_arn = var.workspace_iam_roles[terraform.workspace]
@ -21,23 +21,23 @@ provider "aws" {
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
version = "= 2.70.0"
version = "~> 2.1"
assume_role {
role_arn = var.workspace_iam_roles[terraform.workspace]
}
}
provider "null" {
# provider "null" {
# version = "~> 2.1"
# }
provider "random" {
version = "~> 2.1"
}
provider "random" {
version = "= 2.1.2"
}
#
# Local values to be re-used throughout
# Local values to be re-used throughout this template
locals {
common_tags = {
@ -112,6 +112,14 @@ resource "aws_route53_record" "cert_validation" {
name = aws_acm_certificate.cert.domain_validation_options[count.index]["resource_record_name"]
type = aws_acm_certificate.cert.domain_validation_options[count.index]["resource_record_type"]
ttl = 60
# TF-UPGRADE-TODO: In Terraform v0.10 and earlier, it was sometimes necessary to
# force an interpolation expression to be interpreted as a list by wrapping it
# in an extra set of list brackets. That form was supported for compatibilty in
# v0.11, but is no longer supported in Terraform v0.12.
#
# If the expression in the following list itself returns a list, remove the
# brackets to avoid interpretation as a list of lists. If the expression
# returns a single list item then leave it as-is and remove this TODO comment.
records = [aws_acm_certificate.cert.domain_validation_options[count.index]["resource_record_value"]]
}
@ -257,6 +265,14 @@ resource "aws_cloudfront_distribution" "cdn" {
bucket = aws_s3_bucket.static_logs.bucket_domain_name
}
# TF-UPGRADE-TODO: In Terraform v0.10 and earlier, it was sometimes necessary to
# force an interpolation expression to be interpreted as a list by wrapping it
# in an extra set of list brackets. That form was supported for compatibilty in
# v0.11, but is no longer supported in Terraform v0.12.
#
# If the expression in the following list itself returns a list, remove the
# brackets to avoid interpretation as a list of lists. If the expression
# returns a single list item then leave it as-is and remove this TODO comment.
aliases = [local.domain]
default_cache_behavior {
@ -343,6 +359,14 @@ resource "aws_cloudfront_distribution" "cdn_redirect" {
bucket = aws_s3_bucket.static_logs.bucket_domain_name
}
# TF-UPGRADE-TODO: In Terraform v0.10 and earlier, it was sometimes necessary to
# force an interpolation expression to be interpreted as a list by wrapping it
# in an extra set of list brackets. That form was supported for compatibilty in
# v0.11, but is no longer supported in Terraform v0.12.
#
# If the expression in the following list itself returns a list, remove the
# brackets to avoid interpretation as a list of lists. If the expression
# returns a single list item then leave it as-is and remove this TODO comment.
aliases = [local.naked_domain]
default_cache_behavior {
@ -381,40 +405,29 @@ resource "aws_cloudfront_distribution" "cdn_redirect" {
}
}
resource "null_resource" "deploy_app" {
triggers = {
always = uuid()
}
# resource "null_resource" "deploy_app" {
# triggers = {
# always = uuid()
# }
provisioner "local-exec" {
interpreter = ["bash", "-c"]
command = <<SCRIPT
set -eo pipefail;
# provisioner "local-exec" {
# interpreter = ["bash", "-c"]
# command = <<SCRIPT
# : Create temporary aws config and credentials files
# export AWS_CONFIG_FILE=$(mktemp);
# export AWS_SHARED_CREDENTIALS_FILE=$(mktemp);
: Create temporary aws config and credentials files
export AWS_CONFIG_FILE=$(mktemp);
export AWS_SHARED_CREDENTIALS_FILE=$(mktemp);
# : Add default AWS account profile;
# aws configure --profile ${aws_iam_user.app_deploy.name} set aws_access_key_id ${aws_iam_access_key.app_deploy.id};
# aws configure --profile ${aws_iam_user.app_deploy.name} set aws_secret_access_key ${aws_iam_access_key.app_deploy.secret};
# aws configure --profile ${aws_iam_user.app_deploy.name} set region ${var.region};
: Add default AWS account profile;
aws configure --profile ${aws_iam_user.app_deploy.name} set aws_access_key_id ${aws_iam_access_key.app_deploy.id};
aws configure --profile ${aws_iam_user.app_deploy.name} set aws_secret_access_key ${aws_iam_access_key.app_deploy.secret};
aws configure --profile ${aws_iam_user.app_deploy.name} set region ${var.region};
# : Sync latest app build to s3 bucket;
# aws --profile ${aws_iam_user.app_deploy.name} s3 sync --delete ../_site s3://${aws_s3_bucket.static.id}/;
: Create a random string to be used as a temporary directory name;
TMPDIR=/tmp/$(printf '%s' {a..z} {A..Z} {0..9} | fold -w1 | shuf | paste -s -d '' | head -c16);
# : Cleanup temporary aws config and credentials files
# rm $${AWS_CONFIG_FILE} $${AWS_SHARED_CREDENTIALS_FILE};
# SCRIPT
: Copy site files so that they get a new date/time stamp, allowing 's3 sync' to operate correctly;
cp -r ${var.site_static_files_dir} $${TMPDIR};
: Allow copied site files to be removable after deployment;
chmod u+rw -R $${TMPDIR};
: Sync latest app build to s3 bucket;
aws --profile ${aws_iam_user.app_deploy.name} s3 sync --delete $${TMPDIR} s3://${aws_s3_bucket.static.id}/;
: Cleanup temporary aws config, its credentials files as well as the copied site files;
rm -r $${AWS_CONFIG_FILE} $${AWS_SHARED_CREDENTIALS_FILE} $${TMPDIR};
SCRIPT
}
}
# }
# }

View File

@ -1,36 +0,0 @@
;; (C) Copyright Collin J. Doering 2021
;;
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;; File: manifest.scm
;; Author: Collin J. Doering <collin.doering@rekahsoft.ca>
;; Date: Dec 2, 2021
(use-modules
(gnu packages)
(guix packages)
(guix profiles))
(setenv "PS1" "\\W [env]\\$ ")
(setenv "AWS_PAGER" "")
(specifications->manifest
`("coreutils"
"make"
"terraform-wrapper@0.12.31"
"terraform-provider-aws"
"terraform-provider-null@2.1.2"
"terraform-provider-random"
"awscliv2"
"nss-certs"))

1
infra/requirements.txt Normal file
View File

@ -0,0 +1 @@
pulumi>=3.0.0,<4.0.0

View File

@ -0,0 +1,33 @@
{
"Version": "2008-10-17",
"Id": "StaticBucketPolicy",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Principal": {
"AWS": "${cloudfront_arn}"
},
"Action": "s3:GetObject",
"Resource": "${bucket_arn}/*"
},
{
"Sid": "2",
"Effect": "Allow",
"Principal": {
"AWS": "${user_arn}"
},
"Action": ["s3:ListBucket"],
"Resource": "${bucket_arn}"
},
{
"Sid": "3",
"Effect": "Allow",
"Principal": {
"AWS": "${user_arn}"
},
"Action": "s3:*",
"Resource": "${bucket_arn}/*"
}
]
}

View File

@ -25,6 +25,3 @@ variable "enable_naked_domain" {
default = false
}
variable "site_static_files_dir" {
default = "../_site"
}

View File

@ -27,6 +27,9 @@
/*global jQuery, MathJax*/
//------------------------
// Global array for processing piwik analytics commands
var _paq = _paq || [];
(function ($, mj) {
"use strict";
@ -193,6 +196,49 @@
return spec;
}()),
analytics = (function () {
var inited = false,
spec = {
trackPageView: trackPageView,
debugEnable: function () {
init();
}
};
function trackPageView (href) {
if (inited) {
_paq.push(["setDocumentTitle", document.domain + href]);
_paq.push(["trackPageView"]);
}
}
function init() {
if (!inited) {
_paq.push(["setDoNotTrack", true]);
_paq.push(["enableLinkTracking"]);
_paq.push(["setTrackerUrl", "//analytics.rekahsoft.ca/piwik.php"]);
_paq.push(["setSiteId", 1]);
inited = true;
}
}
// Initialize piwik.js when site is initially loaded
router.onInit(function () {
if (document.domain != "localhost") {
init();
trackPageView('/');
}
});
// Track page views with piwik each time the url changes
router.onChange(function (url, dta) {
trackPageView(url);
});
return spec
}()),
site = (function () {
var status = (function () {
var messages = [],
@ -299,7 +345,17 @@
status: status
};
function appCacheUpdateReady () {
window.applicationCache.swapCache();
// TODO: find what resource is loaded currently and reload it if it has changed
}
function init() {
window.addEventListener("updateready", appCacheUpdateReady);
if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
appCacheUpdateReady();
}
// TODO: deal with jsUrls function which is moved to the router
$(document).ready(function () {
// Add anchor click handlers for internal links

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ This website was proudly made with open source software! Specifically:
- [Clay][]
- [Skeleton][]
- [JQuery][]
- [JQuery-address][]
- [MathJax][]
- [Inkscape][]
- [Gimp][]
@ -27,7 +28,7 @@ Terms of Use
============
------------
This site is _Copyright 2016-2023 © - Collin Doering_ and is distributed under the following terms.
This site is _Copyright 2016 © - Collin Doering_ and is distributed under the following terms.
1. All rights reserved on the "#! λ Slang" name, as well as on the
[banner image](/images/logo-banner.svg), favicons([1](/images/favicon.ico),

View File

@ -10,8 +10,8 @@ Currently as some may have noticed, the "See Comments" link under each individua
does nothing. This is because I have been struggling to find a good solution to handle
comments. Of course many people choose to use Disqus. Unfortunately due to the proprietary
nature of Disqus, I refuse to use it (See
[this](http://web.archive.org/web/20150323224820/http://blog.irukado.org/2013/12/disqus-considered-harmful/)).
There are some open source solutions including:
[this](http://blog.irukado.org/2013/12/disqus-considered-harmful/)). There are some open source
solutions including:
[Isso](http://posativ.org/isso/)
~ - Written in Python.
@ -25,12 +25,14 @@ There are quite a few others, though I haven't spent much time investigating the
them are incomplete or unmaintained. See <https://news.ycombinator.com/item?id=6818416> for a
decent discussion of "Disqus Alternatives".
I've tried Isso but because my blog is a Single Page Application (SPA) where pages can be
addressed directly or using a anchor url. For example, this post can be reached via
[/#/posts/about-comments.html]() or [/posts/about-comments.html](). Forgive my vague
explanation, its been a few months since I last played with Isso. Its too bad I couldn't get
it to function because from all the open source options, it seems to be the most mature
solution.
I've tried Isso but because my blog is a Single Page Application (SPA) Isso doesn't function
correctly. Isso expects the current URL to be a direct link to the post html file that is being
commented on, but in the case of my blog it is a virtual url (Eg. this post,
<http://blog.rekahsoft.ca/#/posts/about-comments.html> doesn't work with Isso but
<http://blog.rekahsoft.ca/posts/about-comments.html> would but links directly to the html snippet
file instead of the entire post page.Forgive my vague explanation, its been a few months since
I last played with Isso. Its too bad I couldn't get it to function because from all the open
source options, it seems to be the most mature solution.
Another option that came to mind is to use a sub-reddit for my blog and post new threads for
each blog post as a way for people to submit comments. I don't like this as the content

View File

@ -137,6 +137,6 @@ to obtain a *FPGA* and complete the implementation (will a VGA screen and a PS2
keyboard). Also just to reintegrate, for those of those interested in the inner workings of a
computer, I highly recommend checking out the *Nand to Tetris* course.
[hack-git]: https://git.rekahsoft.ca/rekahsoft/hack/
[hack-docs]: https://git.rekahsoft.ca/rekahsoft/hack/src/branch/master/README.md
[hack-git]: http://git.rekahsoft.ca/hack/
[hack-docs]: http://git.rekahsoft.ca/hack/about
[GtkWave]: http://gtkwave.sourceforge.net/

109
site Executable file
View File

@ -0,0 +1,109 @@
#!/bin/bash
function run_site_only() {
[ "$SITE_ONLY" == "true" ]
}
function run_override_only() {
[ "$OVERRIDE_ONLY" == "true" ]
}
case "$1" in
-)
OVERRIDE_ONLY=true
shift
;;
--)
SITE_ONLY=true
shift
;;
esac
case "$1" in
test)
if [[ "$2" == "-s" || "$2" == "--run-selenium" ]]; then
if ! type selenium &> /dev/null; then
echo "Failed to run Selenium. It must not be installed or not accessible on \$PATH!"
exit 1
fi
echo "Running Selenium..."
selenium 2> /dev/null &
sleep 3s
fi
# Test site
stack test
;;
gencss)
shift
stack exec gencss -- "$@"
;;
# Override of hakyll site commands
-h|--help)
run_override_only || ! run_site_only && cat << EOF
Wraps hakyll's provided site tool to augment certain commands.
Usage:
./site [-|--] COMMAND
Available commands:
build*
clean*
deploy*
gencss
test
Hakyll site commands:
build
check
clean
deploy
preview
rebuild
server
watch
Starred (*) commands indicate a overridden hakyll site command. However once the override is
run, the corresponding hakyll command is then run. This can be disabled with by specifying '--'
as the first argument, which will then pass all remaining arguments to the hakyll site command.
Similarily, to only run the override, specify '-' as the first argument.
For more details about hakyll site commands and options, see './site -- --help'.
EOF
# Only run hakyll site --help command if override was not run
run_override_only && exit
# Only run hakyll site --help command if -- site only
run_site_only && stack exec blog-rekahsoft-ca -- --help | sed 's/\(Usage: \)blog-rekahsoft-ca/\1.\/site -/g'
;;
build)
run_override_only || ! run_site_only && stack build
run_override_only && exit $?
;;&
clean)
run_override_only || ! run_site_only && stack clean
run_override_only && exit $?
;;&
deploy)
pushd infra > /dev/null
# Only run hakyll site deploy command when site-only is given. Additionally, when
# neither site-only or override-only are given, run only the override. The deploy
# override uses terraform which is also setup to deploy the hakyll site static files
run_override_only || ! run_site_only && (
export PLAN=".plans/local-$(date +%F_%R).plan"
[ ! -d .plans ] && mkdir .plans
make plan deploy
) && exit $?
run_site_only && export S3_BUCKET="$(terraform output s3_bucket_static)"
popd > /dev/null
;;&
*)
stack exec blog-rekahsoft-ca -- "$@"
;;
esac

View File

@ -24,10 +24,15 @@
import Hakyll
import Control.Monad
import Data.Monoid (mconcat,(<>))
import Data.List (sortBy)
import Data.Map (toList)
import Data.Ord (comparing)
import System.Random
import System.FilePath (takeBaseName)
import System.Process
import System.Exit
import System.IO (hGetContents)
import Text.Parsec
import Text.Pandoc.Options
@ -84,167 +89,212 @@ pandocWriterOptions = defaultHakyllWriterOptions
myConfig :: Configuration
myConfig = defaultConfiguration
{ deployCommand = "echo 'TODO (what to do with this cmd) Deploying website...' && " ++
{ deployCommand = "echo 'Deploying website...' && " ++
"aws s3 sync _site/ s3://$S3_BUCKET &&" ++
"echo 'Done!'"
, previewPort = 3000
}
siteRules :: Rules ()
siteRules = do
match ("js/**"
.||. "files/**"
.||. "images/**"
.||. "fonts/**"
.||. "robots.txt") $ do
route idRoute
compile copyFileCompiler
main :: IO ()
main = do
-- Get a random number generator before going into Rules monad
stdGen <- getStdGen
forM_ [("lib/MathJax/fonts/HTML-CSS/**", gsubRoute "lib/MathJax/" $ const ""),
("lib/MathJax/**" .&&. complement "lib/MathJax/fonts", gsubRoute "lib/" $ const ""),
("lib/JQuery/*", gsubRoute "JQuery" $ const "js")] $ \(p, r) ->
match p $ do
route r
compile $ copyFileCompiler
hakyllWith myConfig $ do
match ("action/**" .||. "files/**" .||. "images/**" .||. "fonts/**" .||. "robots.txt") $ do
route idRoute
compile copyFileCompiler
match "css/**" $ do
route idRoute
compile compressCssCompiler
match "css/**" $ do
route idRoute
compile compressCssCompiler
match "lib/Skeleton/*.css" $ do
route $ gsubRoute "Skeleton" (const "css")
compile compressCssCompiler
match "lib/Skeleton/*.css" $ do
route $ gsubRoute "Skeleton" (const "css")
compile compressCssCompiler
match "templates/**" $ compile $ getResourceBody >>= saveSnapshot "original"
>> templateCompiler
match "templates/**" $ compile $ getResourceBody >>= saveSnapshot "original"
>> templateCompiler
-- Generate tags
tags <- buildTags ("posts/**" .&&. hasNoVersion) (fromCapture "tags/*1.html")
-- Generate tags
tags <- buildTags ("posts/**" .&&. hasNoVersion) (fromCapture "tags/*1.html")
-- Generate paginate
paginatedPosts <- buildPaginateWith
(fmap (paginateEvery numPaginatePages) . sortRecentFirst)
("posts/**" .&&. hasNoVersion)
(\n -> fromCapture "blog*.html" (show n))
-- Generate paginate
paginatedPosts <- buildPaginateWith
(fmap (paginateEvery numPaginatePages) . sortRecentFirst)
("posts/**" .&&. hasNoVersion)
(\n -> fromCapture "blog*.html" (show n))
clayDeps <- makePatternDependency $ fromGlob "clay/*.hs"
pageIds <- getMatches ("pages/**" .&&. complement "pages/blog.markdown")
fontIds <- getMatches "fonts/**"
imageIds <- getMatches "images/**"
cssIds <- getMatches "css/**"
jsIds <- getMatches "js/**"
libIds <- getMatches "lib/**"
rulesExtraDependencies [clayDeps] $ create ["default.css"] $ do
route idRoute
compile $ makeItem =<< (unsafeCompiler $ readProcess "gencss" ["compact"] "")
clayIds <- getMatches "clay/**.hs"
let manifestIds = clayIds ++ fontIds ++ imageIds ++ pageIds ++ cssIds ++ libIds ++ jsIds
-- Generate tag pages
forM_ (tagsMap tags) $ \(tag, identifiers) -> do
paginatedTaggedPosts <- buildPaginateWith
(fmap (paginateEvery numPaginatePages) . sortRecentFirst)
(fromList identifiers)
(\n -> fromCapture (fromGlob $ "tags/" ++ tag ++ "*.html") (show n))
clayDeps <- makePatternDependency $ fromList clayIds
manifestDeps <- makePatternDependency $ fromList manifestIds
paginateRules paginatedTaggedPosts $ \pageNum pattern -> do
route $ gsubRoute " " (const "-") `composeRoutes` setExtension "html"
rulesExtraDependencies [clayDeps] $ create ["default.css"] $ do
route idRoute
compile $ makeItem =<< (unsafeCompiler $ do
(_, hout, _, ph) <- createProcess $ shell "stack build blog-rekahsoft-ca:gencss"
exitCode <- waitForProcess ph
if exitCode == ExitSuccess
then readProcess "stack" ["exec", "gencss", "--", "compact"] ""
else case hout of
Nothing -> fail "Error running 'stack build blog-rekahsoft-ca:gencss'"
Just hout' -> hGetContents hout' >>= fail)
rulesExtraDependencies [manifestDeps] $ create ["manifest.appcache"] $ do
route idRoute
compile $ do
manifestCacheRoutesMaybe <- sequence $ liftM getRoute (fontIds ++ pageIds ++ imageIds ++ cssIds ++ libIds ++ jsIds)
let randomNum = random stdGen :: (Int, StdGen)
randomStr = show . abs . fst $ randomNum
manifestStart = [ "CACHE MANIFEST"
, "# " ++ randomStr ]
manifestCacheSingles = [ "/default.css" ]
paginatedPostsCache = take 2 $ map (\(n,_) -> "/blog" ++ (show n) ++ ".html") $ toList $ paginateMap paginatedPosts
tagsCache = concatMap (\(t,ids) -> take 2 $ ["/tags/" ++ t ++ show n ++ ".html" | n <- [1..length $ paginateEvery numPaginatePages ids]]) $ tagsMap tags
manifestCacheFromIds = filter (not . null) $ fmap (maybe "" ("/"++)) manifestCacheRoutesMaybe
manifestCache = manifestCacheFromIds ++ tagsCache ++ paginatedPostsCache
manifestNetwork = [ "NETWORK:"
, "*"
, "http://*"
, "https://*" ]
makeItem . unlines $ manifestStart ++ [""] ++
manifestCacheSingles ++ manifestCache ++ [""] ++
manifestNetwork ++ [""]
match "css/**" $ do
route idRoute
compile compressCssCompiler
match "lib/Skeleton/*.css" $ do
route $ gsubRoute "Skeleton" (const "css")
compile compressCssCompiler
match "templates/**" $ compile $ getResourceBody >>= saveSnapshot "original"
>> templateCompiler
-- Generate tag pages
forM_ (tagsMap tags) $ \(tag, identifiers) -> do
paginatedTaggedPosts <- buildPaginateWith
(fmap (paginateEvery numPaginatePages) . sortRecentFirst)
(fromList identifiers)
(\n -> fromCapture (fromGlob $ "tags/" ++ tag ++ "*.html") (show n))
paginateRules paginatedTaggedPosts $ \pageNum pattern -> do
route $ gsubRoute " " (const "-") `composeRoutes` setExtension "html"
compile $ do
posts <- recentFirst =<< loadAllSnapshots pattern "content"
navCtx <- genNavContext "pages/blog.markdown"
let ctx = taggedPostCtx tags <>
paginateContext paginatedTaggedPosts pageNum <>
constField "tag" tag <>
listField "posts" (taggedPostCtx tags) (return posts)
indexCtx = if pageNum <= 2
then appCacheCtx <> navCtx
else navCtx
makeItem ""
>>= loadAndApplyTemplate "templates/tag-page.html" ctx
>>= loadAndApplyTemplate "templates/default.html" indexCtx
rulesExtraDependencies [tagsDependency tags] $ do
create [fromFilePath $ "tags/" ++ tag ++ ".xml"] $ do
route $ gsubRoute " " (const "-") `composeRoutes` setExtension "xml"
compile $ loadAllSnapshots (fromList identifiers) "content"
>>= fmap (take 10) . recentFirst
>>= renderAtom (feedConfiguration $ Just tag) (bodyField "description" <> defaultContext)
let pageRoute = gsubRoute "pages/" (const "") `composeRoutes` setExtension "html"
match ("pages/*" .&&. complement "pages/blog.markdown") $ version "nav-gen" $ do
route $ pageRoute
compile $ pandocCompiler
match "pages/blog.markdown" $ version "nav-gen" $ do
route $ constRoute "blog1.html"
compile $ pandocCompiler
paginateRules paginatedPosts $ \pageNum pattern -> do
route idRoute
compile $ do
posts <- recentFirst =<< loadAllSnapshots pattern "content"
navCtx <- genNavContext "pages/blog.markdown"
let ctx = taggedPostCtx tags <>
paginateContext paginatedTaggedPosts pageNum <>
constField "tag" tag <>
paginateContext paginatedPosts pageNum <>
listField "posts" (taggedPostCtx tags) (return posts)
indexCtx = navCtx
indexCtx = if pageNum <= 2
then appCacheCtx <> navCtx
else navCtx
makeItem ""
>>= loadAndApplyTemplate "templates/tag-page.html" ctx
>>= loadAndApplyTemplate "templates/pages/blog.html" ctx
>>= loadAndApplyTemplate "templates/default.html" indexCtx
rulesExtraDependencies [tagsDependency tags] $ do
create [fromFilePath $ "tags/" ++ tag ++ ".xml"] $ do
route $ gsubRoute " " (const "-") `composeRoutes` setExtension "xml"
compile $ loadAllSnapshots (fromList identifiers) "content"
>>= fmap (take 10) . recentFirst
>>= renderAtom (feedConfiguration $ Just tag) (bodyField "description" <> defaultContext)
match ("pages/*" .&&. complement "pages/blog.markdown") $ do
route $ pageRoute
compile $ do
posts <- recentFirst =<< loadAllSnapshots "posts/**" "content"
let pageRoute = gsubRoute "pages/" (const "") `composeRoutes` setExtension "html"
-- Get the current Identifier
curId <- getUnderlying
match ("pages/*" .&&. complement "pages/blog.markdown") $ version "nav-gen" $ do
route $ pageRoute
compile $ pandocCompiler
let pageFilePath = toFilePath curId
pageName = takeBaseName pageFilePath
recentPosts = take 5 posts
pageTemplate = "templates/pages/" ++ pageName ++ ".html"
match "pages/blog.markdown" $ version "nav-gen" $ do
route $ constRoute "blog1.html"
compile $ pandocCompiler
-- Generate navigation context
navCtx <- genNavContext pageFilePath
paginateRules paginatedPosts $ \pageNum pattern -> do
route idRoute
compile $ do
posts <- recentFirst =<< loadAllSnapshots pattern "content"
let masterCtx =
listField "recentPosts" (taggedPostCtx tags) (return recentPosts) <>
listField "posts" (taggedPostCtx tags) (return posts) <>
tagCloudField "tagCloud" 65 135 tags <>
defaultContext
indexCtx = navCtx <> appCacheCtx
navCtx <- genNavContext "pages/blog.markdown"
sectionCtx <- getResourceBody >>= genSectionContext
pg <- loadSnapshot (fromFilePath pageTemplate) "original"
>>= applyAsTemplate (sectionCtx <> masterCtx)
let ctx = taggedPostCtx tags <>
paginateContext paginatedPosts pageNum <>
listField "posts" (taggedPostCtx tags) (return posts)
indexCtx = navCtx
(makeItem . itemBody) pg
>>= loadAndApplyTemplate "templates/default.html" indexCtx
makeItem ""
>>= loadAndApplyTemplate "templates/pages/blog.html" ctx
>>= loadAndApplyTemplate "templates/default.html" indexCtx
match "posts/**" $ do
route $ setExtension "html"
compile $ do
indexCtx <- genNavContext "pages/blog.markdown"
match ("pages/*" .&&. complement "pages/blog.markdown") $ do
route $ pageRoute
compile $ do
posts <- recentFirst =<< loadAllSnapshots "posts/**" "content"
pandocCompilerWith pandocReaderOptions pandocWriterOptions
>>= saveSnapshot "content"
>>= loadAndApplyTemplate "templates/partials/post.html" (taggedPostCtx tags)
>>= loadAndApplyTemplate "templates/default.html" indexCtx
-- Get the current Identifier
curId <- getUnderlying
create ["atom.xml"] $ do
route idRoute
compile $ do
let feedCtx = postCtx <> bodyField "description"
blogPosts <- loadAllSnapshots ("posts/**" .&&. hasNoVersion) "content"
>>= fmap (take 10) . recentFirst
renderAtom (feedConfiguration Nothing) feedCtx blogPosts
let pageFilePath = toFilePath curId
pageName = takeBaseName pageFilePath
recentPosts = take 5 posts
pageTemplate = "templates/pages/" ++ pageName ++ ".html"
-- Generate navigation context
navCtx <- genNavContext pageFilePath
let masterCtx =
listField "recentPosts" (taggedPostCtx tags) (return recentPosts) <>
listField "posts" (taggedPostCtx tags) (return posts) <>
tagCloudField "tagCloud" 65 135 tags <>
defaultContext
indexCtx = navCtx
sectionCtx <- getResourceBody >>= genSectionContext
pg <- loadSnapshot (fromFilePath pageTemplate) "original"
>>= applyAsTemplate (sectionCtx <> masterCtx)
(makeItem . itemBody) pg
>>= loadAndApplyTemplate "templates/default.html" indexCtx
match "posts/**" $ do
route $ setExtension "html"
compile $ do
indexCtx <- genNavContext "pages/blog.markdown"
pandocCompilerWith pandocReaderOptions pandocWriterOptions
>>= saveSnapshot "content"
>>= loadAndApplyTemplate "templates/partials/post.html" (taggedPostCtx tags)
>>= loadAndApplyTemplate "templates/default.html" indexCtx
create ["atom.xml"] $ do
route idRoute
compile $ do
let feedCtx = postCtx <> bodyField "description"
blogPosts <- loadAllSnapshots ("posts/**" .&&. hasNoVersion) "content"
>>= fmap (take 10) . recentFirst
renderAtom (feedConfiguration Nothing) feedCtx blogPosts
_devWatch :: IO ()
_devWatch = do
_ <- hakyllWithExitCodeAndArgs myConfig (Options { verbosity = False, optCommand = Rebuild }) $ siteRules
hakyllWithArgs myConfig (Options { verbosity = False, optCommand = Watch "localhost" 3000 False }) $ siteRules
main :: IO ()
main = hakyllWith myConfig siteRules
forM_ [("js/**", idRoute),
("lib/JQuery/*", gsubRoute "JQuery" $ const "js")] $ \(p, r) ->
match p $ do
route r
compile $ compressCssCompiler
---------------------------------------------------------------------------------------------------------
-- Functions & Constants --------------------------------------------------------------------------------
@ -284,6 +334,9 @@ postCtx = dateField "date" "%B %e, %Y" <>
taggedPostCtx :: Tags -> Context String
taggedPostCtx tags = tagsField "tags" tags <> postCtx
appCacheCtx :: Context String
appCacheCtx = constField "appcache" "true"
pageWeight :: (Functor f, MonadMetadata f) => Item a -> f Int
pageWeight i = fmap (maybe 0 read) $ getMetadataField (itemIdentifier i) "weight"

65
stack.yaml Normal file
View File

@ -0,0 +1,65 @@
# This file was automatically generated by 'stack init'
#
# Some commonly used options have been documented as comments in this file.
# For advanced use and comprehensive documentation of the format, please see:
# https://docs.haskellstack.org/en/stable/yaml_configuration/
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
# A snapshot resolver dictates the compiler version and the set of packages
# to be used for project dependencies. For example:
#
# resolver: lts-3.5
# resolver: nightly-2015-09-21
# resolver: ghc-7.10.2
# resolver: ghcjs-0.1.0_ghc-7.10.2
#
# The location of a snapshot can be provided as a file or url. Stack assumes
# a snapshot provided as a file might change, whereas a url resource does not.
#
# resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: lts-12.0
# User packages to be built.
# Various formats can be used as shown in the example below.
#
# packages:
# - some-directory
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
# - location:
# git: https://github.com/commercialhaskell/stack.git
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
# subdirs:
# - auto-update
# - wai
packages:
- .
# Dependency packages to be pulled from upstream that are not in the resolver
# using the same syntax as the packages field.
# (e.g., acme-missiles-0.3)
# extra-deps: []
# Override default flag values for local packages and extra-deps
# flags: {}
# Extra package databases containing global packages
# extra-package-dbs: []
# Control whether we use the GHC we find on the path
system-ghc: true
#
# Require a specific version of stack, using version ranges
# require-stack-version: -any # Default
# require-stack-version: ">=1.7"
#
# Override the architecture used by stack, especially useful on Windows
# arch: i386
# arch: x86_64
#
# Extra directories used by stack for building
# extra-include-dirs: [/path/to/dir]
# extra-lib-dirs: [/path/to/dir]
#
# Allow a newer minor version of GHC than the snapshot specifies
# compiler-check: newer-minor

12
stack.yaml.lock Normal file
View File

@ -0,0 +1,12 @@
# This file was autogenerated by Stack.
# You should not edit this file by hand.
# For more information, please see the documentation at:
# https://docs.haskellstack.org/en/stable/lock_files
packages: []
snapshots:
- completed:
size: 499178
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/12/0.yaml
sha256: 3e9a7b96708cd9196ce7e5396143725097a71f2e9ca8dc19f03f5082642bc1b5
original: lts-12.0

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html class="en">
<html class="en" $if(appcache)$manifest="/manifest.appcache"$endif$>
<head>
<!-- Basic Page Needs -->
<meta charset="utf-8">
@ -24,7 +24,9 @@
</head>
<body>
$partial("templates/partials/analytics.html")$
<!-- Piwik -->
<script type="text/javascript" async defer src="//analytics.rekahsoft.ca/piwik.js"></script>
<noscript><img src="//analytics.rekahsoft.ca/piwik.php?idsite=1" style="border:0;display:none;" alt="" /></noscript>
$partial("templates/partials/logo-banner.html")$
$partial("templates/partials/nav.html")$
@ -49,9 +51,8 @@
$partial("templates/partials/footer.html")$
<!-- External javascript libraries: JQuery, MathJax -->
<script src="/lib/js/jquery-1.12.3.js" type="text/javascript"></script>
<script src="/MathJax/MathJax.js" type="text/javascript"></script>
<script src="/MathJax/config/TeX-MML-AM_CHTML.js" type="text/javascript"></script>
<script src="/lib/js/jquery-1.11.2.js" type="text/javascript"></script>
<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type="text/javascript"></script>
<!-- Custom javascript for user interactivity -->
<script src="/js/default.js" type="text/javascript"></script>

View File

@ -1,21 +0,0 @@
<script>
var ref = document.createElement("script");
ref.setAttribute("type", "text/javascript");
var script = "https://analytics.rekahsoft.ca/js/plausible.js";
switch (window.location.hostname) {
case "www.blog.rekahsoft.ca":
ref.setAttribute("data-domain", "blog.rekahsoft.ca");
break;
case "www.blog.staging.rekahsoft.ca":
ref.setAttribute("data-domain", "blog.staging.rekahsoft.ca");
break;
default:
script = "https://analytics.rekahsoft.ca/js/plausible.exclusions.local.js";
ref.setAttribute("data-domain", "localhost:3000");
ref.setAttribute("data-exclude", "localhost:3000");
}
ref.setAttribute("src", script);
document.getElementsByTagName("head")[0].appendChild(ref)
</script>

View File

@ -2,7 +2,7 @@
<div class="container">
<footer>
<div class="six columns alpha" id="footer-left">
<p>&copy; Collin Doering 2016-2023</p>
<p>&copy; Collin Doering 2016</p>
</div>
<div class="six columns omega" id="footer-right">
<p>