Compare commits

...

75 Commits

Author SHA1 Message Date
Collin J. Doering d194575f06
infra/manifest.scm: Remove unused template provider 2023-12-26 21:07:18 -05:00
Collin J. Doering 2ebb239e8c
README.org: Add section 'Verifying a Release' 2023-12-26 21:06:50 -05:00
Collin J. Doering e4032ba6b3
site: Vendor MathJax from guix, removing dependency on CDN
* .gitignore: Ignore lib/MathJax symlink.
* README.org: Variety of changes, most notably using the README.org file as a literate source
file, which generates various development scripts and a Makefile. One such script is the
vendoring script, which is used to setup vendor symlinks.
* bootstrap.sh: Run emacs to tangle the literate README.org file, after which, run setup
vendored dependency links.
* guix.scm: Depend on js-mathjax, and vendor its built files into lib/MathJax before building
the site.
* pages/index.markdown: Remove stale reference to jquery-address.
* src/site.hs: Adjust hakyll rules to include MathJax from its now locally vendored location.
Additionally, do a tiny bit of rule tidying.
* templates/default.html: Remove stale reference to appcache. Remove reference to MathJax via
cdn and reference the now vendored variant.
2023-12-26 18:01:14 -05:00
Collin J. Doering bad2d07ce9
Improve development environment setup and documentation
* src/site.hs (siteRules): Refactor site building Rules into a new variable allowing for
reuse. Remove duplicate rules ()
(myConfig): Set deployCommand to something temporary.
(_devWatch): Entrypoint mean for use in development; it rebuilds the site builder, then
rebuilds the site.

* site (Removed file): Removed as it is no longer necessary. If additional cli functionality
is required, this can be done directly in haskell.

* guix.scm: Symlink 'site' now that it is no longer provided as a script.
(setenv): setenv only applies to the first (non-cached) run of `guix shell ...`,
because of this, remove setting PS1.
(native-inputs): Include development environment dependencies

* channels.scm (channel): Update rekahsoft-guix channel (noop, but originally updated while
looking at using overmind).

* bootstrap.sh: A script that generates various development scripts from the projects
README.org file.

* README.org: Cleanup and minor restructuring.
(Repository Structure): New (incomplete) section
(Prerequisites): New section
(Quick Start): New section
(Hakyll Site Commands): New section, with subheadings for various hakyll sub-commands.
(Makefile): New section; generates a makefile useful for development

* .gitignore: Ignore files generated by ./bootstrap.sh

* .ghci: ghci configuration file for hakyll development.

* .envrc: direnv configuration that loads a development environment for those who have
allowed it
2023-06-03 00:10:33 -04:00
Collin J. Doering fa89f9f158
Fix broken links (discovered via 'site check') 2023-05-28 22:04:47 -04:00
Collin J. Doering ce9d1fe54c
Update guix channels; make necessary adjustments (new ghc & clay)
* channels.scm (channel): Update all channels (guix and rekahsoft-guix)

* blog-rekahsoft-ca.cabal (cabal-version): Adjust base version now that ghc version 9.2.5 is in use; adjust clay to match ghc-clay package provided by updated guix channels.

* clay/*.hs: Adjust clay sources for api changes (swapping of function parameter order for
functions provided by the 'Clay.Border' module)
2023-05-20 22:32:21 -04:00
Collin J. Doering c51be05eb4
infra/main.tf: Correct typo in b9bee14 2023-05-04 23:00:14 -04:00
Collin J. Doering b9bee14f30
infra/main.tf: Adjust deployment script to account for unchange site file timestamps
* infra/main.tf: See 93dfdb0 for more detail regarding the deployment issue. In addition,
have the deployment script correctly fail if any command does not succeed.
2023-05-04 22:55:55 -04:00
Collin J. Doering b11aa217af
infra/main.tf: Correct typo in 93dfdb0 2023-05-02 22:01:23 -04:00
Collin J. Doering 93dfdb08fd
infra/main.tf: Use --size-only option until 'aws s3 sync' can use checksums
* infra/main.tf: This avoid a deployment issue after switching to guix for builds. Namely,
after to switching to guix to build this project, all built site files will have a datetime
of Unix epoch 0, which will then never be updated when running 'aws s3 sync ...' because the
files in the bucket were deployed before the site was built using guix, so they have newer
timestamps. Using the '--size-only' option improves this situation, in that files that are
modified and have a different size then the original will be updated, but files that are
changed but, by chance have the same size, will not be updated. Ironically, the update that I
discovered this on will end up resulting the same file sizes (changing the copyright date).
2023-05-02 19:14:36 -04:00
Collin J. Doering cee5d48466
guix.scm: Reuse %srcdir instead of calling getcwd 2023-04-30 21:16:37 -04:00
Collin J. Doering 143b38c89c
TODO.org: Remove TODOs 2023-04-30 17:21:00 -04:00
Collin J. Doering f16712cc38
Update copyright year in a variety of files 2023-04-30 13:24:05 -04:00
Collin J. Doering 54c48b55b8
infra: Remove unused template provider 2022-10-16 21:24:00 -04:00
Collin J. Doering d02d329bbc
TODO.org: Move Archive section to the top and add a new TODO item 2021-12-18 21:12:31 -05:00
Collin J. Doering 6b1129aa1a
TODO.org: Mark completed, but forgotten tasks and create Archive heading 2021-12-18 20:44:29 -05:00
Collin J. Doering 9171aa88ad
Replace broken piwiki analytics with self-hosted plausible
* templates/partials/analytics.html: Upon a client load of the website, determine which site
they are viewing (dev, staging, production) and setup plausible analytics appropriately.

* templates/default.html: Remove non-functional piwiki script reference and replace with a
reference to the new partial template for analytics

* src/site.hs (main): Remove remnant of application cache support

* js/default.js: Remove unneeded analytics code specific to piwiki. No additions were made here in support of plausible

* TODO.org: Mark associated TODO item DONE
2021-12-18 20:37:59 -05:00
Collin J. Doering 913c2f0e67
.drone/ci.libsonnet: Add and renamed methods relocated from .drone.jsonnet: guix.pipeline, guix.step, awsDeployStep
* .drone.yml: Update file after updates to .drone.jsonnet

* .drone.jsonnet: Relocate guix_pipeline, guix_step, and deployStep to .drone/ci.libsonnet
library. Adjust existing function names to match new names in library.

Explicitly pass PLAN environment variable to ci.awsDeployStep for plan and apply stages
2021-12-11 13:19:55 -05:00
Collin J. Doering eaf41637ab
.gitignore: Do not ignore hidden files 2021-12-11 12:53:30 -05:00
Collin J. Doering 284abdf916
.drone.jsonnet: Move ci local to its own library in .drone/ci.libsonnet 2021-12-11 12:53:01 -05:00
Collin J. Doering faad56813b
.drone.jsonnet: Remove setting of root user in /etc/passwd
Adding the root user to /etc/passwd was only required when attempting to run guix shell
`-C|--container' commands, however guix shell containers will not work without privileged
docker, so when running via ci, we depend on the provided minimal guix container (build with
guix via 'guix pack -f docker ...').
2021-12-10 22:32:42 -05:00
Collin J. Doering 572f00a79a
README.org: Set drone ci badge to use the master branch 2021-12-09 23:18:41 -05:00
Collin J. Doering 4a1bbf41bf
.drone.jsonnet: Remove unnecessary comment 2021-12-09 23:18:20 -05:00
Collin J. Doering 1c5aeba23e
.dron.jsonnet: Reorganize jsonnet; no change to yaml output 2021-12-09 23:03:53 -05:00
Collin J. Doering 7fc316a171
README.org: Update drone status badge after repository move 2021-12-09 20:42:17 -05:00
Collin J. Doering b9ee2f8266
.drone.jsonnet: Addionally wait for pending status during promotion 2021-12-09 20:11:33 -05:00
Collin J. Doering 5c4138d4d4
.drone.jsonnet: Correct promoted pipeline status checking 2021-12-09 20:08:46 -05:00
Collin J. Doering 9b167b4d9d
.drone.jsonnet: Export DRONE_TOKEN so its available in subprocess 2021-12-09 19:37:02 -05:00
Collin J. Doering 880978c9a1
.drone.jsonnet: Use correct site build during ci
This makes the assumption that the guix store is available from all drone-ci nodes
2021-12-09 18:12:57 -05:00
Collin J. Doering f11fbdd3f4
infra/Makefile: Remove CI env var check for terraform workspace switching 2021-12-09 12:50:08 -05:00
Collin J. Doering ec708bcf29
Revert ".drone.jsonnet: TEST - why is the setup step not switching workspaces?"
This reverts commit 5d00301c86.
2021-12-09 12:49:52 -05:00
Collin J. Doering 5d00301c86
.drone.jsonnet: TEST - why is the setup step not switching workspaces? 2021-12-09 12:42:00 -05:00
Collin J. Doering b162752ce6
.drone.jsonnet: Do not use guix shell containerization as it fails within non-privileged docker 2021-12-09 12:17:43 -05:00
Collin J. Doering bc19c03d75
.drone.jsonnet: Fix issue specific to running guix shell/environment via drone-ci 2021-12-09 11:59:51 -05:00
Collin J. Doering 6a00da6970
.drone.jsonnet: Explicitly specify manifest to use for deploySteps 2021-12-09 10:42:47 -05:00
Collin J. Doering 920fe7d9f4
Enhance drone pipeline and switch to jsonnet based configuration
* .drone.yml: This is now a generated file, based on .drone.jsonnet

* .drone.jsonnet: Add jsonnet drone pipeline configuration to replace existing .drone.yml
file. Note, currently this is used to manually generate .drone.yml. I need to determine how
to handle jsonnet libraries effectively as drone's built in jsonnet support does not allow
for importing libraries. In addition to converting to jsonnet, the pipeline was enhanced with
promotion and deployment functionality that has not yet been tested.
2021-12-09 10:34:37 -05:00
Collin J. Doering 5c2f33456e
README.org: Write build and clean sections in development guide 2021-12-07 21:17:32 -05:00
Collin J. Doering 46d25cd912
Remove top-level manifest.scm as package in guix.scm is sufficient
* manifest.scm: Removed in favor of guix.scm and the blog-rekahsoft-ca package definition within it

* guix.scm: Add environment variable PS1 that was formally set by manifest.scm

* README.org (Start Development Environment): Update guix commands used now that there is no top-level manifest.scm
2021-12-07 21:17:29 -05:00
Collin J. Doering 1dae28b6f4
README.org: Improve grammar make README read more clearly 2021-12-07 00:28:45 -05:00
Collin J. Doering b1476560ec
.gitignore: Update terraform entries 2021-12-07 00:24:29 -05:00
Collin J. Doering 0b47b38f61
TODO.org: (Invalid page urls load as the home page): Add new TODO 2021-12-06 23:31:09 -05:00
Collin J. Doering 1e1ae99446
Use guix for managing the required deployment environment
* infra/variables.tf: Add new variable 'site_statis_files_dir'

	* infra/manifest.scm: Add guix manifest that captures all tools required for deploying this site. This currently includes terraform, in use terraform providers, as well as awscliv2  which is used directly from a null resource

	* infra/main.tf: Pin all provider version so they are available from the rekahsoft-guix channel
	Remove the need for the template provider. It is still included as these changes need to be applied to all environments before it can be removed.
	Remove TF-UPGRAGE-TODO's
	Use the new variable 'site_static_files_dir' for the location of the static site files to be deployed

	* channels.scm (channel): Add symlink to top-level channels file

	* infra/Makefile (SELECTED_WORKSPACE): Removed the dependency on terraform
	(clean): Add new PHONY target 'clean' which cleans up terraform temporary files
	(workspace): Add new PHONY target 'workspace which switches to user provided ENV

	* channels.scm (channel): Updated rekahsoft-guix channel

	* README.org (Features): Updated sections on deployment
2021-12-06 22:25:05 -05:00
Collin J. Doering ee09fc99b8
channels.scm: Update guix and rekahsoft-guix 2021-12-05 04:08:14 -05:00
Collin J. Doering 5ba926b5d2
.drone.yml: Remove --with-source option during ci builds 2021-12-01 23:38:33 -05:00
Collin J. Doering 4b18054324
guix.scm: Use git commit in version string 2021-12-01 23:36:59 -05:00
Collin J. Doering 29dc67c881
README.org: Move 'Known Issues' to TODO.org 2021-11-30 23:40:03 -05:00
Collin J. Doering dd6434b4ee
README.org: Update documentation now that site script is included in package 2021-11-30 23:40:00 -05:00
Collin J. Doering fd15b80b62
Use git source instead of tarball for blog-rekahsoft-ca package
* manifest.scm:
* guix.scm: Prefix global variables with '%'; use git source for blog-rekahsoft-ca package
2021-11-30 23:34:19 -05:00
Collin J. Doering ff1d13a670
src/site.hs: Fix compiler warnings about unused modules/redundant imports 2021-11-29 09:48:12 -05:00
Collin J. Doering e69f751011
Include site script in package and build site as another package output 2021-11-29 09:48:03 -05:00
Collin J. Doering 50f14dfa8d
.drone.yml: Remove ci test 2021-11-28 23:20:46 -05:00
Collin J. Doering c9ad841f53
README.org: Update development documentation 2021-11-28 23:19:40 -05:00
Collin J. Doering fc40cc18d1
manifest.scm: Removed unneeded development tools 2021-11-28 14:20:24 -05:00
Collin J. Doering 3ebc0b1bbd
.drone.yml: TEST 2021-11-28 06:43:38 -05:00
Collin J. Doering fc2fe49022
.drone.yml: TEST 2021-11-28 01:42:26 -05:00
Collin J. Doering 5dfb1b88ee
site: Remove test command from site as the test suite was removed in 84735e9a 2021-11-27 23:52:32 -05:00
Collin J. Doering 10a72486a5
.gitignore: Remove .stack 2021-11-27 23:43:57 -05:00
Collin J. Doering d6464b8ac7
Remove application cache functionality now that its deprecated 2021-11-27 23:43:22 -05:00
Collin J. Doering 6c7fa76f4a
templates/default.js: Update MathJax cdn url 2021-11-27 23:41:12 -05:00
Collin J. Doering 8f47caede9
README.org: Include droneci badge directly as html 2021-11-26 21:18:38 -05:00
Collin J. Doering 7d59eb100b
Remove stack.yaml{,.lock} 2021-11-26 08:08:52 -05:00
Collin J. Doering b1875dfc27
Update copyright year/s 2021-11-26 08:08:34 -05:00
Collin J. Doering df02a2a62d
README.org: Add droneci badge and adjust title a last time
Note: there is a bug in gitea rendering of org-mode files which results in additional space
at the top of the document.
2021-11-26 08:07:36 -05:00
Collin J. Doering a1dbf95c49
.drone.yml: Fix reference to renamed file blog-rekahsoft-ca.scm -> guix.scm 2021-11-26 01:51:31 -05:00
Collin J. Doering b40ff4f4ad
README.org: Fix title spacing 2021-11-26 01:46:49 -05:00
Collin J. Doering 2f3d052cd6
README.org: Fix title spacing 2021-11-26 01:41:54 -05:00
Collin J. Doering 7d679dd94b
README.org: Add more known issues and fix title spacing 2021-11-26 01:39:46 -05:00
Collin J. Doering a15def12bc
Replace README.md with README.org 2021-11-25 22:56:24 -05:00
Collin J. Doering d3ae74879b
Move blog-rekahsoft-ca.scm -> guix.scm and update references 2021-11-25 21:21:36 -05:00
Collin J. Doering b22b7e04b4
Fix bug where are javascript files were incorrectly processed
The compressCssCompiler has been used for javascript for a long time for this website.
Despite this not being a wise decision, it worked up until some version of Hakyll. I should
minify the javascript as part of the site building process, but for now will simply copy the
javascript files as is.
2021-11-25 21:17:29 -05:00
Collin J. Doering 2620a711fb
Update jquery from v1.11.2 -> v1.12.3 2021-11-25 21:16:18 -05:00
Collin J. Doering 06e473583a
image-src: Add inkscape svg's that were used to build site graphics 2021-11-25 14:46:18 -05:00
Collin J. Doering b0cb08f624
Move ghc-* packages to the rekahsoft-guix channel
* channels.scm: Update rekahsoft-guix channel
* blog-rekahsoft-ca.scm: Removed ghc-* packages that are now part of rekahsoft-guix channel;
                         these ghc-* packages can be contributed upstream to guix.
2021-11-25 11:49:45 -05:00
Collin J. Doering f691f99210
WIP: Use newly added channel.scm guix channel file for ci 2021-11-24 21:36:10 -05:00
Collin J. Doering 94e74df3be
WIP: guix development workflow 2021-11-23 22:58:24 -05:00
36 changed files with 4668 additions and 2583 deletions

21
.drone.jsonnet Normal file
View File

@ -0,0 +1,21 @@
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,12 +1,116 @@
---
kind: pipeline
type: docker
name: blog-rekahsoft-ca
name: validate
workspace:
path: /drone/blog-rekahsoft-ca
platform:
os: linux
arch: amd64
steps:
- name: build-site
image: docker.nexus.home.rekahsoft.ca/fpco/stack-build:lts-12.0
- name: build
pull: if-not-exists
image: docker.nexus.home.rekahsoft.ca/guix:latest
commands:
- ./site build
- 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
...

195
.drone/ci.libsonnet Normal file
View File

@ -0,0 +1,195 @@
{
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 Normal file
View File

@ -0,0 +1,10 @@
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 Normal file
View File

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

17
.gitignore vendored
View File

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

View File

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

View File

@ -1,88 +0,0 @@
# 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 Normal file
View File

@ -0,0 +1,390 @@
#+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.11 && <4.12,
hakyll >= 4.12 && <4.13,
build-depends: base >=4.16 && <4.17,
hakyll >= 4.15 && <4.16,
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.11 && <4.12,
clay >=0.13 && <0.14,
build-depends: base >=4.16 && <4.17,
clay >=0.14 && <0.15,
text >=1.2 && <1.3
-- Directories containing source files.

5
bootstrap.sh Executable file
View File

@ -0,0 +1,5 @@
#!/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 Normal file
View File

@ -0,0 +1,20 @@
(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 solid (px 2) black
border (px 2) solid black
borderRightWidth 0
borderLeftWidth 0
backgroundImage $ url "/images/diagonal-stripes.png"
@ -126,8 +126,8 @@ statusMessage :: Css
statusMessage = do
"#status" ? do
display none
border solid (px 1) black
borderTop solid nil black
border (px 1) solid black
borderTop nil solid 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 solid (px 2) "#eee"
border (px 2) solid "#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 solid (px 1) "#eee"
borderTop (px 1) solid "#eee"
".read-more" ? fontWeight bold
".no-teaser" ? do
@ -122,7 +122,7 @@ aPost = do
businessCard :: Css
businessCard = do
"#business-card" ? do
border solid (px 2) black
border (px 2) solid 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 solid (px 1) black
border (px 1) solid black
sym borderRadius (px 10)
minHeight (px 215)
minWidth (px 150)
@ -141,7 +141,7 @@ businessCard = do
marginRight (px 10)
".info" ? do
borderTop solid (px 2) black
borderTop (px 2) solid black
overflow hidden
paddingTop (px 8)
@ -185,11 +185,11 @@ srcCodeBlock = do
<> table # ".sourceCode" ** pre ? do
sym margin nil
sym padding nil
border none nil black
border nil none black
verticalAlign vAlignBaseline
td # ".lineNumbers" ? do
borderRight solid (px 1) "#AAAAAA"
borderRight (px 1) solid "#AAAAAA"
textAlign $ alignSide sideRight
color "#AAAAAA"
paddingLeft (px 8)
@ -222,7 +222,7 @@ srcCodeBlock = do
postFigures :: Css
postFigures = do
figure ? do
border solid (px 1) black
border (px 1) solid black
sym borderRadius (px 3)
clear both
@ -230,7 +230,7 @@ postFigures = do
img <? do
display block
width (pct 100)
borderBottom solid (px 1) black
borderBottom (px 1) solid black
cursor pointer
figcaption # ":before" <? do
@ -249,7 +249,7 @@ inlinePostImages = article # ".post" ? do
clear clearRight
float floatRight
width (pct 30)
border solid (px 1) black
border (px 1) solid 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 solid (px 1) "#888"
border (px 1) solid "#888"
borderRadius (px 5) (px 5) (px 5) (px 5)
-- boxShadow (px 2) (px 5) (px 2) "#888"
sym padding pad'

85
guix.scm Normal file
View File

@ -0,0 +1,85 @@
;; (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

122
images-src/page-next.svg Normal file
View File

@ -0,0 +1,122 @@
<?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>

After

Width:  |  Height:  |  Size: 4.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 62 KiB

View File

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

1
infra/channels.scm Symbolic link
View File

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

View File

@ -11,7 +11,7 @@ terraform {
provider "aws" {
region = var.region
version = "~> 2.15"
version = "= 2.70.0"
assume_role {
role_arn = var.workspace_iam_roles[terraform.workspace]
@ -21,7 +21,7 @@ provider "aws" {
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
version = "~> 2.1"
version = "= 2.70.0"
assume_role {
role_arn = var.workspace_iam_roles[terraform.workspace]
@ -33,15 +33,11 @@ provider "null" {
}
provider "random" {
version = "~> 2.1"
}
provider "template" {
version = "~> 2.1"
version = "= 2.1.2"
}
#
# Local values to be re-used throughout this template
# Local values to be re-used throughout
locals {
common_tags = {
@ -55,18 +51,41 @@ locals {
naked_domain = "${local.subdomain}${var.dns_apex}"
domain = "${local.www}${local.naked_domain}"
project_env = "${var.project}-${terraform.workspace}"
bucket_arn = aws_s3_bucket.static.arn
user_arn = aws_iam_user.app_deploy.arn
cloudfront_arn = aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn
}
#
# Data Sources
data "template_file" "s3_origin_policy" {
template = file("templates/s3_origin_policy.json")
data "aws_iam_policy_document" "s3_origin_policy" {
statement {
principals {
type = "AWS"
identifiers = [local.cloudfront_arn]
}
actions = ["s3:GetObject"]
resources = ["${local.bucket_arn}/*"]
}
vars = {
bucket_arn = aws_s3_bucket.static.arn
user_arn = aws_iam_user.app_deploy.arn
cloudfront_arn = aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn
statement {
principals {
type = "AWS"
identifiers = [local.user_arn]
}
actions = ["s3:ListBucket"]
resources = ["${local.bucket_arn}"]
}
statement {
principals {
type = "AWS"
identifiers = [local.user_arn]
}
actions = ["s3:*"]
resources = ["${local.bucket_arn}/*"]
}
}
@ -93,14 +112,6 @@ 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"]]
}
@ -210,7 +221,7 @@ resource "aws_route53_record" "static_redirect_ipv6" {
resource "aws_s3_bucket_policy" "static_policy" {
bucket = aws_s3_bucket.static.id
policy = data.template_file.s3_origin_policy.rendered
policy = data.aws_iam_policy_document.s3_origin_policy.json
}
resource "aws_cloudfront_origin_access_identity" "origin_access_identity" {
@ -246,14 +257,6 @@ 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 {
@ -340,14 +343,6 @@ 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 {
@ -394,6 +389,8 @@ resource "null_resource" "deploy_app" {
provisioner "local-exec" {
interpreter = ["bash", "-c"]
command = <<SCRIPT
set -eo pipefail;
: Create temporary aws config and credentials files
export AWS_CONFIG_FILE=$(mktemp);
export AWS_SHARED_CREDENTIALS_FILE=$(mktemp);
@ -403,11 +400,20 @@ aws configure --profile ${aws_iam_user.app_deploy.name} set aws_access_key_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};
: 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
}

36
infra/manifest.scm Normal file
View File

@ -0,0 +1,36 @@
;; (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"))

View File

@ -1,33 +0,0 @@
{
"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,3 +25,6 @@ variable "enable_naked_domain" {
default = false
}
variable "site_static_files_dir" {
default = "../_site"
}

View File

@ -27,9 +27,6 @@
/*global jQuery, MathJax*/
//------------------------
// Global array for processing piwik analytics commands
var _paq = _paq || [];
(function ($, mj) {
"use strict";
@ -196,49 +193,6 @@ var _paq = _paq || [];
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 = [],
@ -345,17 +299,7 @@ var _paq = _paq || [];
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,7 +15,6 @@ This website was proudly made with open source software! Specifically:
- [Clay][]
- [Skeleton][]
- [JQuery][]
- [JQuery-address][]
- [MathJax][]
- [Inkscape][]
- [Gimp][]
@ -28,7 +27,7 @@ Terms of Use
============
------------
This site is _Copyright 2016 © - Collin Doering_ and is distributed under the following terms.
This site is _Copyright 2016-2023 © - 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://blog.irukado.org/2013/12/disqus-considered-harmful/)). There are some open source
solutions including:
[this](http://web.archive.org/web/20150323224820/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,14 +25,12 @@ 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) 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.
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.
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]: http://git.rekahsoft.ca/hack/
[hack-docs]: http://git.rekahsoft.ca/hack/about
[hack-git]: https://git.rekahsoft.ca/rekahsoft/hack/
[hack-docs]: https://git.rekahsoft.ca/rekahsoft/hack/src/branch/master/README.md
[GtkWave]: http://gtkwave.sourceforge.net/

109
site
View File

@ -1,109 +0,0 @@
#!/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,15 +24,10 @@
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
@ -89,212 +84,167 @@ pandocWriterOptions = defaultHakyllWriterOptions
myConfig :: Configuration
myConfig = defaultConfiguration
{ deployCommand = "echo 'Deploying website...' && " ++
{ deployCommand = "echo 'TODO (what to do with this cmd) Deploying website...' && " ++
"aws s3 sync _site/ s3://$S3_BUCKET &&" ++
"echo 'Done!'"
, previewPort = 3000
}
main :: IO ()
main = do
-- Get a random number generator before going into Rules monad
stdGen <- getStdGen
siteRules :: Rules ()
siteRules = do
match ("js/**"
.||. "files/**"
.||. "images/**"
.||. "fonts/**"
.||. "robots.txt") $ do
route idRoute
compile copyFileCompiler
hakyllWith myConfig $ do
match ("action/**" .||. "files/**" .||. "images/**" .||. "fonts/**" .||. "robots.txt") $ do
route idRoute
compile copyFileCompiler
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
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))
pageIds <- getMatches ("pages/**" .&&. complement "pages/blog.markdown")
fontIds <- getMatches "fonts/**"
imageIds <- getMatches "images/**"
cssIds <- getMatches "css/**"
jsIds <- getMatches "js/**"
libIds <- getMatches "lib/**"
clayDeps <- makePatternDependency $ fromGlob "clay/*.hs"
clayIds <- getMatches "clay/**.hs"
let manifestIds = clayIds ++ fontIds ++ imageIds ++ pageIds ++ cssIds ++ libIds ++ jsIds
rulesExtraDependencies [clayDeps] $ create ["default.css"] $ do
route idRoute
compile $ makeItem =<< (unsafeCompiler $ readProcess "gencss" ["compact"] "")
clayDeps <- makePatternDependency $ fromList clayIds
manifestDeps <- makePatternDependency $ fromList manifestIds
-- 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))
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
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 paginatedPosts pageNum <>
paginateContext paginatedTaggedPosts pageNum <>
constField "tag" tag <>
listField "posts" (taggedPostCtx tags) (return posts)
indexCtx = if pageNum <= 2
then appCacheCtx <> navCtx
else navCtx
indexCtx = navCtx
makeItem ""
>>= loadAndApplyTemplate "templates/pages/blog.html" ctx
>>= loadAndApplyTemplate "templates/tag-page.html" ctx
>>= loadAndApplyTemplate "templates/default.html" indexCtx
match ("pages/*" .&&. complement "pages/blog.markdown") $ do
route $ pageRoute
compile $ do
posts <- recentFirst =<< loadAllSnapshots "posts/**" "content"
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)
-- Get the current Identifier
curId <- getUnderlying
let pageRoute = gsubRoute "pages/" (const "") `composeRoutes` setExtension "html"
let pageFilePath = toFilePath curId
pageName = takeBaseName pageFilePath
recentPosts = take 5 posts
pageTemplate = "templates/pages/" ++ pageName ++ ".html"
match ("pages/*" .&&. complement "pages/blog.markdown") $ version "nav-gen" $ do
route $ pageRoute
compile $ pandocCompiler
-- Generate navigation context
navCtx <- genNavContext pageFilePath
match "pages/blog.markdown" $ version "nav-gen" $ do
route $ constRoute "blog1.html"
compile $ pandocCompiler
let masterCtx =
listField "recentPosts" (taggedPostCtx tags) (return recentPosts) <>
listField "posts" (taggedPostCtx tags) (return posts) <>
tagCloudField "tagCloud" 65 135 tags <>
defaultContext
indexCtx = navCtx <> appCacheCtx
paginateRules paginatedPosts $ \pageNum pattern -> do
route idRoute
compile $ do
posts <- recentFirst =<< loadAllSnapshots pattern "content"
sectionCtx <- getResourceBody >>= genSectionContext
pg <- loadSnapshot (fromFilePath pageTemplate) "original"
>>= applyAsTemplate (sectionCtx <> masterCtx)
navCtx <- genNavContext "pages/blog.markdown"
(makeItem . itemBody) pg
>>= loadAndApplyTemplate "templates/default.html" indexCtx
let ctx = taggedPostCtx tags <>
paginateContext paginatedPosts pageNum <>
listField "posts" (taggedPostCtx tags) (return posts)
indexCtx = navCtx
match "posts/**" $ do
route $ setExtension "html"
compile $ do
indexCtx <- genNavContext "pages/blog.markdown"
makeItem ""
>>= loadAndApplyTemplate "templates/pages/blog.html" ctx
>>= loadAndApplyTemplate "templates/default.html" indexCtx
pandocCompilerWith pandocReaderOptions pandocWriterOptions
>>= saveSnapshot "content"
>>= loadAndApplyTemplate "templates/partials/post.html" (taggedPostCtx tags)
>>= loadAndApplyTemplate "templates/default.html" indexCtx
match ("pages/*" .&&. complement "pages/blog.markdown") $ do
route $ pageRoute
compile $ do
posts <- recentFirst =<< loadAllSnapshots "posts/**" "content"
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
-- Get the current Identifier
curId <- getUnderlying
forM_ [("js/**", idRoute),
("lib/JQuery/*", gsubRoute "JQuery" $ const "js")] $ \(p, r) ->
match p $ do
route r
compile $ compressCssCompiler
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
---------------------------------------------------------------------------------------------------------
-- Functions & Constants --------------------------------------------------------------------------------
@ -334,9 +284,6 @@ 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"

View File

@ -1,65 +0,0 @@
# 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

View File

@ -1,12 +0,0 @@
# 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" $if(appcache)$manifest="/manifest.appcache"$endif$>
<html class="en">
<head>
<!-- Basic Page Needs -->
<meta charset="utf-8">
@ -24,9 +24,7 @@
</head>
<body>
<!-- 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/analytics.html")$
$partial("templates/partials/logo-banner.html")$
$partial("templates/partials/nav.html")$
@ -51,8 +49,9 @@
$partial("templates/partials/footer.html")$
<!-- External javascript libraries: JQuery, MathJax -->
<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>
<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>
<!-- Custom javascript for user interactivity -->
<script src="/js/default.js" type="text/javascript"></script>

View File

@ -0,0 +1,21 @@
<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</p>
<p>&copy; Collin Doering 2016-2023</p>
</div>
<div class="six columns omega" id="footer-right">
<p>