Compare commits
1 Commits
master
...
poc-pulumi
Author | SHA1 | Date |
---|---|---|
Collin J. Doering | 364a51f21a |
|
@ -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"
|
||||
}),
|
||||
])
|
||||
]
|
116
.drone.yml
116
.drone.yml
|
@ -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
|
||||
|
|
|
@ -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
10
.envrc
|
@ -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
3
.ghci
|
@ -1,3 +0,0 @@
|
|||
:set -fwarn-unused-binds -fwarn-unused-imports
|
||||
:set -isrc
|
||||
:load src/site.hs
|
|
@ -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/
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||
|
|
|
@ -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/
|
390
README.org
390
README.org
|
@ -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]].
|
|
@ -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.
|
||||
|
|
|
@ -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
|
20
channels.scm
20
channels.scm
|
@ -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")))))
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
85
guix.scm
85
guix.scm
|
@ -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
|
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
encryptionsalt: v1:41djbtbdfn8=:v1:nmi4l6XY2PicKzLx:b50NBop6ZJ24hPXiuLA8DOF/vwa3/g==
|
|
@ -0,0 +1,6 @@
|
|||
name: blog-rekahsoft-ca
|
||||
runtime:
|
||||
name: python
|
||||
options:
|
||||
virtualenv: venv
|
||||
description: Personal blog of Collin Doering
|
|
@ -0,0 +1,3 @@
|
|||
"""A Python Pulumi program"""
|
||||
|
||||
import pulumi
|
|
@ -1 +0,0 @@
|
|||
../channels.scm
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
# }
|
||||
# }
|
||||
|
|
|
@ -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"))
|
|
@ -0,0 +1 @@
|
|||
pulumi>=3.0.0,<4.0.0
|
|
@ -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}/*"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -25,6 +25,3 @@ variable "enable_naked_domain" {
|
|||
default = false
|
||||
}
|
||||
|
||||
variable "site_static_files_dir" {
|
||||
default = "../_site"
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
303
src/site.hs
303
src/site.hs
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -2,7 +2,7 @@
|
|||
<div class="container">
|
||||
<footer>
|
||||
<div class="six columns alpha" id="footer-left">
|
||||
<p>© Collin Doering 2016-2023</p>
|
||||
<p>© Collin Doering 2016</p>
|
||||
</div>
|
||||
<div class="six columns omega" id="footer-right">
|
||||
<p>
|
||||
|
|
Loading…
Reference in New Issue