commit f212aae1398e35f252ba08ad9b91f2bc9f7f8d7c Author: Pim Kunis Date: Fri Apr 26 10:59:32 2024 +0200 init diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..5bf8fc1 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0=" + +use devenv \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7166508 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml + +.jekyll-cache/ diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..01beaf6 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,156 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1713968789, + "owner": "cachix", + "repo": "devenv", + "rev": "b26b52a4dac68bdc305f6b9df948c97f49b2c3ee", + "treeHash": "4a034bbd3511c196f4075a1eb0da1b422d1011db", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "treeHash": "2addb7b71a20a25ea74feeaf5c2f6a6b30898ecb", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "treeHash": "bd263f021e345cb4a39d80c126ab650bebc3c10c", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "treeHash": "ca14199cabdfe1a06a7b1654c76ed49100a689f9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1713361204, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "285676e87ad9f0ca23d8714a6ab61e7e027020c6", + "treeHash": "50354b35a3e0277d4a83a0a88fa0b0866b5f392f", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1713995372, + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "dd37924974b9202f8226ed5d74a252a9785aedf8", + "treeHash": "8114bf8e19ad8c67c0e2639b83c606c58c7bccec", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1713954846, + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "6fb82e44254d6a0ece014ec423cb62d92435336f", + "treeHash": "a456512c8da29752b79131f1e5b45053e2394078", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "treeHash": "cce81f2a0f0743b2eb61bc2eb6c7adbe2f2c6beb", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..63f2d1e --- /dev/null +++ b/devenv.nix @@ -0,0 +1,11 @@ +{ pkgs, lib, config, inputs, ... }: + +let + gems = pkgs.bundlerEnv { + name = "blog-pim"; + gemdir = ./src; + }; +in +{ + packages = [ gems gems.wrappedRuby ]; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..2457af5 --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,14 @@ +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + +# If you're using non-OSS software, you can set allowUnfree to true. +# allowUnfree: true + +# If you're willing to use a package that's vulnerable +# permittedInsecurePackages: +# - "openssl-1.1.1w" + +# If you have more than one devenv you can merge them +#imports: +# - ./backend diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c7a9a1c --- /dev/null +++ b/flake.nix @@ -0,0 +1,15 @@ +{ + description = "A very basic flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = { self, nixpkgs }: { + + packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello; + + packages.x86_64-linux.default = self.packages.x86_64-linux.hello; + + }; +} diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Cache/b7/9606fb3afea5bd1609ed40b622142f1c98125abcfe89a76a661b0e8e343910 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Cache/b7/9606fb3afea5bd1609ed40b622142f1c98125abcfe89a76a661b0e8e343910 new file mode 100644 index 0000000..f8ea00c --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Cache/b7/9606fb3afea5bd1609ed40b622142f1c98125abcfe89a76a661b0e8e343910 @@ -0,0 +1 @@ +I"× {"source"=>"/home/pim/git/blog-pim/src", "destination"=>"/home/pim/git/blog-pim/src/_site", "collections_dir"=>"", "cache_dir"=>".jekyll-cache", "plugins_dir"=>"_plugins", "layouts_dir"=>"_layouts", "data_dir"=>"_data", "includes_dir"=>"_includes", "collections"=>{"posts"=>{"output"=>true, "permalink"=>"/:title/"}}, "safe"=>false, "include"=>["_redirects", ".htaccess"], "exclude"=>["CNAME", "Gemfile", "Gemfile.lock", "LICENSE", "CHANGELOG.md", "README.md", "node_modules", "CODE_OF_CONDUCT.md", "CONTRIBUTING.md", "lighthouse.png", "klise-*.gem", "klise.gemspec", "gemset.nix", ".sass-cache", ".jekyll-cache", "gemfiles", "vendor/bundle/", "vendor/cache/", "vendor/gems/", "vendor/ruby/"], "keep_files"=>[".git", ".svn"], "encoding"=>"utf-8", "markdown_ext"=>"markdown,mkdown,mkdn,mkd,md", "strict_front_matter"=>false, "show_drafts"=>nil, "limit_posts"=>0, "future"=>false, "unpublished"=>false, "whitelist"=>[], "plugins"=>["jekyll-feed", "jekyll-sitemap", "jekyll-postfiles", "jekyll-commonmark-ghpages"], "markdown"=>"CommonMarkGhPages", "highlighter"=>"rouge", "lsi"=>false, "excerpt_separator"=>"\n\n", "incremental"=>false, "detach"=>false, "port"=>"4000", "host"=>"127.0.0.1", "baseurl"=>"/", "show_dir_listing"=>false, "permalink"=>"/:title/", "paginate_path"=>"/page:num", "timezone"=>"Europe/Amsterdam", "quiet"=>false, "verbose"=>false, "defaults"=>[{"scope"=>{"path"=>""}, "values"=>{"layout"=>"post", "comments"=>false}}], "liquid"=>{"error_mode"=>"warn", "strict_filters"=>false, "strict_variables"=>false}, "kramdown"=>{"auto_ids"=>true, "toc_levels"=>[1, 2, 3, 4, 5, 6], "entity_output"=>"as_char", "smart_quotes"=>"lsquo,rsquo,ldquo,rdquo", "input"=>"GFM", "hard_wrap"=>false, "guess_lang"=>true, "footnote_nr"=>1, "show_warnings"=>false, "syntax_highlighter"=>"rouge"}, "title"=>"Pim Kunis", "description"=>"A pig's gotta fly", "lang"=>"en-US", "image"=>"assets/img/avatar.jpg", "repo"=>"https://git.kun.is/pim/static", "mode"=>"light", "author"=>{"name"=>"Pim Kunis", "bio"=>"A pig's gotta fly", "username"=>"pim", "avatar"=>"/assets/img/avatar.jpg"}, "url"=>"http://localhost:4000", "jekyll_compose"=>{"post_default_front_matter"=>{"modified"=>nil, "tags"=>[], "description"=>nil}, "draft_default_front_matter"=>{"modified"=>nil, "tags"=>[], "description"=>nil}}, "number_of_posts"=>5, "sass"=>{"style"=>"compressed"}, "commonmark"=>{"options"=>["SMART", "FOOTNOTES"], "extensions"=>["strikethrough", "autolink", "table", "tagfilter"]}, "livereload_port"=>35729, "serving"=>true, "watch"=>true}:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/05/974e3d7573ea82516d84156656de5bbf7ff6ba109d70464cbe6839f592094b b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/05/974e3d7573ea82516d84156656de5bbf7ff6ba109d70464cbe6839f592094b new file mode 100644 index 0000000..679610d --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/05/974e3d7573ea82516d84156656de5bbf7ff6ba109d70464cbe6839f592094b @@ -0,0 +1,6 @@ +I"Ą

Ever SSH’ed into a freshly installed server and gotten the following annoying message?

+
The authenticity of host 'host.tld (1.2.3.4)' can't be established.
+ED25519 key fingerprint is SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8.
+Are you sure you want to continue connecting (yes/no)?
+
+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/0e/763d8239a78fde13ad37342cacbcd9ada77932aa71a300ff4a742b3a5403a2 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/0e/763d8239a78fde13ad37342cacbcd9ada77932aa71a300ff4a742b3a5403a2 new file mode 100644 index 0000000..521cbe9 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/0e/763d8239a78fde13ad37342cacbcd9ada77932aa71a300ff4a742b3a5403a2 @@ -0,0 +1,149 @@ +I"GL

Recently, I deployed Concourse CI because I wanted to get my feet wet with a CI/CD pipeline. +However, I had a practical use case lying around for a long time: automatically compiling my static website and deploying it to my docker Swarm. +This took some time getting right, but the result works like a charm (source code).

+

It’s comforting to know I don’t have move a finger and my website is automatically deployed. +However, I would still like to receive some indication of what’s happening. +And what’s a better way to do that, than using my Apprise service to keep me up to date. +There’s a little snag though: I could not find any Concourse resource that does this. +That’s when I decided to just create it myself.

+

The Plagiarism Hunt

+

As any good computer person, I am lazy. +I’d rather just copy someone’s work, so that’s what I did. +I found this GitHub repository that does the same thing but for Slack notifications. +For some reason it’s archived, but it seemed like it should work. +I actually noticed lots of repositories for Concourse resource types are archived, so not sure what’s going on there.

+

Getting to know Concourse

+

Let’s first understand what we need to do reach our end goal of sending Apprise notifications from Concourse.

+

A Concourse pipeline takes some inputs, performs some operations on them which result in some outputs. +These inputs and outputs are called resources in Concourse. +For example, a Git repository could be a resource. +Each resource is an instance of a resource type. +A resource type therefore is simply a blueprint that can create multiple resources. +To continue the example, a resource type could be “Git repository”.

+

We therefore need to create our own resource type that can send Apprise notifications. +A resource type is simply a container that includes three scripts:

+ +

As Apprise notifications are basically fire-and-forget, we will only implement the out script.

+

Writing the out script

+

The whole script can be found here, but I will explain the most important bits of it. +Note that I only use Apprise’s persistent storage solution, and not its stateless solution.

+

Concourse provides us with the working directory, which we cd to:

+
cd "${1}"
+
+

We create a timestamp, formatted in JSON, which we will use for the resource’s new version later. +Concourse requires us to set a version for the resource, but since Apprise notifications don’t have that, we use the timestamp:

+
timestamp="$(jq -n "{version:{timestamp:\"$(date +%s)\"}}")"
+
+

First some black magic Bash to redirect file descriptors. +Not sure why this is needed, but I copied it anyways. +After that, we create a temporary file holding resource’s parameters.

+
exec 3>&1
+exec 1>&2
+
+payload=$(mktemp /tmp/resource-in.XXXXXX)
+cat > "${payload}" <&0
+
+

We then extract the individual parameters. +The source key contains values how the resource type was specified, while the params key specifies parameters for this specific resource.

+
apprise_host="$(jq -r '.source.host' < "${payload}")"
+apprise_key="$(jq -r '.source.key' < "${payload}")"
+
+alert_body="$(jq -r '.params.body' < "${payload}")"
+alert_title="$(jq -r '.params.title // null' < "${payload}")"
+alert_type="$(jq -r '.params.type // null' < "${payload}")"
+alert_tag="$(jq -r '.params.tag // null' < "${payload}")"
+alert_format="$(jq -r '.params.format // null' < "${payload}")"
+
+

We then format the different parameters using JSON:

+
alert_body="$(eval "printf \"${alert_body}\"" | jq -R -s .)"
+[ "${alert_title}" != "null" ] && alert_title="$(eval "printf \"${alert_title}\"" | jq -R -s .)"
+[ "${alert_type}" != "null" ] && alert_type="$(eval "printf \"${alert_type}\"" | jq -R -s .)"
+[ "${alert_tag}" != "null" ] && alert_tag="$(eval "printf \"${alert_tag}\"" | jq -R -s .)"
+[ "${alert_format}" != "null" ] && alert_format="$(eval "printf \"${alert_format}\"" | jq -R -s .)"
+
+

Next, from the individual parameters we construct the final JSON message body we send to the Apprise endpoint.

+
body="$(cat <<EOF
+{
+  "body": ${alert_body},
+  "title": ${alert_title},
+  "type": ${alert_type},
+  "tag": ${alert_tag},
+  "format": ${alert_format}
+}
+EOF
+)"
+
+

Before sending it just yet, we compact the JSON and remove any values that are null:

+
compact_body="$(echo "${body}" | jq -c '.')"
+echo "$compact_body" | jq 'del(..|nulls)' > /tmp/compact_body.json
+
+

Here is the most important line, where we send the payload to the Apprise endpoint. +It’s quite straight-forward.

+
curl -v -X POST -T /tmp/compact_body.json -H "Content-Type: application/json" "${apprise_host}/notify/${apprise_key}"
+
+

Finally, we print the timestamp (fake version) in order to appease the Concourse gods.

+
echo "${timestamp}" >&3
+
+

Building the Container

+

As said earlier, to actually use this script, we need to add it to a image. +I won’t be explaining this whole process, but the source can be found here. +The most important take-aways are these:

+ +

Using the Resource Type

+

Using our newly created resource type is surprisingly simple. +I use it for the blog you are reading right now and the pipeline definition can be found here. +Here we specify the resource type in a Concourse pipeline:

+
resource_types:
+- name: apprise
+  type: registry-image
+  source:
+    repository: git.kun.is/pim/concourse-apprise-notifier
+    tag: "1.1.1"
+
+

We simply have to tell Concourse where to find the image, and which tag we want. +Next, we instantiate the resource type to create a resource:

+
resources:
+- name: apprise-notification
+  type: apprise
+  source:
+    host: https://apprise.kun.is:444
+    key: concourse
+  icon: bell
+
+

We simply specify the host to send Apprise notifications to. +Yeah, I even gave it a little bell because it’s cute.

+

All that’s left to do, is actually send the notification. +Let’s see how that is done:

+
- name: deploy-static-website
+  plan:
+    - task: deploy-site
+      config: ...
+
+      on_success:
+	put: apprise-notification
+	params:
+	  title: "Static website deployed!"
+	  body: "New version: $(cat version/version)"
+	no_get: true
+
+

As can be seen, the Apprise notification can be triggered when a task is executed successfully. +We do this using the put command, which execute the out script underwater. +We set the notification’s title and body, and send it! +The result is seen below in my Ntfy app, which Apprise forwards the message to: +picture showing my Ntfy app with the Apprise notification

+

And to finish this off, here is what it looks like in the Concourse web UI: +the concourse web gui showing the pipeline of my static website including the the apprise notification resources

+

Conclusion

+

Concourse’s way of representing everything as an image/container is really interesting in my opinion. +A resource type is quite easily implemented as well, although Bash might not be the optimal way to do this. +I’ve seen some people implement it in Rust, which might be a good excuse to finally learn that language :)

+

Apart from Apprise notifications, I’m planning on creating a resource type to deploy to a Docker swarm eventually. +This seems like a lot harder than simply sending notifications though.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/1a/68cb66b01bbf383da07f97fa2f92ac4f63f127b7ede00efab21d453837389e b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/1a/68cb66b01bbf383da07f97fa2f92ac4f63f127b7ede00efab21d453837389e new file mode 100644 index 0000000..1e583e6 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/1a/68cb66b01bbf383da07f97fa2f92ac4f63f127b7ede00efab21d453837389e @@ -0,0 +1,146 @@ +I"¸8

Ever SSH’ed into a freshly installed server and gotten the following annoying message?

+
The authenticity of host 'host.tld (1.2.3.4)' can't be established.
+ED25519 key fingerprint is SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8.
+Are you sure you want to continue connecting (yes/no)?
+
+

Or even more annoying:

+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
+Someone could be eavesdropping on you right now (man-in-the-middle attack)!
+It is also possible that a host key has just been changed.
+The fingerprint for the ED25519 key sent by the remote host is
+SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8.
+Please contact your system administrator.
+Add correct host key in /home/user/.ssh/known_hosts to get rid of this message.
+Offending ED25519 key in /home/user/.ssh/known_hosts:3
+  remove with:
+  ssh-keygen -f "/etc/ssh/ssh_known_hosts" -R "1.2.3.4"
+ED25519 host key for 1.2.3.4 has changed and you have requested strict checking.
+Host key verification failed.
+
+

Could it be that the programmers at OpenSSH simply like to annoy us with these confusing messages? +Maybe, but these warnings also serve as a way to notify users of a potential Man-in-the-Middle (MITM) attack. +I won’t go into the details of this problem, but I refer you to this excellent blog post. +Instead, I would like to talk about ways to solve these annoying warnings.

+

One obvious solution is simply to add each host to your known_hosts file. +This works okay when managing a handful of servers, but becomes unbearable when managing many servers. +In my case, I wanted to quickly spin up virtual machines using Duncan Mac-Vicar’s Terraform Libvirt provider, without having to accept their host key before connecting. +The solution? Issuing SSH host certificates using an SSH certificate authority.

+

SSH Certificate Authorities vs. the Web

+

The idea of an SSH certificate authority (CA) is quite easy to grasp, if you understand the web’s Public Key Infrastructure (PKI). +Just like with the web, a trusted party can issue certificates that are offered when establishing a connection. +The idea is, just by trusting the trusted party, you trust every certificate they issue. +In the case of the web’s PKI, this trusted party is bundled and trusted by your browser or operating system. +However, in the case of SSH, the trusted party is you! (Okay you can also trust your own web certificate authority) +With this great power, comes great responsibility which we will abuse heavily in this article.

+

SSH Certificate Authority for Terraform

+

So, let’s start with a plan. +I want to spawn virtual machines with Terraform which which are automatically provisioned with a SSH host certificate issued by my CA. +This CA will be another host on my private network, issuing certificates over SSH.

+

Fetching the SSH Host Certificate

+

First we generate an SSH key pair in Terraform. +Below is the code for that:

+
resource "tls_private_key" "debian" {
+  algorithm = "ED25519"
+}
+
+data "tls_public_key" "debian" {
+  private_key_pem = tls_private_key.debian.private_key_pem
+}
+
+

Now that we have an SSH key pair, we need to somehow make Terraform communicate this with the CA. +Lucky for us, there is a way for Terraform to execute an arbitrary command with the external data feature. +We call this script below:

+
data "external" "cert" {
+  program = ["bash", "${path.module}/get_cert.sh"]
+
+  query = {
+    pubkey   = trimspace(data.tls_public_key.debian.public_key_openssh)
+    host     = var.name
+    cahost   = var.ca_host
+    cascript = var.ca_script
+    cakey    = var.ca_key
+  }
+}
+
+

These query parameters will end up in the script’s stdin in JSON format. +We can then read these parameters, and send them to the CA over SSH. +The result must as well be in JSON format.

+
#!/bin/bash
+set -euo pipefail
+IFS=$'\n\t'
+
+# Read the query parameters
+eval "$(jq -r '@sh "PUBKEY=\(.pubkey) HOST=\(.host) CAHOST=\(.cahost) CASCRIPT=\(.cascript) CAKEY=\(.cakey)"')"
+
+# Fetch certificate from the CA
+# Warning: extremely ugly code that I am to lazy to fix
+CERT=$(ssh -o ConnectTimeout=3 -o ConnectionAttempts=1 root@$CAHOST '"'"$CASCRIPT"'" host "'"$CAKEY"'" "'"$PUBKEY"'" "'"$HOST"'".dmz')
+
+jq -n --arg cert "$CERT" '{"cert":$cert}'
+
+

We see that a script is called on the remote host that issues the certificate. +This is just a simple wrapper around ssh-keygen, which you can see below.

+
#!/bin/bash
+set -euo pipefail
+IFS=$'\n\t'
+
+host() {
+	CAKEY="$2"
+	PUBKEY="$3"
+	HOST="$4"
+
+	echo "$PUBKEY" > /root/ca/"$HOST".pub
+	ssh-keygen -h -s /root/ca/keys/"$CAKEY" -I "$HOST" -n "$HOST" /root/ca/"$HOST".pub
+	cat /root/ca/"$HOST"-cert.pub
+	rm /root/ca/"$HOST"*.pub
+}
+
+"$1" "$@"
+
+

Appeasing the Terraform Gods

+

So nice, we can fetch the SSH host certificate from the CA. +We should just be able to use it right? +We can, but it brings a big annoyance with it: Terraform will fetch a new certificate every time it is run. +This is because the external feature of Terraform is a data source. +If we were to use this data source for a Terraform resource, it would need to be updated every time we run Terraform. +I have not been able to find a way to avoid fetching the certificate every time, except for writing my own resource provider which I’d rather not. +I have, however, found a way to hack around the issue.

+

The idea is as follows: we can use Terraform’s ignore_changes to, well, ignore any changes of a resource. +Unfortunately, we cannot use this for a data source, so we must create a glue null_resource that supports ignore_changes. +This is shown in the code snipppet below. +We use the triggers property simply to copy the certificate in; we don’t use it for it’s original purpose.

+
resource "null_resource" "cert" {
+  triggers = {
+    cert = data.external.cert.result["cert"]
+  }
+
+  lifecycle {
+    ignore_changes = [
+      triggers
+    ]
+  }
+}
+
+

And voilà, we can now use null_resource.cert.triggers["cert"] as our certificate, that won’t trigger replacements in Terraform.

+

Setting the Host Certificate with Cloud-Init

+

Terraform’s Libvirt provider has native support for Cloud-Init, which is very handy. +We can give the host certificate directly to Cloud-Init and place it on the virtual machine. +Inside the Cloud-Init configuration, we can set the ssh_keys property to do this:

+
ssh_keys:
+  ed25519_private: |
+    ${indent(4, private_key)}
+  ed25519_certificate: "${host_cert}"
+
+

I hardcoded this to ED25519 keys, because this is all I use.

+

This works perfectly, and I never have to accept host certificates from virtual machines again.

+

Caveats

+

A sharp eye might have noticed the lifecycle of these host certificates is severely lacking. +Namely, the deployed host certificates have no expiration date nore is there revocation function. +There are ways to implement these, but for my home lab I did not deem this necessary at this point. +In a more professional environment, I would suggest using Hashicorp’s Vault.

+

This project did teach me about the limits and flexibility of Terraform, so all in all a success! +All code can be found on the git repository here.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/42/129c8d8ed3adce9c8fb7301f8c4137151c8a5fce8bfdc7931f6d3eddecd1d0 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/42/129c8d8ed3adce9c8fb7301f8c4137151c8a5fce8bfdc7931f6d3eddecd1d0 new file mode 100644 index 0000000..ff88a77 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/42/129c8d8ed3adce9c8fb7301f8c4137151c8a5fce8bfdc7931f6d3eddecd1d0 @@ -0,0 +1,62 @@ +I"¶

For months, I’ve had a peculiar problem with my laptop: once in a while, seemingly without reason, my laptop screen would freeze. +This only happened on my laptop screen, and not on an external monitor. +I had kind of learned to live with it as I couldn’t find a solution online. +The only remedy I had was reloading my window manager, which would often unfreeze the screen.

+

Yesterday I tried Googling once more and I actually found a thread about it on the Arch Linux forums! +They talk about the same laptop model, the Lenovo ThinkPad x260, having the problem. +Fortunately, they also propose a temporary fix.

+

Trying the Fix

+

Apparently, a problem with the Panel Self Refresh (PSR) feature of Intel iGPUs is the culprit. +According to the Linux source code, PSR enables the display to go into a lower standby mode when the sytem is idle but the screen is in use. +These lower standby modes can reduce power usage of your device when idling.

+

This all seems useful, except when it makes your screen freeze! +The proposed fix disables the PSR feature entirely. +To do this, we need to change a parameter to the Intel Graphics Linux Kernel Module (LKM). +The LKM for Intel Graphics is called i915. +There are multiple ways to change kernel parameters, but I chose to edit my Grub configuration.

+

First, I wanted to test whether it actually works. +When booting into my Linux partition via Grub, you can press e to edit the Grub definition. +Somewhere there, you can find the linux command which specifies to boot Linux and how to do that. +I simply appended the option i915.enable_psr=0 to this line. +After rebooting, I noticed my screen no longer freezes! +Success!

+

Persisting the Fix

+

To make the change permanent, we need to permanently change Grub’s configuration. +One way to do this, is by changing Grub’s defaults in /etc/default/grub. +Namely, the GRUB_CMDLINE_LINUX_DEFAULT option specifies what options Grub should pass to the Linux kernel by default. +For me, this is a nice solution as the problem exists for both Linux OSes I have installed. +I changed this option to:

+
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i915.enable_psr=0"
+
+

Next, I wanted to automate this solution using Ansible. +This turned out to be quite easy, as the Grub configuration looks a bit like an ini file (maybe it is?):

+
- name: Edit grub to disable Panel Self Refresh
+  become: true
+  ini_file:
+    path: /etc/default/grub
+    section: null
+    option: "GRUB_CMDLINE_LINUX_DEFAULT"
+    value: '"quiet splash i915.enable_psr=0"'
+    no_extra_spaces: true
+  notify: update grub
+
+

Lastly, I created the notify hook to update the Grub configuration:

+
- name: update grub
+  become: true
+  command:
+    cmd: update-grub
+
+

Update: Just use Nix

+

Lately, I have been learning a bit of NixOS with the intention of replacing my current setup. +Compared to Ansible, applying this fix is a breeze on NixOS:

+
{
+  boot.kernelParams = [ "i915.enable_psr=0" ];
+}
+
+

That’s it, yep.

+

Conclusion

+

It turned out to be quite easy to change Linux kernel parameters using Ansible. +Maybe some kernel gurus have better ways to change parameters, but this works for me for now.

+

As a sidenote, I started reading a bit more about NixOS and realised that it can solve issues like these much more nicely than Ansible does. +I might replace my OS with NixOS some day, if I manage to rewrite my Ansible for it.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/4b/03ed67d8bc9c0d8fd335b681a9a1d44e97b95fb060758712befbe0a20bf7ed b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/4b/03ed67d8bc9c0d8fd335b681a9a1d44e97b95fb060758712befbe0a20bf7ed new file mode 100644 index 0000000..1261b6e --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/4b/03ed67d8bc9c0d8fd335b681a9a1d44e97b95fb060758712befbe0a20bf7ed @@ -0,0 +1,214 @@ +I"GG

I have been meaning to write about the current state of my home lab infrastructure for a while now. +Now that the most important parts are quite stable, I think the opportunity is ripe. +I expect this post to get quite long, so I might have to leave out some details along the way.

+

This post will be a starting point for future infrastructure snapshots which I can hopefully put out periodically. +That is, if there is enough worth talking about.

+

Keep an eye out for the icon, which links to the source code and configuration of anything mentioned. +Oh yeah, did I mention everything I do is open source?

+

Networking and Infrastructure Overview

+

Hardware and Operating Systems

+

Let’s start with the basics: what kind of hardware do I use for my home lab? +The most important servers are my three Gigabyte Brix GB-BLCE-4105. +Two of them have 16 GB of memory, and one 8 GB. +I named these servers as follows:

+ +

Here is a picture of them sitting in their cosy closet:

+

A picture of my servers.

+

If you look look to the left, you will also see a Raspberry pi 4B. +I use this Pi to do some rudimentary monitoring whether servers and services are running. +More on this in the relevant section below. +The Pi is called Iris because it’s a messenger for the other servers.

+

I used to run Ubuntu on these systems, but I have since migrated away to Debian. +The main reasons were Canonical putting advertisements in my terminal and pushing Snap which has a proprietry backend. +Two of my servers run the newly released Debian Bookworm, while one still runs Debian Bullseye.

+

Networking

+

For networking, I wanted hypervisors and virtual machines separated by VLANs for security reasons. +The following picture shows a simplified view of the VLANs present in my home lab:

+

Picture showing the VLANS in my home lab.

+

All virtual machines are connected to a virtual bridge which tags network traffic with the DMZ VLAN. +The hypervisors VLAN is used for traffic to and from the hypervisors. +Devices from the hypervisors VLAN are allowed to connect to devices in the DMZ, but not vice versa. +The hypervisors are connected to a switch using a trunk link, allows both DMZ and hypervisors traffic.

+

I realised the above design using ifupdown. +Below is the configuration for each hypervisor, which creates a new enp3s0.30 interface with all DMZ traffic from the enp3s0 interface .

+
auto enp3s0.30
+iface enp3s0.30 inet manual
+iface enp3s0.30 inet6 auto
+	accept_ra 0
+	dhcp 0
+	request_prefix 0
+	privext 0
+	pre-up sysctl -w net/ipv6/conf/enp3s0.30/disable_ipv6=1
+
+

This configuration seems more complex than it actually is. +Most of it is to make sure the interface is not assigned an IPv4/6 address on the hypervisor host. +The magic .30 at the end of the interface name makes this interface tagged with VLAN ID 30 (DMZ for me).

+

Now that we have an interface tagged for the DMZ VLAN, we can create a bridge where future virtual machines can connect to:

+
auto dmzbr
+iface dmzbr inet manual
+	bridge_ports enp3s0.30
+	bridge_stp off
+iface dmzbr inet6 auto
+	accept_ra 0
+	dhcp 0
+	request_prefix 0
+	privext 0
+	pre-up sysctl -w net/ipv6/conf/dmzbr/disable_ipv6=1
+
+

Just like the previous config, this is quite bloated because I don’t want the interface to be assigned an IP address on the host. +Most importantly, the bridge_ports enp3s0.30 line here makes this interface a virtual bridge for the enp3s0.30 interface.

+

And voilĂ , we now have a virtual bridge on each machine, where only DMZ traffic will flow. +Here I verify whether this configuration works:

+
+ Show +

We can see that the two virtual interfaces are created, and are only assigned a MAC address and not a IP address:

+
root@atlas:~# ip a show enp3s0.30
+4: enp3s0.30@enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master dmzbr state UP group default qlen 1000
+    link/ether d8:5e:d3:4c:70:38 brd ff:ff:ff:ff:ff:ff
+5: dmzbr: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
+    link/ether 4e:f7:1f:0f:ad:17 brd ff:ff:ff:ff:ff:ff
+
+

Pinging a VM from a hypervisor works:

+
root@atlas:~# ping -c1 maestro.dmz
+PING maestro.dmz (192.168.30.8) 56(84) bytes of data.
+64 bytes from 192.168.30.8 (192.168.30.8): icmp_seq=1 ttl=63 time=0.457 ms
+
+

Pinging a hypervisor from a VM does not work:

+
root@maestro:~# ping  -c1 atlas.hyp
+PING atlas.hyp (192.168.40.2) 56(84) bytes of data.
+
+--- atlas.hyp ping statistics ---
+1 packets transmitted, 0 received, 100% packet loss, time 0ms
+
+
+

DNS and DHCP

+

Now that we have a working DMZ network, let’s build on it to get DNS and DHCP working. +This will enable new virtual machines to obtain a static or dynamic IP address and register their host in DNS. +This has actually been incredibly annoying due to our friend Network address translation (NAT).

+
+ NAT recap +

Network address translation (NAT) is a function of a router which allows multiple hosts to share a single IP address. +This is needed for IPv4, because IPv4 addresses are scarce and usually one household is only assigned a single IPv4 address. +This is one of the problems IPv6 attempts to solve (mainly by having so many IP addresses that they should never run out). +To solve the problem for IPv4, each host in a network is assigned a private IPv4 address, which can be reused for every network.

+

Then, the router must perform address translation. +It does this by keeping track of ports opened by hosts in its private network. +If a packet from the internet arrives at the router for such a port, it forwards this packet to the correct host.

+
+

I would like to host my own DNS on a virtual machine (called hermes, more on VMs later) in the DMZ network. +This basically gives two problems:

+
    +
  1. The upstream DNS server will refer to the public internet-accessible IP address of our DNS server. +This IP-address has no meaning inside the private network due to NAT and the router will reject the packet.
  2. +
  3. Our DNS resolves hosts to their public internet-accessible IP address. +This is similar to the previous problem as the public IP address has no meaning.
  4. +
+

The first problem can be remediated by overriding the location of the DNS server for hosts inside the DMZ network. +This can be achieved on my router, which uses Unbound as its recursive DNS server:

+

Unbound overides for kun.is and dmz domains.

+

Any DNS requests to Unbound to domains in either dmz or kun.is will now be forwarded 192.168.30.7 (port 5353). +This is the virtual machine hosting my DNS.

+

The second problem can be solved at the DNS server. +We need to do some magic overriding, which dnsmasq is perfect for :

+
alias=84.245.14.149,192.168.30.8
+server=/kun.is/192.168.30.7
+
+

This always overrides the public IPv4 address to the private one. +It also overrides the DNS server for kun.is to 192.168.30.7.

+

Finally, behind the dnsmasq server, I run Powerdns as authoritative DNS server . +I like this DNS server because I can manage it with Terraform .

+

Here is a small diagram showing my setup (my networking teacher would probably kill me for this): +Shitty diagram showing my DNS setup.

+

Virtualization

+

https://github.com/containrrr/shepherd +Now that we have laid out the basic networking, let’s talk virtualization. +Each of my servers are configured to run KVM virtual machines, orchestrated using Libvirt. +Configuration of the physical hypervisor servers, including KVM/Libvirt is done using Ansible. +The VMs are spun up using Terraform and the dmacvicar/libvirt Terraform provider.

+

This all isn’t too exciting, except that I created a Terraform module that abstracts the Terraform Libvirt provider for my specific scenario :

+
module "maestro" {
+  source          = "git::https://git.kun.is/home/tf-modules.git//debian"
+  name            = "maestro"
+  domain_name     = "tf-maestro"
+  memory          = 10240
+  mac             = "CA:FE:C0:FF:EE:08"
+}
+
+

This automatically creates a Debian virtual machines with the properties specified. +It also sets up certificate-based SSH authentication which I talked about before.

+

Clustering

+

With virtualization explained, let’s move up one level further. +Each of my three physical servers hosts a virtual machine running Docker, which together form a Docker Swarm. +I use Traefik as a reverse proxy which routes requests to the correct container.

+

All data is hosted on a single machine and made available to containers using NFS. +This might not be very secure (as NFS is not encrypted and no proper authentication), it is quite fast.

+

As of today, I host the following services on my Docker Swarm :

+ +

CI / CD

+

For CI / CD, I run Concourse CI in a separate VM. +This is needed, because Concourse heavily uses containers to create reproducible builds.

+

Although I should probably use it for more, I currently use my Concourse for three pipelines:

+ +

Backups

+

To create backups, I use Borg. +As I keep all data on one machine, this backup process is quite simple. +In fact, all this data is stored in a single Libvirt volume. +To configure Borg with a simple declarative script, I use Borgmatic.

+

In order to back up the data inside the Libvirt volume, I create a snapshot to a file. +Then I can mount this snapshot in my file system. +The files can then be backed up while the system is still running. +It is also possible to simply back up the Libvirt image, but this takes more time and storage .

+

Monitoring and Alerting

+

The last topic I would like to talk about is monitoring and alerting. +This is something I’m still actively improving and only just set up properly.

+

Alerting

+

For alerting, I wanted something that runs entirely on my own infrastructure. +I settled for Apprise + Ntfy.

+

Apprise is a server that is able to send notifications to dozens of services. +For application developers, it is thus only necessary to implement the Apprise API to gain access to all these services. +The Apprise API itself is also very simple. +By using Apprise, I can also easily switch to another notification service later. +Ntfy is free software made for mobile push notifications.

+

I use this alerting system in quite a lot of places in my infrastructure, for example when creating backups.

+

Uptime Monitoring

+

The first monitoring setup I created, was using Uptime Kuma. +Uptime Kuma periodically pings a service to see whether it is still running. +You can do a literal ping, test HTTP response codes, check database connectivity and much more. +I use it to check whether my services and VMs are online. +And the best part is, Uptime Kuma supports Apprise so I get push notifications on my phone whenever something goes down!

+

Metrics and Log Monitoring

+

A new monitoring system I am still in the process of deploying is focused on metrics and logs. +I plan on creating a separate blog post about this, so keep an eye out on that (for example using RSS :)). +Safe to say, it is no basic ELK stack!

+

Conclusion

+

That’s it for now! +Hopefully I inspired someone to build something… or how not to :)

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/4d/1f7103582bf7a375b7e199c115b152c6960d408bde12ec01191a8b470bc37d b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/4d/1f7103582bf7a375b7e199c115b152c6960d408bde12ec01191a8b470bc37d new file mode 100644 index 0000000..28af8b8 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/4d/1f7103582bf7a375b7e199c115b152c6960d408bde12ec01191a8b470bc37d @@ -0,0 +1,2 @@ +I"U

See the Update at the end of the article.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/5a/79890c75774971d86fa3a9500f60acb485aab92f38b4d59116b26fa5b065a5 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/5a/79890c75774971d86fa3a9500f60acb485aab92f38b4d59116b26fa5b065a5 new file mode 100644 index 0000000..e68b7a4 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/5a/79890c75774971d86fa3a9500f60acb485aab92f38b4d59116b26fa5b065a5 @@ -0,0 +1,6 @@ +I"f

BorgBackup and Borgmatic have been my go-to tools to create backups for my home lab since I started creating backups. +Using Systemd Timers, I regularly create a backup every night. +I also monitor successful execution of the backup process, in case some error occurs. +However, the way I set this up resulted in not receiving notifications. +Even though it boils down to RTFM, I’d like to explain my error and how to handle errors correctly.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/5c/8fa8240a1640578f5aa674d4f7f9445d7b22817fc1803dbddfc2b5fe30da33 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/5c/8fa8240a1640578f5aa674d4f7f9445d7b22817fc1803dbddfc2b5fe30da33 new file mode 100644 index 0000000..d45a833 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/5c/8fa8240a1640578f5aa674d4f7f9445d7b22817fc1803dbddfc2b5fe30da33 @@ -0,0 +1,7 @@ +I"1

When I was scaling up my home lab, I started thinking more about data management. +I hadn’t (and still haven’t) set up any form of network storage. +I have, however, set up a backup mechanism using Borg. +Still, I want to operate lots of virtual machines, and backing up each one of them separately seemed excessive. +So I started thinking, what if I just let the host machines back up the data? +After all, the amount of physical hosts I have in my home lab is unlikely to increase drastically.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/91/5e385b14adac9ab2123fd9d2352da7c5bb04d9882563beb3316a61554ca575 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/91/5e385b14adac9ab2123fd9d2352da7c5bb04d9882563beb3316a61554ca575 new file mode 100644 index 0000000..9a3212d --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/91/5e385b14adac9ab2123fd9d2352da7c5bb04d9882563beb3316a61554ca575 @@ -0,0 +1,4 @@ +I"

I have been meaning to write about the current state of my home lab infrastructure for a while now. +Now that the most important parts are quite stable, I think the opportunity is ripe. +I expect this post to get quite long, so I might have to leave out some details along the way.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/95/3072d9307fff41fb3452f89e0c9cc99bcf4bcbf90a5e819a6827177e476177 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/95/3072d9307fff41fb3452f89e0c9cc99bcf4bcbf90a5e819a6827177e476177 new file mode 100644 index 0000000..924975d --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/95/3072d9307fff41fb3452f89e0c9cc99bcf4bcbf90a5e819a6827177e476177 @@ -0,0 +1,54 @@ +I"Ű

Previously, I have used Prometheus’ node_exporter to monitor the memory usage of my servers. +However, I am currently in the process of moving away from Prometheus to a new Monioring stack. +While I understand the advantages, I felt like Prometheus’ pull architecture does not scale nicely. +Everytime I spin up a new machine, I would have to centrally change Prometheus’ configuration in order for it to query the new server.

+

In order to collect metrics from my servers, I am now using Fluent Bit. +I love Fluent Bit’s way of configuration which I can easily express as code and automate, its focus on effiency and being vendor agnostic. +However, I have stumbled upon one, in my opinion, big issue with Fluent Bit: its mem plugin to monitor memory usage is completely useless. +In this post I will go over the problem and my temporary solution.

+

The Problem with Fluent Bit’s mem Plugin

+

As can be seen in the documentation, Fluent Bit’s mem input plugin exposes a few metrics regarding memory usage which should be self-explaining: Mem.total, Mem.used, Mem.free, Swap.total, Swap.used and Swap.free. +The problem is that Mem.used and Mem.free do not accurately reflect the machine’s actual memory usage. +This is because these metrics include caches and buffers, which can be reclaimed by other processes if needed. +Most tools reporting memory usage therefore include an additional metric that specifices the memory available on the system. +For example, the command free -m reports the following data on my laptop:

+
               total        used        free      shared  buff/cache   available
+Mem:           15864        3728        7334         518        5647       12136
+Swap:           2383         663        1720
+
+

Notice that the available memory is more than free memory.

+

While the issue is known (see this and this link), it is unfortunately not yet fixed.

+

A Temporary Solution

+

The issues I linked previously provide stand-alone plugins that fix the problem, which will hopefully be merged in the official project at some point. +However, I didn’t want to install another plugin so I used Fluent Bit’s exec input plugin and the free Linux command to query memory usage like so:

+
[INPUT]
+    Name exec
+    Tag memory
+    Command free -m | tail -2 | tr '\n' ' '
+    Interval_Sec 1
+
+

To interpret the command’s output, I created the following filter:

+
[FILTER]
+    Name parser
+    Match memory
+    Key_Name exec
+    Parser free
+
+

Lastly, I created the following parser (warning: regex shitcode incoming):

+
[PARSER]
+    Name free
+    Format regex
+    Regex ^Mem:\s+(?<mem_total>\d+)\s+(?<mem_used>\d+)\s+(?<mem_free>\d+)\s+(?<mem_shared>\d+)\s+(?<mem_buff_cache>\d+)\s+(?<mem_available>\d+) Swap:\s+(?<swap_total>\d+)\s+(?<swap_used>\d+)\s+(?<swap_free>\d+)
+    Types mem_total:integer mem_used:integer mem_free:integer mem_shared:integer mem_buff_cache:integer mem_available:integer swap_total:integer swap_used:integer
+
+

With this configuration, you can use the mem_available metric to get accurate memory usage in Fluent Bit.

+

Conclusion

+

Let’s hope Fluent Bit’s mem input plugin is improved upon soon so this hacky solution is not needed. +I also intend to document my new monitoring pipeline, which at the moment consists of:

+ +:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/a0/3c83cf411782db1c50d9126663f49970c6f45dc99e8db7ec0da9af3b56d618 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/a0/3c83cf411782db1c50d9126663f49970c6f45dc99e8db7ec0da9af3b56d618 new file mode 100644 index 0000000..50e89b2 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/a0/3c83cf411782db1c50d9126663f49970c6f45dc99e8db7ec0da9af3b56d618 @@ -0,0 +1,4 @@ +I"ť

Recently, I deployed Concourse CI because I wanted to get my feet wet with a CI/CD pipeline. +However, I had a practical use case lying around for a long time: automatically compiling my static website and deploying it to my docker Swarm. +This took some time getting right, but the result works like a charm (source code).

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/a3/2451b46abf31bffd4ef16c4fba342105d08b449ffcd5d8857fac68c8d666ac b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/a3/2451b46abf31bffd4ef16c4fba342105d08b449ffcd5d8857fac68c8d666ac new file mode 100644 index 0000000..8a31833 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/a3/2451b46abf31bffd4ef16c4fba342105d08b449ffcd5d8857fac68c8d666ac @@ -0,0 +1,5 @@ +I"

For months, I’ve had a peculiar problem with my laptop: once in a while, seemingly without reason, my laptop screen would freeze. +This only happened on my laptop screen, and not on an external monitor. +I had kind of learned to live with it as I couldn’t find a solution online. +The only remedy I had was reloading my window manager, which would often unfreeze the screen.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/b0/5eaa7d51955d5eb4c0531861b6ceff386cf59937bb012e007e75bb17be3a70 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/b0/5eaa7d51955d5eb4c0531861b6ceff386cf59937bb012e007e75bb17be3a70 new file mode 100644 index 0000000..9a2b2e4 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/b0/5eaa7d51955d5eb4c0531861b6ceff386cf59937bb012e007e75bb17be3a70 @@ -0,0 +1,9 @@ +I"ł

Finally, after several months this website is up and running again!

+

My homelab has completely changed, but the reason why it initially went offline is because of my failing CI installation. +I was using Concourse CI which I was initially interested in due to the reproducible nature of its builds using containers. +However, for some reason pipelines were sporadically getting stuck when I reboot the virtual machine it was running on. +The fix was very annoying: I had to re-create the pipelines manually (which feels very backwards for a CI/CD system!) +Additionally, my virtual machine setup back then was also quite fragile and I decided to get rid of that as well.

+

I have learned that having an escape hatch to deploy something is probably a good idea đź… +Expect a new overview of my homelab soon, in the same vein as this post from last year!

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/b4/97aba66d754cde21ccf57911a944a2c6cdecdcf1af5627383af00a22c1698a b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/b4/97aba66d754cde21ccf57911a944a2c6cdecdcf1af5627383af00a22c1698a new file mode 100644 index 0000000..0febaba --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/b4/97aba66d754cde21ccf57911a944a2c6cdecdcf1af5627383af00a22c1698a @@ -0,0 +1,38 @@ +I":

See the Update at the end of the article.

+

Already a week ago, Hashicorp announced it would change the license on almost all its projects. +Unlike their previous license, which was the Mozilla Public License 2.0, their new license is no longer truly open source. +It is called the Business Source License™ and restricts use of their software for competitors. +In their own words:

+
+

Vendors who provide competitive services built on our community products will no longer be able to incorporate future releases, bug fixes, or security patches contributed to our products.

+
+

I found a great article by MeshedInsights that names this behaviour the “rights ratchet model”. +They define a script start-ups use to garner the interest of open source enthusiasts but eventually turn their back on them for profit. +The reason why Hashicorp can do this, is because contributors signed a copyright license agreement (CLA). +This agreement transfers the copyright of contributors’ code to Hashicorp, allowing them to change the license if they want to.

+

I find this action really regrettable because I like their products. +This sort of action was also why I wanted to avoid using an Elastic stack, which also had their license changed.1 +These companies do not respect their contributors and the software stack beneath they built their product on, which is actually open source (Golang, Linux, etc.).

+

Impact on my Home Lab

+

I am using Terraform in my home lab to manage several important things:

+ +

With Hashicorp’s anti open source move, I intend to move away from Terraform in the future. +While I will not use Hashicorp’s products for new personal projects, I will leave my current setup as-is for some time because there is no real need to quickly migrate.

+

I might also investigate some of Terraform’s competitors, like Pulumi. +Hopefully there is a project that respects open source which I can use in the future.

+

Update

+

A promising fork of Terraform has been announced called OpenTF. +They intend to take part of the Cloud Native Computing Foundation, which I think is a good effort because Terraform is so important for modern cloud infrastructures.

+

Footnotes

+
+
    +
  1. +

    While I am still using Elasticsearch, I don’t use the rest of the Elastic stack in order to prevent a vendor lock-in. ↩

    +
  2. +
+
+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/ca/fa91a891b326d3785106ef39feb14d15f250d4e9053e26bc4a59df1be95175 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/ca/fa91a891b326d3785106ef39feb14d15f250d4e9053e26bc4a59df1be95175 new file mode 100644 index 0000000..938b887 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/ca/fa91a891b326d3785106ef39feb14d15f250d4e9053e26bc4a59df1be95175 @@ -0,0 +1,2 @@ +I"H

Here I might post some personally identifiable information.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/cf/a279eddeb4b979b0e9e6555e6f42751b96af7f180d0a086c18cac528a356b7 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/cf/a279eddeb4b979b0e9e6555e6f42751b96af7f180d0a086c18cac528a356b7 new file mode 100644 index 0000000..2001e9f --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/cf/a279eddeb4b979b0e9e6555e6f42751b96af7f180d0a086c18cac528a356b7 @@ -0,0 +1,41 @@ +I"q

When I was scaling up my home lab, I started thinking more about data management. +I hadn’t (and still haven’t) set up any form of network storage. +I have, however, set up a backup mechanism using Borg. +Still, I want to operate lots of virtual machines, and backing up each one of them separately seemed excessive. +So I started thinking, what if I just let the host machines back up the data? +After all, the amount of physical hosts I have in my home lab is unlikely to increase drastically.

+

The Use Case for Sharing Directories

+

I started working out this idea further. +Without network storage, I needed a way for guest VMs to access the host’s disks. +Here there are two possibilities, either expose some block device or a file system. +Creating a whole virtual disk for just the data of some VMs seemed wasteful, and from my experiences also increases backup times dramatically. +I therefore searched for a way to mount a directory from the host OS on the guest VM. +This is when I stumbled upon this blog post talking about sharing directories with virtual machines.

+

Sharing Directories with virtio-9p

+

virtio-9p is a way to map a directory on the host OS to a special device on the virtual machine. +In virt-manager, it looks like the following: +picture showing virt-manager configuration to map a directory to a VM +Under the hood, virtio-9p uses the 9pnet protocol. +Originally developed at Bell Labs, support for this is available in all modern Linux kernels. +If you share a directory with a VM, you can then mount it. +Below is an extract of my /etc/fstab to automatically mount the directory:

+
data	/mnt/data	9p	trans=virtio,rw	0	0
+
+

The first argument (data) refers to the name you gave this share from the host +With the trans option we specify that this is a virtio share.

+

Problems with virtio-9p

+

At first I had no problems with my setup, but I am now contemplating just moving to a network storage based setup because of two problems.

+

The first problem is that some files have suddenly changed ownership from libvirt-qemu to root. +If the file is owned by root, the guest OS can still see it, but cannot access it. +I am not entirely sure the problem lies with virtio, but I suspect it is. +For anyone experiencing this problem, I wrote a small shell script to revert ownership to the libvirt-qemu user:

+
find -printf "%h/%f %u\n"  | grep root | cut -d ' ' -f1 | xargs chown libvirt-qemu:libvirt-qemu
+
+

Another problem that I have experienced, is guests being unable to mount the directory at all. +I have only experienced this problem once, but it was highly annoying. +To fix it, I had to reboot the whole physical machine.

+

Alternatives

+

virtio-9p seemed like a good idea, but as discussed, I had some problems with it. +It seems virtioFS might be a an interesting alternative as it is designed specifically for sharing directories with VMs.

+

As for me, I will probably finally look into deploying network storage either with NFS or SSHFS.

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/dd/fa550314d3f6e485fefcd71068b25447db3acbd8d5f496b19d502161a999cd b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/dd/fa550314d3f6e485fefcd71068b25447db3acbd8d5f496b19d502161a999cd new file mode 100644 index 0000000..e1d86bf --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/dd/fa550314d3f6e485fefcd71068b25447db3acbd8d5f496b19d502161a999cd @@ -0,0 +1,42 @@ +I"

BorgBackup and Borgmatic have been my go-to tools to create backups for my home lab since I started creating backups. +Using Systemd Timers, I regularly create a backup every night. +I also monitor successful execution of the backup process, in case some error occurs. +However, the way I set this up resulted in not receiving notifications. +Even though it boils down to RTFM, I’d like to explain my error and how to handle errors correctly.

+

I was using the on_error option to handle errors, like so:

+
on_error:
+  - 'apprise --body="Error while performing backup" <URL> || true'
+
+

However, on_error does not handle errors from the execution of before_everything and after_everything hooks. +My solution to this was moving the error handling up to the Systemd service that calls Borgmatic. +This results in the following Systemd service:

+
[Unit]
+Description=Backup data using Borgmatic
+# Added
+OnFailure=backup-failure.service
+
+[Service]
+ExecStart=/usr/bin/borgmatic --config /root/backup.yml
+Type=oneshot
+
+

This handles any error, be it from Borgmatic’s hooks or itself. +The backup-failure service is very simple, and just calls Apprise to send a notification:

+
[Unit]
+Description=Send backup failure notification
+
+[Service]
+Type=oneshot
+ExecStart=apprise --body="Failed to create backup!" <URL>
+
+[Install]
+WantedBy=multi-user.target
+
+

The Aftermath (or what I learned)

+

Because the error handling and alerting weren’t working propertly, my backups didn’t succeed for two weeks straight. +And, of course, you only notice your backups aren’t working when you actually need them. +This is exactly what happened: my disk was full and a MariaDB database crashed as a result of that. +Actually, the whole database seemed to be corrupt and I find it worrying MariaDB does not seem to be very resilient to failures (in comparison a PostgreSQL database was able to recover automatically). +I then tried to recover the data using last night’s backup, only to find out there was no such backup. +Fortunately, I had other means to recover the data so I incurred no data loss.

+

I already knew it is important to test backups, but I learned it is also important to test failures during backups!

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/de/3e2dabf1802e61ca9f839af6ae4614ee4dd39a3db773a8ac7c3b1dcc8d5b5c b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/de/3e2dabf1802e61ca9f839af6ae4614ee4dd39a3db773a8ac7c3b1dcc8d5b5c new file mode 100644 index 0000000..9ab03e1 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/de/3e2dabf1802e61ca9f839af6ae4614ee4dd39a3db773a8ac7c3b1dcc8d5b5c @@ -0,0 +1,2 @@ +I"P

Finally, after several months this website is up and running again!

+:ET \ No newline at end of file diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/e3/b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/e3/b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 new file mode 100644 index 0000000..177adbd Binary files /dev/null and b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/e3/b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 differ diff --git a/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/ee/52e5fa863848f5119ac696d0af443c67f63ca3732d79fbd250d0c2ebb8bbb8 b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/ee/52e5fa863848f5119ac696d0af443c67f63ca3732d79fbd250d0c2ebb8bbb8 new file mode 100644 index 0000000..8796725 --- /dev/null +++ b/src/.jekyll-cache/Jekyll/Cache/Jekyll--Converters--Markdown/ee/52e5fa863848f5119ac696d0af443c67f63ca3732d79fbd250d0c2ebb8bbb8 @@ -0,0 +1,5 @@ +I"ď

Previously, I have used Prometheus’ node_exporter to monitor the memory usage of my servers. +However, I am currently in the process of moving away from Prometheus to a new Monioring stack. +While I understand the advantages, I felt like Prometheus’ pull architecture does not scale nicely. +Everytime I spin up a new machine, I would have to centrally change Prometheus’ configuration in order for it to query the new server.

+:ET \ No newline at end of file diff --git a/src/404.md b/src/404.md new file mode 100644 index 0000000..59e993f --- /dev/null +++ b/src/404.md @@ -0,0 +1,5 @@ +--- +title: "404" +layout: 404 +permalink: "/404.html" +--- diff --git a/src/Gemfile b/src/Gemfile new file mode 100644 index 0000000..75ad158 --- /dev/null +++ b/src/Gemfile @@ -0,0 +1,34 @@ +source "https://rubygems.org" + +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! +gem "jekyll", "~> 4.1.0" + +# This is the default theme for new Jekyll sites. You may change this to anything you like. + +# If you want to use GitHub Pages, remove the "gem "jekyll"" above and +# uncomment the line below. To upgrade, run `bundle update github-pages`. +# gem "github-pages", group: :jekyll_plugins + +# If you have any plugins, put them here! +group :jekyll_plugins do + gem 'jekyll-feed', '~> 0.13' + gem 'jekyll-sitemap', '~> 1.4' + gem 'jekyll-compose', '~> 0.12.0' + gem 'jekyll-postfiles', '~> 3.1' + gem 'jekyll-commonmark-ghpages' +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +# gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +# Performance-booster for watching directories on Windows +# gem "wdm", "~> 0.1.0" if Gem.win_platform? + +gem "webrick", "~> 1.7" diff --git a/src/Gemfile.lock b/src/Gemfile.lock new file mode 100644 index 0000000..9240647 --- /dev/null +++ b/src/Gemfile.lock @@ -0,0 +1,93 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) + colorator (1.1.0) + commonmarker (0.17.13) + ruby-enum (~> 0.5) + concurrent-ruby (1.2.0) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.15.5) + forwardable-extended (2.6.0) + http_parser.rb (0.8.0) + i18n (1.12.0) + concurrent-ruby (~> 1.0) + jekyll (4.1.1) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (~> 2.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (~> 0.4.0) + pathutil (~> 0.9) + rouge (~> 3.0) + safe_yaml (~> 1.0) + terminal-table (~> 1.8) + jekyll-commonmark (1.3.1) + commonmarker (~> 0.14) + jekyll (>= 3.7, < 5.0) + jekyll-commonmark-ghpages (0.1.6) + commonmarker (~> 0.17.6) + jekyll-commonmark (~> 1.2) + rouge (>= 2.0, < 4.0) + jekyll-compose (0.12.0) + jekyll (>= 3.7, < 5.0) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-postfiles (3.1.0) + jekyll (>= 3.8.6, < 5) + jekyll-sass-converter (2.2.0) + sassc (> 2.0.1, < 3.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.4.0) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (5.0.1) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.5) + rouge (3.30.0) + ruby-enum (0.9.0) + i18n + safe_yaml (1.0.5) + sassc (2.4.0) + ffi (~> 1.9) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + unicode-display_width (1.8.0) + webrick (1.7.0) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + jekyll (~> 4.1.0) + jekyll-commonmark-ghpages + jekyll-compose (~> 0.12.0) + jekyll-feed (~> 0.13) + jekyll-postfiles (~> 3.1) + jekyll-sitemap (~> 1.4) + webrick (~> 1.7) + +BUNDLED WITH + 2.4.5 diff --git a/src/_config.yml b/src/_config.yml new file mode 100644 index 0000000..18b40da --- /dev/null +++ b/src/_config.yml @@ -0,0 +1,77 @@ +title: Pim Kunis +description: A pig's gotta fly +lang: en-US +timezone: Europe/Amsterdam +image: assets/img/avatar.jpg +repo: https://git.kun.is/pim/static +mode: light + +author: + name: Pim Kunis + bio: A pig's gotta fly + username: pim + avatar: /assets/img/avatar.jpg + +url: "https://pim.kun.is" +baseurl: "/" +permalink: /:title/ + +collections: + posts: + output: true + +markdown: CommonMarkGhPages +highlighter: rouge +kramdown: + syntax_highlighter: rouge + +defaults: + - scope: + path: "" + values: + layout: post + comments: false + +jekyll_compose: + post_default_front_matter: + modified: + tags: [] + description: + draft_default_front_matter: + modified: + tags: [] + description: + +number_of_posts: 5 + +sass: + style: compressed + +include: + - _redirects + - .htaccess + +exclude: + - CNAME + - Gemfile + - Gemfile.lock + - LICENSE + - CHANGELOG.md + - README.md + - node_modules + - CODE_OF_CONDUCT.md + - CONTRIBUTING.md + - lighthouse.png + - klise-*.gem + - klise.gemspec + - gemset.nix + +plugins: + - jekyll-feed + - jekyll-sitemap + - jekyll-postfiles + - jekyll-commonmark-ghpages + +commonmark: + options: ["SMART", "FOOTNOTES"] + extensions: ["strikethrough", "autolink", "table", "tagfilter"] diff --git a/src/_data/menus.yml b/src/_data/menus.yml new file mode 100644 index 0000000..78daf1c --- /dev/null +++ b/src/_data/menus.yml @@ -0,0 +1,16 @@ +- title: home + url: / + external: false + +- title: archive + url: /archive/ + external: false + +- title: about + url: /about/ + external: false # set true if you using external link, see below + +# Example: +# - title: github +# url: https://github.com/piharpi/jekyll-klise +# external: true diff --git a/src/_includes/anchor_headings.html b/src/_includes/anchor_headings.html new file mode 100644 index 0000000..7a56b16 --- /dev/null +++ b/src/_includes/anchor_headings.html @@ -0,0 +1,105 @@ +{% capture headingsWorkspace %} + {% comment %} + Version 1.0.4 + https://github.com/allejo/jekyll-anchor-headings + + "Be the pull request you wish to see in the world." ~Ben Balter + + Usage: + {% include anchor_headings.html html=content %} + + Parameters: + * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll + + Optional Parameters: + * beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content + * anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `` tag; you may NOT use `href`, `class` or `title` + * anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available + * anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space + * anchorTitle (string) : '' - The `title` attribute that will be used for anchors + * h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored + * h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored + * bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content + * bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content + + Output: + The original HTML with the addition of anchors inside of all of the h1-h6 headings. + {% endcomment %} + + {% assign minHeader = include.h_min | default: 1 %} + {% assign maxHeader = include.h_max | default: 6 %} + {% assign beforeHeading = include.beforeHeading %} + {% assign nodes = include.html | split: ' + {% if headerLevel == 0 %} + {% if nextChar != '<' and nextChar != '' %} + {% capture node %}' | first }}>{% endcapture %} + {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} + + + {% capture anchor %}{% endcapture %} + + {% if html_id and headerLevel >= minHeader and headerLevel <= maxHeader %} + {% capture anchor %}href="#{{ html_id }}"{% endcapture %} + + {% if include.anchorClass %} + {% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %} + {% endif %} + + {% if include.anchorTitle %} + {% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', header }}"{% endcapture %} + {% endif %} + + {% if include.anchorAttrs %} + {% capture anchor %}{{ anchor }} {{ include.anchorAttrs }}{% endcapture %} + {% endif %} + + {% capture anchor %}{{ include.anchorBody | replace: '%heading%', header | default: '' }}{% endcapture %} + + + {% if beforeHeading %} + {% capture anchor %}{{ anchor }} {% endcapture %} + {% else %} + {% capture anchor %} {{ anchor }}{% endcapture %} + {% endif %} + {% endif %} + + {% capture new_heading %} + + {{ site.author.username }} +

{{ site.author.name }}

+

{{ site.author.bio }}

+ diff --git a/src/_includes/comments.html b/src/_includes/comments.html new file mode 100644 index 0000000..347bc6f --- /dev/null +++ b/src/_includes/comments.html @@ -0,0 +1,10 @@ + + diff --git a/src/_includes/footer.html b/src/_includes/footer.html new file mode 100644 index 0000000..64e6718 --- /dev/null +++ b/src/_includes/footer.html @@ -0,0 +1,28 @@ + + +{%- if page.url == '/archive/' -%} + + +{%- endif -%} diff --git a/src/_includes/header.html b/src/_includes/header.html new file mode 100644 index 0000000..b93c524 --- /dev/null +++ b/src/_includes/header.html @@ -0,0 +1,165 @@ + + + + + + + + + + + {% if page.title %}{{ page.title | escape }} - {{ site.title }}{% else %}{{ + site.title | escape }}{% endif %} + + + + {% if paginator.previous_page %} + + {% endif %} {% if paginator.next_page %} + + {% endif %} + + + + + + {% if page.location %} + + + {% else %} + + {% endif %} + + + {% if page.image %} + + {% else %} + + {% endif %} + + + + + + + + + {% if page.image %} + + {% else %} + + {% endif %} {% feed_meta %} + + + + + + + + + + + + + diff --git a/src/_includes/navbar.html b/src/_includes/navbar.html new file mode 100644 index 0000000..018c5a0 --- /dev/null +++ b/src/_includes/navbar.html @@ -0,0 +1,211 @@ + diff --git a/src/_includes/navigation.html b/src/_includes/navigation.html new file mode 100644 index 0000000..83e3a61 --- /dev/null +++ b/src/_includes/navigation.html @@ -0,0 +1,16 @@ + diff --git a/src/_includes/pagination.html b/src/_includes/pagination.html new file mode 100644 index 0000000..71b28f9 --- /dev/null +++ b/src/_includes/pagination.html @@ -0,0 +1,21 @@ + + diff --git a/src/_layouts/404.html b/src/_layouts/404.html new file mode 100644 index 0000000..77bbabd --- /dev/null +++ b/src/_layouts/404.html @@ -0,0 +1,45 @@ +--- +layout: compress +--- + + + + +{% include header.html %} + + + + + {% include navbar.html %} +
+
+ +
+ {% include footer.html %} +
+ + + + diff --git a/src/_layouts/compress.html b/src/_layouts/compress.html new file mode 100644 index 0000000..8b71fb8 --- /dev/null +++ b/src/_layouts/compress.html @@ -0,0 +1,4 @@ +--- +--- + +{% if site.compress_html.ignore.envs contains jekyll.environment %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd p rt rp optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if site.compress_html.comments.size == 2 %}{% assign _comment_befores = _content | split: site.compress_html.comments.first %}{% for _comment_before in _comment_befores %}{% assign _comment_content = _comment_before | split: site.compress_html.comments.last | first %}{% if _comment_content %}{% capture _comment %}{{ site.compress_html.comments.first }}{{ _comment_content }}{{ site.compress_html.comments.last }}{% endcapture %}{% assign _content = _content | remove: _comment %}{% endif %}{% endfor %}{% endif %}{% assign _pre_befores = _content | split: "" %}{% case _pres.size %}{% when 2 %}{% capture _content %}{{ _content }}{{ _pres.last | split: " " | join: " " }}{% endcapture %}{% when 1 %}{% capture _content %}{{ _content }}{{ _pres.last | split: " " | join: " " }}{% endcapture %}{% endcase %}{% endfor %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " ;; ;" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{{ _content }}{% endif %} diff --git a/src/_layouts/default.html b/src/_layouts/default.html new file mode 100644 index 0000000..1500975 --- /dev/null +++ b/src/_layouts/default.html @@ -0,0 +1,38 @@ +--- +layout: compress +--- + + +{% include header.html %} + + + + + + {% include navbar.html %} +
+ {% include author.html %} +
+ {{ content }} +
+ {% include footer.html %} +
+ + diff --git a/src/_layouts/home.html b/src/_layouts/home.html new file mode 100644 index 0000000..d147051 --- /dev/null +++ b/src/_layouts/home.html @@ -0,0 +1,14 @@ +--- +layout: default +home: true +--- + +

Recent Posts

+{%- for post in site.posts limit: site.number_of_posts -%} + +{%- endfor -%} diff --git a/src/_layouts/page.html b/src/_layouts/page.html new file mode 100644 index 0000000..9aebe9f --- /dev/null +++ b/src/_layouts/page.html @@ -0,0 +1,43 @@ +--- +layout: compress +--- + + + + +{% include header.html %} + + + + + + {% include navbar.html %} +
+
+

{{ page.title | escape }}.

+
+
+ {% include anchor_headings.html html=content anchorClass="anchor-head" beforeHeading=true h_min=4 h_max=4 %} +
+ {% include footer.html %} +
+ + + diff --git a/src/_layouts/post.html b/src/_layouts/post.html new file mode 100644 index 0000000..e15f093 --- /dev/null +++ b/src/_layouts/post.html @@ -0,0 +1,94 @@ +--- +layout: compress +--- + + + + +{% include header.html %} + + + + + + {% include navbar.html %} +
+
+
+ +
+ {% if page.tags and page.tags != empty %} +
+ {% assign tags = page.tags %} + + {% for tag in tags %} + {{tag | upcase }}{% unless forloop.last %},{% endunless %} + {% endfor %} + +
+ {% endif %} +

{{ page.title | escape }}

+ {% if page.date %} + + {% endif %} +
+ +
+ {% include anchor_headings.html html=content anchorClass="anchor-head" beforeHeading=true h_min=1 h_max=4 %} + {% if page.tweet %} +

Comments this article on + Twitter. +

+ {% endif %} +
+
+ + {% if page.comments %} + {% include comments.html %} + {% endif %} + +
+ + {% if page.modified %} + updated_at {{page.modified | date: "%d-%m-%Y"}} + {% endif %} + {% if page.next or page.previous %} + {% include navigation.html %} + {% endif %} + + {% include footer.html %} +
+ + + diff --git a/src/_posts/ansible-edit-kernel-params/2023-06-19-ansible-edit-grub.md b/src/_posts/ansible-edit-kernel-params/2023-06-19-ansible-edit-grub.md new file mode 100644 index 0000000..3320889 --- /dev/null +++ b/src/_posts/ansible-edit-kernel-params/2023-06-19-ansible-edit-grub.md @@ -0,0 +1,87 @@ +--- +layout: post +title: Using Ansible to alter Kernel Parameters +date: 2023-06-19 09:31:00 Europe/Amsterdam +categories: ansible grub linux +--- + +For months, I've had a peculiar problem with my laptop: once in a while, seemingly without reason, my laptop screen would freeze. +This only happened on my laptop screen, and not on an external monitor. +I had kind of learned to live with it as I couldn't find a solution online. +The only remedy I had was reloading my window manager, which would often unfreeze the screen. + +Yesterday I tried Googling once more and I actually found [a thread](https://bbs.archlinux.org/viewtopic.php?id=246841) about it on the Arch Linux forums! +They talk about the same laptop model, the Lenovo ThinkPad x260, having the problem. +Fortunately, they also propose [a temporary fix](https://bbs.archlinux.org/viewtopic.php?pid=1888932#p1888932). + +# Trying the Fix + +Apparently, a problem with the Panel Self Refresh (PSR) feature of Intel iGPUs is the culprit. +According to the [Linux source code](https://github.com/torvalds/linux/blob/45a3e24f65e90a047bef86f927ebdc4c710edaa1/drivers/gpu/drm/i915/display/intel_psr.c#L42), PSR enables the display to go into a lower standby mode when the sytem is idle but the screen is in use. +These lower standby modes can reduce power usage of your device when idling. + +This all seems useful, except when it makes your screen freeze! +The proposed fix disables the PSR feature entirely. +To do this, we need to change a parameter to the Intel Graphics Linux Kernel Module (LKM). +The LKM for Intel Graphics is called `i915`. +There are [multiple ways](https://wiki.archlinux.org/title/Kernel_parameters) to change kernel parameters, but I chose to edit my Grub configuration. + +First, I wanted to test whether it actually works. +When booting into my Linux partition via Grub, you can press `e` to edit the Grub definition. +Somewhere there, you can find the `linux` command which specifies to boot Linux and how to do that. +I simply appended the option `i915.enable_psr=0` to this line. +After rebooting, I noticed my screen no longer freezes! +Success! + +# Persisting the Fix + +To make the change permanent, we need to permanently change Grub's configuration. +One way to do this, is by changing Grub's defaults in `/etc/default/grub`. +Namely, the `GRUB_CMDLINE_LINUX_DEFAULT` option specifies what options Grub should pass to the Linux kernel by default. +For me, this is a nice solution as the problem exists for both Linux OSes I have installed. +I changed this option to: +```ini +GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i915.enable_psr=0" +``` + +Next, I wanted to automate this solution using Ansible. +This turned out to be quite easy, as the Grub configuration looks a bit like an ini file (maybe it is?): +```yaml +- name: Edit grub to disable Panel Self Refresh + become: true + ini_file: + path: /etc/default/grub + section: null + option: "GRUB_CMDLINE_LINUX_DEFAULT" + value: '"quiet splash i915.enable_psr=0"' + no_extra_spaces: true + notify: update grub +``` + +Lastly, I created the `notify` hook to update the Grub configuration: +```yaml +- name: update grub + become: true + command: + cmd: update-grub +``` + +# Update: Just use Nix + +Lately, I have been learning a bit of NixOS with the intention of replacing my current setup. +Compared to Ansible, applying this fix is a breeze on NixOS: +```nix +{ + boot.kernelParams = [ "i915.enable_psr=0" ]; +} +``` + +That's it, yep. + +# Conclusion + +It turned out to be quite easy to change Linux kernel parameters using Ansible. +Maybe some kernel gurus have better ways to change parameters, but this works for me for now. + +As a sidenote, I started reading a bit more about NixOS and realised that it can solve issues like these much more nicely than Ansible does. +I might replace my OS with NixOS some day, if I manage to rewrite my Ansible for it. diff --git a/src/_posts/backup-failure/2023-08-08-backup-failure.md b/src/_posts/backup-failure/2023-08-08-backup-failure.md new file mode 100644 index 0000000..9a03b52 --- /dev/null +++ b/src/_posts/backup-failure/2023-08-08-backup-failure.md @@ -0,0 +1,60 @@ +--- +layout: post +title: Error Handling in Borgmatic +date: 2023-08-08 11:51:00 Europe/Amsterdam +categories: backup borg borgmatic +--- + +[BorgBackup](https://borgbackup.readthedocs.io/en/stable/) and [Borgmatic](https://torsion.org/borgmatic/) have been my go-to tools to create backups for my home lab since I started creating backups. +Using [Systemd Timers](https://wiki.archlinux.org/title/systemd/Timers), I regularly create a backup every night. +I also monitor successful execution of the backup process, in case some error occurs. +However, the way I set this up resulted in not receiving notifications. +Even though it boils down to RTFM, I'd like to explain my error and how to handle errors correctly. + +I was using the `on_error` option to handle errors, like so: + +```yaml +on_error: + - 'apprise --body="Error while performing backup" || true' +``` + +However, `on_error` does not handle errors from the execution of `before_everything` and `after_everything` hooks. +My solution to this was moving the error handling up to the Systemd service that calls Borgmatic. +This results in the following Systemd service: + +```systemd +[Unit] +Description=Backup data using Borgmatic +# Added +OnFailure=backup-failure.service + +[Service] +ExecStart=/usr/bin/borgmatic --config /root/backup.yml +Type=oneshot +``` + +This handles any error, be it from Borgmatic's hooks or itself. +The `backup-failure` service is very simple, and just calls Apprise to send a notification: + +```systemd +[Unit] +Description=Send backup failure notification + +[Service] +Type=oneshot +ExecStart=apprise --body="Failed to create backup!" + +[Install] +WantedBy=multi-user.target +``` + +# The Aftermath (or what I learned) + +Because the error handling and alerting weren't working propertly, my backups didn't succeed for two weeks straight. +And, of course, you only notice your backups aren't working when you actually need them. +This is exactly what happened: my disk was full and a MariaDB database crashed as a result of that. +Actually, the whole database seemed to be corrupt and I find it worrying MariaDB does not seem to be very resilient to failures (in comparison a PostgreSQL database was able to recover automatically). +I then tried to recover the data using last night's backup, only to find out there was no such backup. +Fortunately, I had other means to recover the data so I incurred no data loss. + +I already knew it is important to test backups, but I learned it is also important to test failures during backups! diff --git a/src/_posts/concourse-apprise-notifier/2023-06-14-concourse-apprise-notifier.md b/src/_posts/concourse-apprise-notifier/2023-06-14-concourse-apprise-notifier.md new file mode 100644 index 0000000..a207578 --- /dev/null +++ b/src/_posts/concourse-apprise-notifier/2023-06-14-concourse-apprise-notifier.md @@ -0,0 +1,194 @@ +--- +layout: post +title: Sending Apprise Notifications from Concourse CI +date: 2023-06-14 23:39:00 Europe/Amsterdam +categories: concourse apprise +--- + +Recently, I deployed [Concourse CI](https://concourse-ci.org/) because I wanted to get my feet wet with a CI/CD pipeline. +However, I had a practical use case lying around for a long time: automatically compiling my static website and deploying it to my docker Swarm. +This took some time getting right, but the result works like a charm ([source code](https://git.kun.is/pim/static)). + +It's comforting to know I don't have move a finger and my website is automatically deployed. +However, I would still like to receive some indication of what's happening. +And what's a better way to do that, than using my [Apprise](https://github.com/caronc/apprise) service to keep me up to date. +There's a little snag though: I could not find any Concourse resource that does this. +That's when I decided to just create it myself. + +# The Plagiarism Hunt + +As any good computer person, I am lazy. +I'd rather just copy someone's work, so that's what I did. +I found [this](https://github.com/mockersf/concourse-slack-notifier) GitHub repository that does the same thing but for Slack notifications. +For some reason it's archived, but it seemed like it should work. +I actually noticed lots of repositories for Concourse resource types are archived, so not sure what's going on there. + +# Getting to know Concourse + +Let's first understand what we need to do reach our end goal of sending Apprise notifications from Concourse. + +A Concourse pipeline takes some inputs, performs some operations on them which result in some outputs. +These inputs and outputs are called _resources_ in Concourse. +For example, a Git repository could be a resource. +Each resource is an instance of a _resource type_. +A resource type therefore is simply a blueprint that can create multiple resources. +To continue the example, a resource type could be "Git repository". + +We therefore need to create our own resource type that can send Apprise notifications. +A resource type is simply a container that includes three scripts: +- `check`: check for a new version of a resource +- `in`: retrieve a version of the resource +- `out`: create a version of the resource + +As Apprise notifications are basically fire-and-forget, we will only implement the `out` script. + +# Writing the `out` script + +The whole script can be found [here](https://git.kun.is/pim/concourse-apprise-notifier/src/branch/master/out), but I will explain the most important bits of it. +Note that I only use Apprise's persistent storage solution, and not its stateless solution. + +Concourse provides us with the working directory, which we `cd` to: +```bash +cd "${1}" +``` + +We create a timestamp, formatted in JSON, which we will use for the resource's new version later. +Concourse requires us to set a version for the resource, but since Apprise notifications don't have that, we use the timestamp: +```bash +timestamp="$(jq -n "{version:{timestamp:\"$(date +%s)\"}}")" +``` + +First some black magic Bash to redirect file descriptors. +Not sure why this is needed, but I copied it anyways. +After that, we create a temporary file holding resource's parameters. +```bash +exec 3>&1 +exec 1>&2 + +payload=$(mktemp /tmp/resource-in.XXXXXX) +cat > "${payload}" <&0 +``` + +We then extract the individual parameters. +The `source` key contains values how the resource type was specified, while the `params` key specifies parameters for this specific resource. +```bash +apprise_host="$(jq -r '.source.host' < "${payload}")" +apprise_key="$(jq -r '.source.key' < "${payload}")" + +alert_body="$(jq -r '.params.body' < "${payload}")" +alert_title="$(jq -r '.params.title // null' < "${payload}")" +alert_type="$(jq -r '.params.type // null' < "${payload}")" +alert_tag="$(jq -r '.params.tag // null' < "${payload}")" +alert_format="$(jq -r '.params.format // null' < "${payload}")" +``` + +We then format the different parameters using JSON: +```bash +alert_body="$(eval "printf \"${alert_body}\"" | jq -R -s .)" +[ "${alert_title}" != "null" ] && alert_title="$(eval "printf \"${alert_title}\"" | jq -R -s .)" +[ "${alert_type}" != "null" ] && alert_type="$(eval "printf \"${alert_type}\"" | jq -R -s .)" +[ "${alert_tag}" != "null" ] && alert_tag="$(eval "printf \"${alert_tag}\"" | jq -R -s .)" +[ "${alert_format}" != "null" ] && alert_format="$(eval "printf \"${alert_format}\"" | jq -R -s .)" +``` + +Next, from the individual parameters we construct the final JSON message body we send to the Apprise endpoint. +```bash +body="$(cat < /tmp/compact_body.json +``` + +Here is the most important line, where we send the payload to the Apprise endpoint. +It's quite straight-forward. +```bash +curl -v -X POST -T /tmp/compact_body.json -H "Content-Type: application/json" "${apprise_host}/notify/${apprise_key}" +``` + +Finally, we print the timestamp (fake version) in order to appease the Concourse gods. +```bash +echo "${timestamp}" >&3 +``` + +# Building the Container + +As said earlier, to actually use this script, we need to add it to a image. +I won't be explaining this whole process, but the source can be found [here](https://git.kun.is/pim/concourse-apprise-notifier/src/branch/master/pipeline.yml). +The most important take-aways are these: +- Use `concourse/oci-build-task` to build a image from a Dockerfile. +- Use `registry-image` to push the image to an image registry. + +# Using the Resource Type + +Using our newly created resource type is surprisingly simple. +I use it for the blog you are reading right now and the pipeline definition can be found [here](https://git.kun.is/pim/static/src/branch/main/pipeline.yml). +Here we specify the resource type in a Concourse pipeline: +```yaml +resource_types: +- name: apprise + type: registry-image + source: + repository: git.kun.is/pim/concourse-apprise-notifier + tag: "1.1.1" +``` + +We simply have to tell Concourse where to find the image, and which tag we want. +Next, we instantiate the resource type to create a resource: +```yaml +resources: +- name: apprise-notification + type: apprise + source: + host: https://apprise.kun.is:444 + key: concourse + icon: bell +``` + +We simply specify the host to send Apprise notifications to. +Yeah, I even gave it a little bell because it's cute. + +All that's left to do, is actually send the notification. +Let's see how that is done: +```yaml +- name: deploy-static-website + plan: + - task: deploy-site + config: ... + + on_success: + put: apprise-notification + params: + title: "Static website deployed!" + body: "New version: $(cat version/version)" + no_get: true +``` + +As can be seen, the Apprise notification can be triggered when a task is executed successfully. +We do this using the `put` command, which execute the `out` script underwater. +We set the notification's title and body, and send it! +The result is seen below in my Ntfy app, which Apprise forwards the message to: +![picture showing my Ntfy app with the Apprise notification](ntfy.png) + +And to finish this off, here is what it looks like in the Concourse web UI: +![the concourse web gui showing the pipeline of my static website including the the apprise notification resources](pipeline.png) + +# Conclusion + +Concourse's way of representing everything as an image/container is really interesting in my opinion. +A resource type is quite easily implemented as well, although Bash might not be the optimal way to do this. +I've seen some people implement it in Rust, which might be a good excuse to finally learn that language :) + +Apart from Apprise notifications, I'm planning on creating a resource type to deploy to a Docker swarm eventually. +This seems like a lot harder than simply sending notifications though. diff --git a/src/_posts/concourse-apprise-notifier/ntfy.png b/src/_posts/concourse-apprise-notifier/ntfy.png new file mode 100644 index 0000000..3b47f51 Binary files /dev/null and b/src/_posts/concourse-apprise-notifier/ntfy.png differ diff --git a/src/_posts/concourse-apprise-notifier/pipeline.png b/src/_posts/concourse-apprise-notifier/pipeline.png new file mode 100644 index 0000000..68d0d14 Binary files /dev/null and b/src/_posts/concourse-apprise-notifier/pipeline.png differ diff --git a/src/_posts/fluent-bit-memory/2023-08-09-fluent-bit-memory.md b/src/_posts/fluent-bit-memory/2023-08-09-fluent-bit-memory.md new file mode 100644 index 0000000..b20ba90 --- /dev/null +++ b/src/_posts/fluent-bit-memory/2023-08-09-fluent-bit-memory.md @@ -0,0 +1,74 @@ +--- +layout: post +title: Monitoring Correct Memory Usage in Fluent Bit +date: 2023-08-09 16:19:00 Europe/Amsterdam +categories: fluentd fluentbit memory +--- + +Previously, I have used [Prometheus' node_exporter](https://github.com/prometheus/node_exporter) to monitor the memory usage of my servers. +However, I am currently in the process of moving away from Prometheus to a new Monioring stack. +While I understand the advantages, I felt like Prometheus' pull architecture does not scale nicely. +Everytime I spin up a new machine, I would have to centrally change Prometheus' configuration in order for it to query the new server. + +In order to collect metrics from my servers, I am now using [Fluent Bit](https://fluentbit.io/). +I love Fluent Bit's way of configuration which I can easily express as code and automate, its focus on effiency and being vendor agnostic. +However, I have stumbled upon one, in my opinion, big issue with Fluent Bit: its `mem` plugin to monitor memory usage is _completely_ useless. +In this post I will go over the problem and my temporary solution. + +# The Problem with Fluent Bit's `mem` Plugin + +As can be seen in [the documentation](https://docs.fluentbit.io/manual/pipeline/inputs/memory-metrics), Fluent Bit's `mem` input plugin exposes a few metrics regarding memory usage which should be self-explaining: `Mem.total`, `Mem.used`, `Mem.free`, `Swap.total`, `Swap.used` and `Swap.free`. +The problem is that `Mem.used` and `Mem.free` do not accurately reflect the machine's actual memory usage. +This is because these metrics include caches and buffers, which can be reclaimed by other processes if needed. +Most tools reporting memory usage therefore include an additional metric that specifices the memory _available_ on the system. +For example, the command `free -m` reports the following data on my laptop: +```text + total used free shared buff/cache available +Mem: 15864 3728 7334 518 5647 12136 +Swap: 2383 663 1720 +``` + +Notice that the `available` memory is more than `free` memory. + +While the issue is known (see [this](https://github.com/fluent/fluent-bit/pull/3092) and [this](https://github.com/fluent/fluent-bit/pull/5237) link), it is unfortunately not yet fixed. + +# A Temporary Solution + +The issues I linked previously provide stand-alone plugins that fix the problem, which will hopefully be merged in the official project at some point. +However, I didn't want to install another plugin so I used Fluent Bit's `exec` input plugin and the `free` Linux command to query memory usage like so: +```conf +[INPUT] + Name exec + Tag memory + Command free -m | tail -2 | tr '\n' ' ' + Interval_Sec 1 +``` + +To interpret the command's output, I created the following filter: +```conf +[FILTER] + Name parser + Match memory + Key_Name exec + Parser free +``` + +Lastly, I created the following parser (warning: regex shitcode incoming): +```conf +[PARSER] + Name free + Format regex + Regex ^Mem:\s+(?\d+)\s+(?\d+)\s+(?\d+)\s+(?\d+)\s+(?\d+)\s+(?\d+) Swap:\s+(?\d+)\s+(?\d+)\s+(?\d+) + Types mem_total:integer mem_used:integer mem_free:integer mem_shared:integer mem_buff_cache:integer mem_available:integer swap_total:integer swap_used:integer +``` + +With this configuration, you can use the `mem_available` metric to get accurate memory usage in Fluent Bit. + +# Conclusion + +Let's hope Fluent Bit's `mem` input plugin is improved upon soon so this hacky solution is not needed. +I also intend to document my new monitoring pipeline, which at the moment consists of: +- Fluent Bit +- Fluentd +- Elasticsearch +- Grafana diff --git a/src/_posts/hashicorp-license-change/2023-08-17-hashicorp-license-change.md b/src/_posts/hashicorp-license-change/2023-08-17-hashicorp-license-change.md new file mode 100644 index 0000000..e9e3024 --- /dev/null +++ b/src/_posts/hashicorp-license-change/2023-08-17-hashicorp-license-change.md @@ -0,0 +1,45 @@ +--- +layout: post +title: Hashicorp's License Change and my Home Lab - Update +date: 2023-08-17 18:15:00 Europe/Amsterdam +categories: hashicorp terraform vault nomad +--- + +_See the [Update](#update) at the end of the article._ + +Already a week ago, Hashicorp [announced](https://www.hashicorp.com/blog/hashicorp-adopts-business-source-license) it would change the license on almost all its projects. +Unlike [their previous license](https://github.com/hashicorp/terraform/commit/ab411a1952f5b28e6c4bd73071194761da36a83f), which was the Mozilla Public License 2.0, their new license is no longer truly open source. +It is called the Business Source License™ and restricts use of their software for competitors. +In their own words: +> Vendors who provide competitive services built on our community products will no longer be able to incorporate future releases, bug fixes, or security patches contributed to our products. + +I found [a great article](https://meshedinsights.com/2021/02/02/rights-ratchet/) by MeshedInsights that names this behaviour the "rights ratchet model". +They define a script start-ups use to garner the interest of open source enthusiasts but eventually turn their back on them for profit. +The reason why Hashicorp can do this, is because contributors signed a copyright license agreement (CLA). +This agreement transfers the copyright of contributors' code to Hashicorp, allowing them to change the license if they want to. + +I find this action really regrettable because I like their products. +This sort of action was also why I wanted to avoid using an Elastic stack, which also had their [license changed](https://www.elastic.co/pricing/faq/licensing).[^elastic] +These companies do not respect their contributors and the software stack beneath they built their product on, which is actually open source (Golang, Linux, etc.). + +# Impact on my Home Lab + +I am using Terraform in my home lab to manage several important things: +- Libvirt virtual machines +- PowerDNS records +- Elasticsearch configuration + +With Hashicorp's anti open source move, I intend to move away from Terraform in the future. +While I will not use Hashicorp's products for new personal projects, I will leave my current setup as-is for some time because there is no real need to quickly migrate. + +I might also investigate some of Terraform's competitors, like Pulumi. +Hopefully there is a project that respects open source which I can use in the future. + +# Update + +A promising fork of Terraform has been announced called [OpenTF](https://opentf.org/announcement). +They intend to take part of the Cloud Native Computing Foundation, which I think is a good effort because Terraform is so important for modern cloud infrastructures. + +# Footnotes + +[^elastic]: While I am still using Elasticsearch, I don't use the rest of the Elastic stack in order to prevent a vendor lock-in. diff --git a/src/_posts/homebrew-ssh-ca/2023-05-23-homebrew-ssh-ca.md b/src/_posts/homebrew-ssh-ca/2023-05-23-homebrew-ssh-ca.md new file mode 100644 index 0000000..92a1092 --- /dev/null +++ b/src/_posts/homebrew-ssh-ca/2023-05-23-homebrew-ssh-ca.md @@ -0,0 +1,184 @@ +--- +layout: post +title: Homebrew SSH Certificate Authority for the Terraform Libvirt Provider +date: 2023-05-23 11:14:00 Europe/Amsterdam +categories: ssh terraform ansible +--- + +Ever SSH'ed into a freshly installed server and gotten the following annoying message? +```text +The authenticity of host 'host.tld (1.2.3.4)' can't be established. +ED25519 key fingerprint is SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8. +Are you sure you want to continue connecting (yes/no)? +``` + +Or even more annoying: +```text +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! +Someone could be eavesdropping on you right now (man-in-the-middle attack)! +It is also possible that a host key has just been changed. +The fingerprint for the ED25519 key sent by the remote host is +SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8. +Please contact your system administrator. +Add correct host key in /home/user/.ssh/known_hosts to get rid of this message. +Offending ED25519 key in /home/user/.ssh/known_hosts:3 + remove with: + ssh-keygen -f "/etc/ssh/ssh_known_hosts" -R "1.2.3.4" +ED25519 host key for 1.2.3.4 has changed and you have requested strict checking. +Host key verification failed. +``` + +Could it be that the programmers at OpenSSH simply like to annoy us with these confusing messages? +Maybe, but these warnings also serve as a way to notify users of a potential Man-in-the-Middle (MITM) attack. +I won't go into the details of this problem, but I refer you to [this excellent blog post](https://blog.g3rt.nl/ssh-host-key-validation-strict-yet-user-friendly.html). +Instead, I would like to talk about ways to solve these annoying warnings. + +One obvious solution is simply to add each host to your `known_hosts` file. +This works okay when managing a handful of servers, but becomes unbearable when managing many servers. +In my case, I wanted to quickly spin up virtual machines using Duncan Mac-Vicar's [Terraform Libvirt provider](https://registry.terraform.io/providers/dmacvicar/libvirt/latest/docs), without having to accept their host key before connecting. +The solution? Issuing SSH host certificates using an SSH certificate authority. + +## SSH Certificate Authorities vs. the Web + +The idea of an SSH certificate authority (CA) is quite easy to grasp, if you understand the web's Public Key Infrastructure (PKI). +Just like with the web, a trusted party can issue certificates that are offered when establishing a connection. +The idea is, just by trusting the trusted party, you trust every certificate they issue. +In the case of the web's PKI, this trusted party is bundled and trusted by [your browser](https://wiki.mozilla.org/CA) or operating system. +However, in the case of SSH, the trusted party is you! (Okay you can also trust your own web certificate authority) +With this great power, comes great responsibility which we will abuse heavily in this article. + +## SSH Certificate Authority for Terraform + +So, let's start with a plan. +I want to spawn virtual machines with Terraform which which are automatically provisioned with a SSH host certificate issued by my CA. +This CA will be another host on my private network, issuing certificates over SSH. + +### Fetching the SSH Host Certificate + +First we generate an SSH key pair in Terraform. +Below is the code for that: +```terraform +resource "tls_private_key" "debian" { + algorithm = "ED25519" +} + +data "tls_public_key" "debian" { + private_key_pem = tls_private_key.debian.private_key_pem +} +``` + +Now that we have an SSH key pair, we need to somehow make Terraform communicate this with the CA. +Lucky for us, there is a way for Terraform to execute an arbitrary command with the `external` data feature. +We call this script below: +```terraform +data "external" "cert" { + program = ["bash", "${path.module}/get_cert.sh"] + + query = { + pubkey = trimspace(data.tls_public_key.debian.public_key_openssh) + host = var.name + cahost = var.ca_host + cascript = var.ca_script + cakey = var.ca_key + } +} +``` + +These query parameters will end up in the script's stdin in JSON format. +We can then read these parameters, and send them to the CA over SSH. +The result must as well be in JSON format. +```bash +#!/bin/bash +set -euo pipefail +IFS=$'\n\t' + +# Read the query parameters +eval "$(jq -r '@sh "PUBKEY=\(.pubkey) HOST=\(.host) CAHOST=\(.cahost) CASCRIPT=\(.cascript) CAKEY=\(.cakey)"')" + +# Fetch certificate from the CA +# Warning: extremely ugly code that I am to lazy to fix +CERT=$(ssh -o ConnectTimeout=3 -o ConnectionAttempts=1 root@$CAHOST '"'"$CASCRIPT"'" host "'"$CAKEY"'" "'"$PUBKEY"'" "'"$HOST"'".dmz') + +jq -n --arg cert "$CERT" '{"cert":$cert}' +``` + +We see that a script is called on the remote host that issues the certificate. +This is just a simple wrapper around `ssh-keygen`, which you can see below. +```bash +#!/bin/bash +set -euo pipefail +IFS=$'\n\t' + +host() { + CAKEY="$2" + PUBKEY="$3" + HOST="$4" + + echo "$PUBKEY" > /root/ca/"$HOST".pub + ssh-keygen -h -s /root/ca/keys/"$CAKEY" -I "$HOST" -n "$HOST" /root/ca/"$HOST".pub + cat /root/ca/"$HOST"-cert.pub + rm /root/ca/"$HOST"*.pub +} + +"$1" "$@" +``` + +### Appeasing the Terraform Gods + +So nice, we can fetch the SSH host certificate from the CA. +We should just be able to use it right? +We can, but it brings a big annoyance with it: Terraform will fetch a new certificate every time it is run. +This is because the `external` feature of Terraform is a data source. +If we were to use this data source for a Terraform resource, it would need to be updated every time we run Terraform. +I have not been able to find a way to avoid fetching the certificate every time, except for writing my own resource provider which I'd rather not. +I have, however, found a way to hack around the issue. + +The idea is as follows: we can use Terraform's `ignore_changes` to, well, ignore any changes of a resource. +Unfortunately, we cannot use this for a `data` source, so we must create a glue `null_resource` that supports `ignore_changes`. +This is shown in the code snipppet below. +We use the `triggers` property simply to copy the certificate in; we don't use it for it's original purpose. + +```terraform +resource "null_resource" "cert" { + triggers = { + cert = data.external.cert.result["cert"] + } + + lifecycle { + ignore_changes = [ + triggers + ] + } +} +``` + +And voilà, we can now use `null_resource.cert.triggers["cert"]` as our certificate, that won't trigger replacements in Terraform. + +### Setting the Host Certificate with Cloud-Init + +Terraform's Libvirt provider has native support for Cloud-Init, which is very handy. +We can give the host certificate directly to Cloud-Init and place it on the virtual machine. +Inside the Cloud-Init configuration, we can set the `ssh_keys` property to do this: +```yml +ssh_keys: + ed25519_private: | + ${indent(4, private_key)} + ed25519_certificate: "${host_cert}" +``` + +I hardcoded this to ED25519 keys, because this is all I use. + +This works perfectly, and I never have to accept host certificates from virtual machines again. + +### Caveats + +A sharp eye might have noticed the lifecycle of these host certificates is severely lacking. +Namely, the deployed host certificates have no expiration date nore is there revocation function. +There are ways to implement these, but for my home lab I did not deem this necessary at this point. +In a more professional environment, I would suggest using [Hashicorp's Vault](https://www.vaultproject.io/). + +This project did teach me about the limits and flexibility of Terraform, so all in all a success! +All code can be found on the git repository [here](https://git.kun.is/home/tf-modules/src/branch/master/debian). diff --git a/src/_posts/infrastructure-snapshot/2023-08-13-infrastructure-snapshot.md b/src/_posts/infrastructure-snapshot/2023-08-13-infrastructure-snapshot.md new file mode 100644 index 0000000..f9abcb2 --- /dev/null +++ b/src/_posts/infrastructure-snapshot/2023-08-13-infrastructure-snapshot.md @@ -0,0 +1,283 @@ +--- +layout: post +title: Home Lab Infrastructure Snapshot August 2023 +date: 2023-08-27 22:23:00 Europe/Amsterdam +categories: infrastructure homelab +--- + +I have been meaning to write about the current state of my home lab infrastructure for a while now. +Now that the most important parts are quite stable, I think the opportunity is ripe. +I expect this post to get quite long, so I might have to leave out some details along the way. + +This post will be a starting point for future infrastructure snapshots which I can hopefully put out periodically. +That is, if there is enough worth talking about. + +Keep an eye out for the icon, which links to the source code and configuration of anything mentioned. +Oh yeah, did I mention everything I do is open source? + +# Networking and Infrastructure Overview + +## Hardware and Operating Systems + +Let's start with the basics: what kind of hardware do I use for my home lab? +The most important servers are my three [Gigabyte Brix GB-BLCE-4105](https://www.gigabyte.com/Mini-PcBarebone/GB-BLCE-4105-rev-10). +Two of them have 16 GB of memory, and one 8 GB. +I named these servers as follows: +- **Atlas**: because this server was going to "lift" a lot of virtual machines. +- **Lewis**: we started out with a "Max" server named after the Formula 1 driver Max Verstappen, but it kind of became an unmanagable behemoth without infrastructure-as-code. Our second server we subsequently named Lewis after his colleague Lewis Hamilton. Note: people around me vetoed these names and I am no F1 fan! +- **Jefke**: it's a funny Belgian name. That's all. + +Here is a picture of them sitting in their cosy closet: + +![A picture of my servers.](servers.jpeg) + +If you look look to the left, you will also see a Raspberry pi 4B. +I use this Pi to do some rudimentary monitoring whether servers and services are running. +More on this in the relevant section below. +The Pi is called **Iris** because it's a messenger for the other servers. + +I used to run Ubuntu on these systems, but I have since migrated away to Debian. +The main reasons were Canonical [putting advertisements in my terminal](https://askubuntu.com/questions/1434512/how-to-get-rid-of-ubuntu-pro-advertisement-when-updating-apt) and pushing Snap which has a [proprietry backend](https://hackaday.com/2020/06/24/whats-the-deal-with-snap-packages/). +Two of my servers run the newly released Debian Bookworm, while one still runs Debian Bullseye. + +## Networking + +For networking, I wanted hypervisors and virtual machines separated by VLANs for security reasons. +The following picture shows a simplified view of the VLANs present in my home lab: + +![Picture showing the VLANS in my home lab.](vlans.png) + +All virtual machines are connected to a virtual bridge which tags network traffic with the DMZ VLAN. +The hypervisors VLAN is used for traffic to and from the hypervisors. +Devices from the hypervisors VLAN are allowed to connect to devices in the DMZ, but not vice versa. +The hypervisors are connected to a switch using a trunk link, allows both DMZ and hypervisors traffic. + +I realised the above design using ifupdown. +Below is the configuration for each hypervisor, which creates a new `enp3s0.30` interface with all DMZ traffic from the `enp3s0` interface [](https://git.kun.is/home/hypervisors/src/commit/71b96d462116e4160b6467533fc476f3deb9c306/ansible/dmz.conf.j2). + +```text +auto enp3s0.30 +iface enp3s0.30 inet manual +iface enp3s0.30 inet6 auto + accept_ra 0 + dhcp 0 + request_prefix 0 + privext 0 + pre-up sysctl -w net/ipv6/conf/enp3s0.30/disable_ipv6=1 +``` + +This configuration seems more complex than it actually is. +Most of it is to make sure the interface is not assigned an IPv4/6 address on the hypervisor host. +The magic `.30` at the end of the interface name makes this interface tagged with VLAN ID 30 (DMZ for me). + +Now that we have an interface tagged for the DMZ VLAN, we can create a bridge where future virtual machines can connect to: + +```text +auto dmzbr +iface dmzbr inet manual + bridge_ports enp3s0.30 + bridge_stp off +iface dmzbr inet6 auto + accept_ra 0 + dhcp 0 + request_prefix 0 + privext 0 + pre-up sysctl -w net/ipv6/conf/dmzbr/disable_ipv6=1 +``` + +Just like the previous config, this is quite bloated because I don't want the interface to be assigned an IP address on the host. +Most importantly, the `bridge_ports enp3s0.30` line here makes this interface a virtual bridge for the `enp3s0.30` interface. + +And voilà, we now have a virtual bridge on each machine, where only DMZ traffic will flow. +Here I verify whether this configuration works: +
+ Show + + +We can see that the two virtual interfaces are created, and are only assigned a MAC address and not a IP address: +```text +root@atlas:~# ip a show enp3s0.30 +4: enp3s0.30@enp3s0: mtu 1500 qdisc noqueue master dmzbr state UP group default qlen 1000 + link/ether d8:5e:d3:4c:70:38 brd ff:ff:ff:ff:ff:ff +5: dmzbr: mtu 1500 qdisc noqueue state UP group default qlen 1000 + link/ether 4e:f7:1f:0f:ad:17 brd ff:ff:ff:ff:ff:ff +``` + +Pinging a VM from a hypervisor works: +```text +root@atlas:~# ping -c1 maestro.dmz +PING maestro.dmz (192.168.30.8) 56(84) bytes of data. +64 bytes from 192.168.30.8 (192.168.30.8): icmp_seq=1 ttl=63 time=0.457 ms +``` + +Pinging a hypervisor from a VM does not work: +```text +root@maestro:~# ping -c1 atlas.hyp +PING atlas.hyp (192.168.40.2) 56(84) bytes of data. + +--- atlas.hyp ping statistics --- +1 packets transmitted, 0 received, 100% packet loss, time 0ms +``` +
+ +## DNS and DHCP + +Now that we have a working DMZ network, let's build on it to get DNS and DHCP working. +This will enable new virtual machines to obtain a static or dynamic IP address and register their host in DNS. +This has actually been incredibly annoying due to our friend [Network address translation (NAT)](https://en.wikipedia.org/wiki/Network_address_translation?useskin=vector). +
+ NAT recap + +Network address translation (NAT) is a function of a router which allows multiple hosts to share a single IP address. +This is needed for IPv4, because IPv4 addresses are scarce and usually one household is only assigned a single IPv4 address. +This is one of the problems IPv6 attempts to solve (mainly by having so many IP addresses that they should never run out). +To solve the problem for IPv4, each host in a network is assigned a private IPv4 address, which can be reused for every network. + +Then, the router must perform address translation. +It does this by keeping track of ports opened by hosts in its private network. +If a packet from the internet arrives at the router for such a port, it forwards this packet to the correct host. +
+ +I would like to host my own DNS on a virtual machine (called **hermes**, more on VMs later) in the DMZ network. +This basically gives two problems: + +1. The upstream DNS server will refer to the public internet-accessible IP address of our DNS server. +This IP-address has no meaning inside the private network due to NAT and the router will reject the packet. +2. Our DNS resolves hosts to their public internet-accessible IP address. +This is similar to the previous problem as the public IP address has no meaning. + +The first problem can be remediated by overriding the location of the DNS server for hosts inside the DMZ network. +This can be achieved on my router, which uses Unbound as its recursive DNS server: + +![Unbound overides for kun.is and dmz domains.](unbound_overrides.png) + +Any DNS requests to Unbound to domains in either `dmz` or `kun.is` will now be forwarded `192.168.30.7` (port 5353). +This is the virtual machine hosting my DNS. + +The second problem can be solved at the DNS server. +We need to do some magic overriding, which [dnsmasq](https://dnsmasq.org/docs/dnsmasq-man.html) is perfect for [](https://git.kun.is/home/hermes/src/commit/488024a7725f2325b8992e7a386b4630023f1b52/ansible/roles/dnsmasq/files/dnsmasq.conf): + +```conf +alias=84.245.14.149,192.168.30.8 +server=/kun.is/192.168.30.7 +``` + +This always overrides the public IPv4 address to the private one. +It also overrides the DNS server for `kun.is` to `192.168.30.7`. + +Finally, behind the dnsmasq server, I run [Powerdns](https://www.powerdns.com/) as authoritative DNS server [](https://git.kun.is/home/hermes/src/branch/master/ansible/roles/powerdns). +I like this DNS server because I can manage it with Terraform [](https://git.kun.is/home/hermes/src/commit/488024a7725f2325b8992e7a386b4630023f1b52/terraform/dns/kun_is.tf). + +Here is a small diagram showing my setup (my networking teacher would probably kill me for this): +![Shitty diagram showing my DNS setup.](nat.png) + +# Virtualization +https://github.com/containrrr/shepherd +Now that we have laid out the basic networking, let's talk virtualization. +Each of my servers are configured to run KVM virtual machines, orchestrated using Libvirt. +Configuration of the physical hypervisor servers, including KVM/Libvirt is done using Ansible. +The VMs are spun up using Terraform and the [dmacvicar/libvirt](https://registry.terraform.io/providers/dmacvicar/libvirt/latest/docs) Terraform provider. + +This all isn't too exciting, except that I created a Terraform module that abstracts the Terraform Libvirt provider for my specific scenario [](https://git.kun.is/home/tf-modules/src/commit/e77d62f4a2a0c3847ffef4434c50a0f40f1fa794/debian/main.tf): +```terraform +module "maestro" { + source = "git::https://git.kun.is/home/tf-modules.git//debian" + name = "maestro" + domain_name = "tf-maestro" + memory = 10240 + mac = "CA:FE:C0:FF:EE:08" +} +``` + +This automatically creates a Debian virtual machines with the properties specified. +It also sets up certificate-based SSH authentication which I talked about [before]({% post_url homebrew-ssh-ca/2023-05-23-homebrew-ssh-ca %}). + +# Clustering + +With virtualization explained, let's move up one level further. +Each of my three physical servers hosts a virtual machine running Docker, which together form a Docker Swarm. +I use Traefik as a reverse proxy which routes requests to the correct container. + +All data is hosted on a single machine and made available to containers using NFS. +This might not be very secure (as NFS is not encrypted and no proper authentication), it is quite fast. + +As of today, I host the following services on my Docker Swarm [](https://git.kun.is/home/shoarma): +- [Forgejo](https://forgejo.org/) as Git server +- [FreshRSS](https://www.freshrss.org/) as RSS aggregator +- [Hedgedoc](https://hedgedoc.org/) as Markdown note-taking +- [Inbucket](https://hedgedoc.org/) for disposable email +- [Cyberchef](https://cyberchef.org/) for the lulz +- [Kitchenowl](https://kitchenowl.org/) for grocery lists +- [Mastodon](https://joinmastodon.org/) for microblogging +- A monitoring stack (read more below) +- [Nextcloud](https://nextcloud.com/) for cloud storage +- [Pihole](https://pi-hole.net/) to block advertisements +- [Radicale](https://radicale.org/v3.html) for calendar and contacts sync +- [Seafile](https://www.seafile.com/en/home/) for cloud storage and sync +- [Shephard](https://github.com/containrrr/shepherd) for automatic container updates +- [Nginx](https://nginx.org/en/) hosting static content (like this page!) +- [Docker Swarm dashboard](https://hub.docker.com/r/charypar/swarm-dashboard/#!) +- [Syncthing](https://syncthing.net/) for file sync + +# CI / CD + +For CI / CD, I run [Concourse CI](https://concourse-ci.org/) in a separate VM. +This is needed, because Concourse heavily uses containers to create reproducible builds. + +Although I should probably use it for more, I currently use my Concourse for three pipelines: + +- A pipeline to build this static website and create a container image of it. +The image is then uploaded to the image registry of my Forgejo instance. +I love it when I can use stuff I previously built :) +The pipeline finally deploys this new image to the Docker Swarm [](https://git.kun.is/pim/static/src/commit/eee4f0c70af6f2a49fabb730df761baa6475db22/pipeline.yml). +- A pipeline to create a Concourse resource that sends Apprise alerts (Concourse-ception?) [](https://git.kun.is/pim/concourse-apprise-notifier/src/commit/b5d4413c1cd432bc856c45ec497a358aca1b8b21/pipeline.yml) +- A pipeline to build a custom Fluentd image with plugins installed [](https://git.kun.is/pim/fluentd) + +# Backups + +To create backups, I use [Borg](https://www.borgbackup.org/). +As I keep all data on one machine, this backup process is quite simple. +In fact, all this data is stored in a single Libvirt volume. +To configure Borg with a simple declarative script, I use [Borgmatic](https://torsion.org/borgmatic/). + +In order to back up the data inside the Libvirt volume, I create a snapshot to a file. +Then I can mount this snapshot in my file system. +The files can then be backed up while the system is still running. +It is also possible to simply back up the Libvirt image, but this takes more time and storage [](https://git.kun.is/home/hypervisors/src/commit/71b96d462116e4160b6467533fc476f3deb9c306/ansible/roles/borg/backup.yml.j2). + +# Monitoring and Alerting + +The last topic I would like to talk about is monitoring and alerting. +This is something I'm still actively improving and only just set up properly. + +## Alerting + +For alerting, I wanted something that runs entirely on my own infrastructure. +I settled for Apprise + Ntfy. + +[Apprise](https://github.com/caronc/apprise) is a server that is able to send notifications to dozens of services. +For application developers, it is thus only necessary to implement the Apprise API to gain access to all these services. +The Apprise API itself is also very simple. +By using Apprise, I can also easily switch to another notification service later. +[Ntfy](https://ntfy.sh/) is free software made for mobile push notifications. + +I use this alerting system in quite a lot of places in my infrastructure, for example when creating backups. + +## Uptime Monitoring + +The first monitoring setup I created, was using [Uptime Kuma](https://github.com/louislam/uptime-kuma). +Uptime Kuma periodically pings a service to see whether it is still running. +You can do a literal ping, test HTTP response codes, check database connectivity and much more. +I use it to check whether my services and VMs are online. +And the best part is, Uptime Kuma supports Apprise so I get push notifications on my phone whenever something goes down! + +## Metrics and Log Monitoring + +A new monitoring system I am still in the process of deploying is focused on metrics and logs. +I plan on creating a separate blog post about this, so keep an eye out on that (for example using RSS :)). +Safe to say, it is no basic ELK stack! + +# Conclusion + +That's it for now! +Hopefully I inspired someone to build something... or how not to :) diff --git a/src/_posts/infrastructure-snapshot/nat.png b/src/_posts/infrastructure-snapshot/nat.png new file mode 100644 index 0000000..0d5f72c Binary files /dev/null and b/src/_posts/infrastructure-snapshot/nat.png differ diff --git a/src/_posts/infrastructure-snapshot/servers.jpeg b/src/_posts/infrastructure-snapshot/servers.jpeg new file mode 100644 index 0000000..b269484 Binary files /dev/null and b/src/_posts/infrastructure-snapshot/servers.jpeg differ diff --git a/src/_posts/infrastructure-snapshot/unbound_overrides.png b/src/_posts/infrastructure-snapshot/unbound_overrides.png new file mode 100644 index 0000000..f94394f Binary files /dev/null and b/src/_posts/infrastructure-snapshot/unbound_overrides.png differ diff --git a/src/_posts/infrastructure-snapshot/vlans.png b/src/_posts/infrastructure-snapshot/vlans.png new file mode 100644 index 0000000..7bf4add Binary files /dev/null and b/src/_posts/infrastructure-snapshot/vlans.png differ diff --git a/src/_posts/its-alive/2024-04-21-its-alive.md b/src/_posts/its-alive/2024-04-21-its-alive.md new file mode 100644 index 0000000..0db9c45 --- /dev/null +++ b/src/_posts/its-alive/2024-04-21-its-alive.md @@ -0,0 +1,17 @@ +--- +layout: post +title: "It's alive!" +date: 2024-04-21 10:02:00 Europe/Amsterdam +categories: jekyll blog +--- + +Finally, after several months this website is up and running again! + +My homelab has completely changed, but the reason why it initially went offline is because of my failing CI installation. +I was using [Concourse CI](https://concourse-ci.org/) which I was initially interested in due to the reproducible nature of its builds using containers. +However, for some reason pipelines were sporadically getting stuck when I reboot the virtual machine it was running on. +The fix was very annoying: I had to re-create the pipelines manually (which feels very backwards for a CI/CD system!) +Additionally, my virtual machine setup back then was also quite fragile and I decided to get rid of that as well. + +I have learned that having an escape hatch to deploy something is probably a good idea đź… +Expect a new overview of my homelab soon, in the same vein as [this post from last year]({% post_url infrastructure-snapshot/2023-08-13-infrastructure-snapshot %})! diff --git a/src/_posts/virtio-9p-experiences/2023-05-31-virtio-9p-experiences.md b/src/_posts/virtio-9p-experiences/2023-05-31-virtio-9p-experiences.md new file mode 100644 index 0000000..da8953d --- /dev/null +++ b/src/_posts/virtio-9p-experiences/2023-05-31-virtio-9p-experiences.md @@ -0,0 +1,61 @@ +--- +layout: post +title: My Experiences with virtio-9p +date: 2023-05-31 14:18:00 Europe/Amsterdam +categories: libvirt virtio 9p +--- + +When I was scaling up my home lab, I started thinking more about data management. +I hadn't (and still haven't) set up any form of network storage. +I have, however, set up a backup mechanism using [Borg](https://borgbackup.readthedocs.io/en/stable/). +Still, I want to operate lots of virtual machines, and backing up each one of them separately seemed excessive. +So I started thinking, what if I just let the host machines back up the data? +After all, the amount of physical hosts I have in my home lab is unlikely to increase drastically. + +# The Use Case for Sharing Directories + +I started working out this idea further. +Without network storage, I needed a way for guest VMs to access the host's disks. +Here there are two possibilities, either expose some block device or a file system. +Creating a whole virtual disk for just the data of some VMs seemed wasteful, and from my experiences also increases backup times dramatically. +I therefore searched for a way to mount a directory from the host OS on the guest VM. +This is when I stumbled upon [this blog](https://rabexc.org/posts/p9-setup-in-libvirt) post talking about sharing directories with virtual machines. + +# Sharing Directories with virtio-9p + +virtio-9p is a way to map a directory on the host OS to a special device on the virtual machine. +In `virt-manager`, it looks like the following: +![picture showing virt-manager configuration to map a directory to a VM](virt-manager.png) +Under the hood, virtio-9p uses the 9pnet protocol. +Originally developed at Bell Labs, support for this is available in all modern Linux kernels. +If you share a directory with a VM, you can then mount it. +Below is an extract of my `/etc/fstab` to automatically mount the directory: +```text +data /mnt/data 9p trans=virtio,rw 0 0 +``` + +The first argument (`data`) refers to the name you gave this share from the host +With the `trans` option we specify that this is a virtio share. + +# Problems with virtio-9p + +At first I had no problems with my setup, but I am now contemplating just moving to a network storage based setup because of two problems. + +The first problem is that some files have suddenly changed ownership from `libvirt-qemu` to `root`. +If the file is owned by `root`, the guest OS can still see it, but cannot access it. +I am not entirely sure the problem lies with virtio, but I suspect it is. +For anyone experiencing this problem, I wrote a small shell script to revert ownership to the `libvirt-qemu` user: +```shell +find -printf "%h/%f %u\n" | grep root | cut -d ' ' -f1 | xargs chown libvirt-qemu:libvirt-qemu +``` + +Another problem that I have experienced, is guests being unable to mount the directory at all. +I have only experienced this problem once, but it was highly annoying. +To fix it, I had to reboot the whole physical machine. + +# Alternatives + +virtio-9p seemed like a good idea, but as discussed, I had some problems with it. +It seems [virtioFS](https://virtio-fs.gitlab.io/) might be a an interesting alternative as it is designed specifically for sharing directories with VMs. + +As for me, I will probably finally look into deploying network storage either with NFS or SSHFS. diff --git a/src/_posts/virtio-9p-experiences/virt-manager.png b/src/_posts/virtio-9p-experiences/virt-manager.png new file mode 100644 index 0000000..8567efb Binary files /dev/null and b/src/_posts/virtio-9p-experiences/virt-manager.png differ diff --git a/src/_sass/klise/_base.scss b/src/_sass/klise/_base.scss new file mode 100644 index 0000000..53b4f24 --- /dev/null +++ b/src/_sass/klise/_base.scss @@ -0,0 +1,368 @@ +// Reset some basic elements +* { + -webkit-transition: background-color 75ms ease-in, border-color 75ms ease-in; + -moz-transition: background-color 75ms ease-in, border-color 75ms ease-in; + -ms-transition: background-color 75ms ease-in, border-color 75ms ease-in; + -o-transition: background-color 75ms ease-in, border-color 75ms ease-in; + transition: background-color 75ms ease-in, border-color 75ms ease-in; +} + +.notransition { + -webkit-transition: none; + -moz-transition: none; + -ms-transition: none; + -o-transition: none; + transition: none; +} + +html { + overflow-x: hidden; + width: 100%; +} + +body, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +hr, +dl, +dd, +ol, +ul, +figure { + margin: 0; + padding: 0; +} + +// Basic styling +body { + min-height: 100vh; + overflow-x: hidden; + position: relative; + color: $text-base-color; + background-color: $white; + font: $normal-weight #{$base-font-size}/#{$base-line-height} $sans-family; + -webkit-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; + -webkit-font-feature-settings: "kern" 1; + -moz-font-feature-settings: "kern" 1; + -o-font-feature-settings: "kern" 1; + font-feature-settings: "kern" 1; + font-kerning: normal; + box-sizing: border-box; +} + +// Set `margin-bottom` to maintain vertical rhythm +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +ul, +ol, +dl, +figure, +%vertical-rhythm { + margin-top: $spacing-full - 20; + margin-bottom: $spacing-full - 20; +} + +// strong | bold +strong, +b { + font-weight: $bold-weight; + color: $black; +} + +// horizontal rule +hr { + border-bottom: 0; + border-style: solid; + border-color: $light; +} + +// kbd tag +kbd { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border: 1px solid $light; + border-radius: 2px; + color: $black; + display: inline-block; + font-size: $small-font-size; + line-height: 1.4; + font-family: $mono-family; + margin: 0 0.1em; + font-weight: $bold-weight; + padding: 0.01em 0.4em; + text-shadow: 0 1px 0 $white; +} + +// Image +img { + max-width: 100%; + vertical-align: middle; + -webkit-user-drag: none; + margin: 0 auto; + text-align: center; +} + +// Figure +figure { + position: relative; +} + +// Image inside Figure tag +figure > img { + display: block; + position: relative; +} + +// Image caption +figcaption { + font-size: 13px; + text-align: center; +} + +// List +ul { + list-style: none; + li { + display: list-item; + text-align: -webkit-match-parent; + } + li::before { + content: "\FE63"; + display: inline-block; + top: -1px; + width: 1.2em; + position: relative; + margin-left: -1.3em; + font-weight: 700; + } +} + +ol { + list-style: none; + counter-reset: li; + li { + position: relative; + counter-increment: li; + &::before { + content: counter(li); + display: inline-block; + width: 1em; + margin-right: 0.5em; + margin-left: -1.6em; + text-align: right; + direction: rtl; + font-weight: $bold-weight; + font-size: $small-font-size; + } + } +} + +ul, +ol { + margin-top: 0; + margin-left: $spacing-full; +} + +li { + padding-bottom: 1px; + padding-top: 1px; + + &:before { + color: $black; + } + + > ul, + > ol { + margin-bottom: 2px; + margin-top: 0; + } +} + +// Headings +h1, +h2, +h3, +h4, +h5, +h6 { + color: $black; + font-weight: $bold-weight; + & + ul, + & + ol { + margin-top: 10px; + } + + @include media-query($on-mobile) { + scroll-margin-top: 65px; + } +} + +// Headings with link +h1 > a, +h2 > a, +h3 > a, +h4 > a, +h5 > a, +h6 > a { + text-decoration: none; + border: none; + + &:hover { + text-decoration: none; + border: none; + } +} + +// Link +a { + color: inherit; + text-decoration-color: $smoke; + + &:hover { + color: $text-link-blue; + } + + &:focus { + outline: 3px solid rgba(0, 54, 199, 0.6); + outline-offset: 2px; + } +} + +// Del +del { + color: inherit; +} + +// Em +em { + color: inherit; +} + +// Blockquotes +blockquote { + color: $gray; + font-style: italic; + text-align: center; + opacity: 0.9; + border-top: 1px solid $light; + border-bottom: 1px solid $light; + padding: 10px; + margin-left: 10px; + margin-right: 10px; + font-size: 1em; + + > :last-child { + margin-bottom: 0; + margin-top: 0; + } +} + +// Wrapper +.wrapper { + max-width: -webkit-calc(#{$narrow-size} - (#{$spacing-full} * 2)); + max-width: calc(#{$narrow-size} - (#{$spacing-full} * 2)); + position: relative; + margin-right: auto; + margin-left: auto; + padding-right: $spacing-full; + padding-left: $spacing-full; + @extend %clearfix; + + @include media-query($on-mobile) { + max-width: -webkit-calc(#{$narrow-size} - (#{$spacing-full})); + max-width: calc(#{$narrow-size} - (#{$spacing-full})); + padding-right: $spacing-full - 10; + padding-left: $spacing-full - 10; + + &.blurry { + animation: 0.2s ease-in forwards blur; + -webkit-animation: 0.2s ease-in forwards blur; + } + } +} + +// Underline +u { + text-decoration-color: #d2c7c7; +} + +// Small +small { + font-size: $small-font-size; +} + +// Superscript +sup { + border-radius: 10%; + top: -3px; + left: 2px; + font-size: small; + position: relative; + margin-right: 2px; +} + +// Table +.overflow-table { + overflow-x: auto; +} + +table { + width: 100%; + margin-top: $spacing-half; + border-collapse: collapse; + font-size: $small-font-size; + + thead { + font-weight: $bold-weight; + color: $black; + border-bottom: 1px solid $light; + } + + th, + td, + tr { + border: 1px solid $light; + padding: 2px 7px; + } +} + +// Clearfix +%clearfix:after { + content: ""; + display: table; + clear: both; +} + +// When mouse block a text set this color +mark, +::selection { + background: #fffba0; + color: $black; +} + +// Github Gist clear border +.gist { + table { + border: 0; + + tr, + td { + border: 0; + } + } +} diff --git a/src/_sass/klise/_dark.scss b/src/_sass/klise/_dark.scss new file mode 100644 index 0000000..84a7cb6 --- /dev/null +++ b/src/_sass/klise/_dark.scss @@ -0,0 +1,247 @@ +body[data-theme="dark"] { + color: $dark-text-base-color; + background-color: $dark-black; + + // Heading + h1, + h2, + h3, + h4, + h5, + h6 { + color: $dark-white; + } + + // Table + table { + thead { + color: $dark-white; + border-color: $dark-light; + } + + th, + td, + tr { + border-color: $dark-light; + } + } + + // Post + .page-content { + a { + color: $dark-text-link-blue; + + &:hover, + &:active, + &:focus { + color: $dark-text-link-blue-active; + } + } + + h3 { + border-color: $dark-light; + } + h1, + h2, + h3, + h4, + h5, + h6 { + .anchor-head { + color: $dark-text-link-blue; + } + } + } + + // Syntax + code { + &.highlighter-rouge { + background-color: $dark-light; + } + } + + // kbd tag + kbd { + border-color: $dark-light; + color: $dark-white; + text-shadow: 0 1px 0 $dark-black; + } + + // horizontal rule + hr { + border-color: $dark-light; + } + + // Post Meta + .post-meta { + color: $dark-gray; + + time { + &::after { + background-color: $dark-light; + } + } + + span[itemprop="author"] { + border-color: $dark-light; + } + } + + // Link + a { + color: inherit; + text-decoration-color: $dark-smoke; + + &:hover { + color: $dark-text-link-blue; + } + + &:focus { + outline-color: rgba(255, 82, 119, 0.6); + } + } + + // List + li { + &:before { + color: $dark-white; + } + } + + // Blockquote + blockquote { + color: $dark-gray; + border-color: $dark-light; + } + + // Strong, Bold + strong, + b { + color: $dark-white; + } + + // Navbar + .navbar { + border-color: $dark-light; + .menu { + a#mode { + .mode-sunny { + display: block; + } + .mode-moon { + display: none; + } + } + + .menu-link { + color: $dark-white; + } + @include media-query($on-mobile) { + background-color: $dark-black; + border-color: $dark-light; + + .menu-icon { + > svg { + fill: $dark-white; + } + } + + input[type="checkbox"]:checked ~ .trigger { + background: $dark-black; + } + } + } + } + + // Post Item + .post-item { + &:not(:first-child) { + border-color: $dark-light; + } + + .post-item-date { + color: $dark-white; + } + .post-item-title { + a { + color: $dark-text-base-color; + + &:hover, + &focus { + color: $dark-white; + } + } + } + } + + // Post Navigation + .post-nav { + border-color: $dark-light; + + .post-nav-item { + font-weight: $bold-weight; + + .post-title { + color: $dark-white; + opacity: 0.9; + } + + &:hover, + &:focus { + .post-title { + color: $dark-text-link-blue-active; + } + } + + .nav-arrow { + color: $dark-gray; + } + } + + @include media-query($on-mobile) { + .post-nav-item:nth-child(even) { + border-color: $dark-light; + } + } + } + + // Footer + .footer { + span.footer_item { + color: $dark-white; + } + a.footer_item:not(:last-child) { + color: $dark-white; + } + .footer_copyright { + color: $dark-gray; + opacity: 1; + } + } + + // 404 Page + .not-found { + .title { + color: $dark-white; + text-shadow: 1px 0px 0px $dark-text-link-blue; + } + .phrase { + color: $dark-text-base-color; + } + .solution { + color: $dark-text-link-blue; + } + .solution:hover { + color: $dark-text-link-blue-active; + } + } + + .search-article { + input[type="search"] { + color: $dark-text-base-color; + &::-webkit-input-placeholder { + color: rgba(128,128,128,0.8); + } + } + } +} diff --git a/src/_sass/klise/_fonts.scss b/src/_sass/klise/_fonts.scss new file mode 100644 index 0000000..e63c05c --- /dev/null +++ b/src/_sass/klise/_fonts.scss @@ -0,0 +1 @@ +@charset "utf-8"; diff --git a/src/_sass/klise/_layout.scss b/src/_sass/klise/_layout.scss new file mode 100644 index 0000000..c04dcb3 --- /dev/null +++ b/src/_sass/klise/_layout.scss @@ -0,0 +1,380 @@ +// Navbar +.navbar { + height: auto; + max-width: calc(#{$wide-size} - (#{$spacing-full} * 2)); + max-width: -webkit-calc(#{$wide-size} - (#{$spacing-full} * 2)); + position: relative; + margin-right: auto; + margin-left: auto; + border-bottom: 1px solid $light; + padding: $spacing-full - 15px $spacing-full; + @extend %clearfix; +} + +// Navigation +.menu { + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + + a#mode { + float: left; + left: 8px; + top: 6px; + position: relative; + clear: both; + -webkit-transform: scale(1, 1); + transform: scale(1, 1); + opacity: 0.7; + z-index: 1; + &:hover { + cursor: pointer; + opacity: 1; + } + &:active { + -webkit-transform: scale(0.9, 0.9); + transform: scale(0.9, 0.9); + } + .mode-moon { + display: block; + line { + stroke: $black; + fill: none; + } + + circle { + fill: $black; + stroke: $black; + } + } + .mode-sunny { + display: none; + line { + stroke: $dark-white; + fill: none; + } + circle { + fill: none; + stroke: $dark-white; + } + } + } + + .trigger { + float: right; + } + + .menu-trigger { + display: none; + } + + .menu-icon { + display: none; + } + + .menu-link { + color: $black; + line-height: $base-line-height + 0.4; + text-decoration: none; + padding: 5px 8px; + opacity: 0.7; + letter-spacing: 0.3px; + + &:hover { + opacity: 1; + } + + &:not(:last-child) { + margin-right: 5px; + } + + &.rss { + position: relative; + bottom: -3px; + outline: none; + } + + @include media-query($on-mobile) { + opacity: 0.8; + } + } + + .menu-link.active { + opacity: 1; + font-weight: 600; + } + + @include media-query($on-mobile) { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 2; + text-align: center; + height: 50px; + background-color: $white; + border-bottom: 1px solid $light; + + a#mode { + left: 10px; + top: 12px; + } + + .menu-icon { + display: block; + position: absolute; + right: 0; + width: 50px; + height: 23px; + line-height: 0; + padding-top: 13px; + padding-bottom: 15px; + cursor: pointer; + text-align: center; + z-index: 1; + > svg { + fill: $black; + opacity: 0.7; + } + &:hover { + > svg { + opacity: 1; + } + } + &:active { + -webkit-transform: scale(0.9, 0.9); + transform: scale(0.9, 0.9); + } + } + + input[type="checkbox"]:not(:checked) ~ .trigger { + clear: both; + visibility: hidden; + } + + input[type="checkbox"]:checked ~ .trigger { + position: fixed; + animation: 0.2s ease-in forwards fadein; + -webkit-animation: 0.2s ease-in forwards fadein; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + background-color: $white; + height: 100vh; + width: 100%; + top: 0; + } + + .menu-link { + display: block; + box-sizing: border-box; + font-size: 1.1em; + + &:not(:last-child) { + margin: 0; + padding: 2px 0; + } + } + } +} + +// Author +.author { + margin-top: 6.3rem; + margin-bottom: 7.2rem; + text-align: center; + + @include media-query($on-mobile) { + margin-bottom: 3em; + } + + .author-avatar { + width: 70px; + height: 70px; + border-radius: 100%; + user-select: none; + // background-color: $black; + -ms-user-select: none; + -webkit-user-select: none; + -webkit-animation: 0.5s ease-in forwards fadein; + animation: 0.5s ease-in forwards fadein; + opacity: 1; + } + + .author-name { + font-size: 1.7em; + margin-bottom: 2px; + } + + .author-bio { + margin: 0 auto; + opacity: 0.9; + max-width: 393px; + line-height: 1.688; + } +} + +// Content +.posts-item-note { + font-size: $base-font-size; + font-weight: 700; + margin-bottom: 5px; + color: $black; +} + +// List of posts +.post-item { + display: flex; + padding-top: 5px; + padding-bottom: 6px; + @extend %clearfix; + + &:not(:first-child) { + border-top: 1px solid $light; + } + + .post-item-date { + min-width: 96px; + color: $black; + font-weight: 700; + padding-right: 10px; + + @include media-query($on-mobile) { + font-size: 16px; + } + } + + .post-item-title { + margin: 0; + border: 0; + padding: 0; + font-size: $base-font-size; + font-weight: normal; + letter-spacing: 0.1px; + + a { + color: $text-base-color; + + &:hover, + &focus { + color: $black; + } + } + } +} + +// Footer +.footer { + margin-top: 8em; + margin-bottom: 2em; + text-align: center; + + @include media-query($on-mobile) { + margin-top: 3em; + } + span.footer_item { + color: $black; + opacity: 0.8; + font-weight: $bold-weight; + font-size: $small-font-size; + } + a.footer_item { + color: $black; + opacity: 0.8; + text-decoration: none; + + &:not(:last-child) { + margin-right: 10px; + &:hover { + opacity: 1; + } + } + } + + .footer_copyright { + font-size: $small-font-size - 1; + margin-top: 3px; + display: block; + color: $gray; + opacity: 0.8; + } +} + +.not-found { + text-align: center; + display: flex; + justify-content: center; + flex-direction: column; + height: 75vh; + .title { + font-size: 5em; + font-weight: $bold-weight; + line-height: 1.1; + color: $black; + text-shadow: 1px 0px 0px $text-link-blue; + } + .phrase { + color: $text-base-color; + } + .solution { + color: $text-link-blue; + letter-spacing: 0.5px; + } + .solution:hover { + color: $text-link-blue-active; + } +} + + +.search-article { + position: relative; + margin-bottom: 50px; + + label[for="search-input"] { + position: relative; + top: 10px; + left: 11px; + } + + input[type="search"] { + top: 0; + left: 0; + border: 0; + width: 100%; + height: 40px; + outline: none; + position: absolute; + border-radius: 5px; + padding: 10px 10px 10px 35px; + color: $text-base-color; + -webkit-appearance: none; + font-size: $base-font-size; + background-color: rgba(128, 128, 128, 0.1); + border: 1px solid rgba(128, 128, 128, 0.1); + &::-webkit-input-placeholder { + color: #808080; + } + &::-webkit-search-decoration, + &::-webkit-search-results-decoration { + display: none; + } + } +} + +#search-results { + text-align: center; + li { + text-align: left; + } +} + +.archive-tags { + height: auto; + .tag-item { + padding: 1px 3px; + border-radius: 2px; + border: 1px solid rgba(128, 128, 128, 0.1); + background-color: rgba(128, 128, 128, 0.1); + } +} diff --git a/src/_sass/klise/_miscellaneous.scss b/src/_sass/klise/_miscellaneous.scss new file mode 100644 index 0000000..0c96da6 --- /dev/null +++ b/src/_sass/klise/_miscellaneous.scss @@ -0,0 +1,41 @@ +// Animation fade-in +@keyframes fadein { + 0% { + opacity: 0.2; + } + + 100% { + opacity: 0.8; + } +} + +// Animation blur +@keyframes blur { + 0% { + filter: blur(0px); + } + + 100% { + filter: blur(4px); + } +} + +// Responsive embed video +.embed-responsive { + height: 0; + max-width: 100%; + overflow: hidden; + position: relative; + padding-bottom: 56.25%; + margin-top: 20px; + + iframe, + object, + embed { + top: 0; + left: 0; + width: 100%; + height: 100%; + position: absolute; + } +} diff --git a/src/_sass/klise/_post.scss b/src/_sass/klise/_post.scss new file mode 100644 index 0000000..7465d46 --- /dev/null +++ b/src/_sass/klise/_post.scss @@ -0,0 +1,261 @@ +// Post wrapper +.wrapper.post { + @include media-query($on-mobile) { + padding-left: $spacing-half; + padding-right: $spacing-half; + } +} + +// Post title +.header { + margin-top: 7.8em; + margin-bottom: 3em; + + .tags { + margin-left: 3px; + letter-spacing: 0.5px; + + .tag { + font-weight: $bold-weight; + font-size: $small-font-size - 2; + + &:hover { + text-decoration: none; + } + } + } + + .header-title { + font-size: 2em; + line-height: 1.2; + margin-top: 10px; + margin-bottom: 20px; + + &.center { + text-align: center; + } + + @include media-query($on-mobile) { + font-size: 1.9em; + } + } +} + +// Post meta +.post-meta { + padding-top: 3px; + line-height: 1.3; + color: $gray; + + time { + position: relative; + margin-right: 1.5em; + + &::after { + background: $light; + bottom: 1px; + content: " "; + height: 2px; + position: absolute; + right: -20px; + width: 12px; + } + } + + span[itemprop="author"] { + border-bottom: 1px dotted $light; + } +} + +// Post content +.page-content { + padding-top: 8px; + + iframe { + text-align: center; + } + + figure { + img { + border-radius: 2px; + } + + figcaption { + margin-top: 5px; + font-style: italic; + font-size: $small-font-size; + } + } + + a { + color: $text-link-blue; + text-decoration: none; + &[target="_blank"]::after { + content: " \2197"; + font-size: $small-font-size; + line-height: 0; + position: relative; + bottom: 5px; + vertical-align: baseline; + } + + &:hover { + color: $text-link-blue-active; + } + + &:focus { + color: $text-link-blue; + } + } + + > p { + margin: 0; + padding-top: $spacing-full - 15; + padding-bottom: $spacing-full - 15; + } + + ul.task-list { + list-style: none; + margin: 0; + + li::before { + content: ""; + } + + li input[type="checkbox"] { + margin-right: 10px; + } + } + + dl dt { + font-weight: $bold-weight; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + color: $black; + font-weight: $bold-weight; + margin-top: $spacing-full; + margin-bottom: 0; + + &:hover { + .anchor-head { + color: $text-link-blue; + opacity: 1; + } + } + + .anchor-head { + position: relative; + opacity: 0; + outline: none; + + &::before { + content: "#"; + position: absolute; + right: -3px; + width: 1em; + font-weight: $bold-weight; + } + } + } + + h1 { + @include relative-font-size(1.5); + } + + h2 { + @include relative-font-size(1.375); + } + + h3 { + @include relative-font-size(1.25); + border-bottom: 1px solid $light; + padding-bottom: 4px; + } + + h4 { + @include relative-font-size(1.25); + } + + h5 { + @include relative-font-size(1); + } + + h6 { + @include relative-font-size(0.875); + } +} + +.post-nav { + display: flex; + position: relative; + margin-top: 5em; + border-top: 1px solid $light; + line-height: 1.4; + + .post-nav-item { + border-bottom: 0; + font-weight: $bold-weight; + padding-bottom: 10px; + + .post-title { + color: $black; + } + + &:hover, + &:focus { + .post-title { + color: $text-link-blue-active; + opacity: 0.9; + } + } + + .nav-arrow { + font-weight: $normal-weight; + font-size: $small-font-size; + color: $gray; + margin-bottom: 3px; + } + + width: 50%; + padding-top: 10px; + text-decoration: none; + box-sizing: border-box; + + &:nth-child(odd) { + padding-left: 0; + padding-right: 20px; + } + + &:nth-child(even) { + text-align: right; + padding-right: 0; + padding-left: 20px; + } + } + + @include media-query($on-mobile) { + display: block; + font-size: $small-font-size; + + .post-nav-item { + display: block; + width: 100%; + } + + .post-nav-item:nth-child(even) { + border-left: 0; + padding-left: 0; + border-top: 1px solid $light; + } + } +} + +.post-updated-at { + font-family: "Ubuntu mono", "monospace"; +} diff --git a/src/_sass/klise/_syntax.scss b/src/_sass/klise/_syntax.scss new file mode 100644 index 0000000..ccad7af --- /dev/null +++ b/src/_sass/klise/_syntax.scss @@ -0,0 +1,185 @@ +// Code +code { + font-family: $mono-family; + text-rendering: optimizeLegibility; + font-feature-settings: "calt" 1; + font-variant-ligatures: normal; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + font-size: inherit; + + &.highlighter-rouge { + padding: 1px 3px; + position: relative; + top: -1px; + background-color: #f6f6f6; + border-radius: 2px; + border: 1px solid rgba(128,128,128,0.1); + } +} + +// Codeblock Theme +pre.highlight, pre { + margin: 0 -27px; + @include media-query($on-mobile) { + margin: 0 calc(51% - 51vw); + padding-left: 20px; + } + border: 1px solid rgba(128,128,128,0.1); + background-color: #1a1b21; + border-radius: 2px; + padding: 10px; + display: block; + overflow-x: auto; + + > code { + width: 100%; + max-width: 50rem; + margin-left: auto; + margin-right: auto; + line-height: 1.5; + display: block; + border: 0; + } +} + +.highlight table td { + padding: 5px; +} + +.highlight table pre { + margin: 0; +} + +.highlight, +.highlight .w { + color: #fbf1c7; + // background-color: #1a1b21; +} + +.highlight .err { + color: #fb4934; + // background-color: #1a1b21; + font-weight: bold; +} + +.highlight .c, +.highlight .cd, +.highlight .cm, +.highlight .c1, +.highlight .cs { + color: #928374; + font-style: italic; +} + +.highlight .cp { + color: #8ec07c; +} + +.highlight .nt { + color: #fb4934; +} + +.highlight .o, +.highlight .ow { + color: #fbf1c7; +} + +.highlight .p, +.highlight .pi { + color: #fbf1c7; +} + +.highlight .gi { + color: #b8bb26; + background-color: #282828; +} + +.highlight .gd { + color: #fb4934; + background-color: #282828; +} + +.highlight .gh { + color: #b8bb26; + font-weight: bold; +} + +.highlight .k, +.highlight .kn, +.highlight .kp, +.highlight .kr, +.highlight .kv { + color: #fb4934; +} + +.highlight .kc { + color: #d3869b; +} + +.highlight .kt { + color: #fabd2f; +} + +.highlight .kd { + color: #fe8019; +} + +.highlight .s, +.highlight .sb, +.highlight .sc, +.highlight .sd, +.highlight .s2, +.highlight .sh, +.highlight .sx, +.highlight .s1 { + color: #b8bb26; + font-style: italic; +} + +.highlight .si { + color: #b8bb26; + font-style: italic; +} + +.highlight .sr { + color: #b8bb26; + font-style: italic; +} + +.highlight .se { + color: #fe8019; +} + +.highlight .nn { + color: #8ec07c; +} + +.highlight .nc { + color: #8ec07c; +} + +.highlight .no { + color: #d3869b; +} + +.highlight .na { + color: #b8bb26; +} + +.highlight .m, +.highlight .mf, +.highlight .mh, +.highlight .mi, +.highlight .il, +.highlight .mo, +.highlight .mb, +.highlight .mx { + color: #d3869b; +} + +.highlight .ss { + color: #83a598; +} diff --git a/src/_sass/main.scss b/src/_sass/main.scss new file mode 100644 index 0000000..1a389a7 --- /dev/null +++ b/src/_sass/main.scss @@ -0,0 +1,63 @@ +// Fonts preferences +$sans-family: Roboto, sans-serif; +$mono-family: Consolas, monospace; +$base-font-size: 16px; +$medium-font-size: $base-font-size * 0.938; +$small-font-size: $base-font-size * 0.875; +$base-line-height: 1.85; + +// Font weight +// $light-weight: 300; // uncomment if necessary +$normal-weight: 400; +$bold-weight: 700; +// $black-weight: 900; // uncomment if necessary + +//Light Colors +$text-base-color: #434648; +$text-link-blue: #003fff; +$text-link-blue-active: #0036c7; + +$black: #0d122b; +$light: #ececec; +$smoke: #d2c7c7; +$gray: #6b7886; +$white: #fff; + +// Dark Colors +$dark-text-base-color: #c7bebe; +$dark-text-link-blue: #ff5277; +$dark-text-link-blue-active: #ff2957; + +$dark-black: #131418; +$dark-white: #eaeaea; +$dark-light: #1b1d25; +$dark-smoke: #4a4d56; +$dark-gray: #767f87; + +// Width of the content area +$wide-size: 890px; +$narrow-size: 720px; + +// Padding unit +$spacing-full: 30px; +$spacing-half: $spacing-full / 2; + +// State of devices +$on-mobile: 768px; +$on-tablet: 769px; +$on-desktop: 1024px; +$on-widescreen: 1152px; + +@mixin media-query($device) { + @media screen and (max-width: $device) { + @content; + } +} + +@mixin relative-font-size($ratio) { + font-size: $base-font-size * $ratio; +} + +// Import sass files +@import "klise/fonts", "klise/base", "klise/layout", "klise/post", + "klise/miscellaneous", "klise/syntax", "klise/dark"; diff --git a/src/_site/404.html b/src/_site/404.html new file mode 100644 index 0000000..714474e --- /dev/null +++ b/src/_site/404.html @@ -0,0 +1 @@ + 404 - Pim Kunis diff --git a/src/_site/about/index.html b/src/_site/about/index.html new file mode 100644 index 0000000..0a0c79e --- /dev/null +++ b/src/_site/about/index.html @@ -0,0 +1 @@ + Me - Pim Kunis

Me.

Here I might post some personally identifiable information.

diff --git a/src/_site/ansible-edit-grub/index.html b/src/_site/ansible-edit-grub/index.html new file mode 100644 index 0000000..d1c7883 --- /dev/null +++ b/src/_site/ansible-edit-grub/index.html @@ -0,0 +1,18 @@ + Using Ansible to alter Kernel Parameters - Pim Kunis

Using Ansible to alter Kernel Parameters

For months, I’ve had a peculiar problem with my laptop: once in a while, seemingly without reason, my laptop screen would freeze. This only happened on my laptop screen, and not on an external monitor. I had kind of learned to live with it as I couldn’t find a solution online. The only remedy I had was reloading my window manager, which would often unfreeze the screen.

Yesterday I tried Googling once more and I actually found a thread about it on the Arch Linux forums! They talk about the same laptop model, the Lenovo ThinkPad x260, having the problem. Fortunately, they also propose a temporary fix.

Trying the Fix

Apparently, a problem with the Panel Self Refresh (PSR) feature of Intel iGPUs is the culprit. According to the Linux source code, PSR enables the display to go into a lower standby mode when the sytem is idle but the screen is in use. These lower standby modes can reduce power usage of your device when idling.

This all seems useful, except when it makes your screen freeze! The proposed fix disables the PSR feature entirely. To do this, we need to change a parameter to the Intel Graphics Linux Kernel Module (LKM). The LKM for Intel Graphics is called i915. There are multiple ways to change kernel parameters, but I chose to edit my Grub configuration.

First, I wanted to test whether it actually works. When booting into my Linux partition via Grub, you can press e to edit the Grub definition. Somewhere there, you can find the linux command which specifies to boot Linux and how to do that. I simply appended the option i915.enable_psr=0 to this line. After rebooting, I noticed my screen no longer freezes! Success!

Persisting the Fix

To make the change permanent, we need to permanently change Grub’s configuration. One way to do this, is by changing Grub’s defaults in /etc/default/grub. Namely, the GRUB_CMDLINE_LINUX_DEFAULT option specifies what options Grub should pass to the Linux kernel by default. For me, this is a nice solution as the problem exists for both Linux OSes I have installed. I changed this option to:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i915.enable_psr=0"
+

Next, I wanted to automate this solution using Ansible. This turned out to be quite easy, as the Grub configuration looks a bit like an ini file (maybe it is?):

- name: Edit grub to disable Panel Self Refresh
+  become: true
+  ini_file:
+    path: /etc/default/grub
+    section: null
+    option: "GRUB_CMDLINE_LINUX_DEFAULT"
+    value: '"quiet splash i915.enable_psr=0"'
+    no_extra_spaces: true
+  notify: update grub
+

Lastly, I created the notify hook to update the Grub configuration:

- name: update grub
+  become: true
+  command:
+    cmd: update-grub
+

Update: Just use Nix

Lately, I have been learning a bit of NixOS with the intention of replacing my current setup. Compared to Ansible, applying this fix is a breeze on NixOS:

{
+  boot.kernelParams = [ "i915.enable_psr=0" ];
+}
+

That’s it, yep.

Conclusion

It turned out to be quite easy to change Linux kernel parameters using Ansible. Maybe some kernel gurus have better ways to change parameters, but this works for me for now.

As a sidenote, I started reading a bit more about NixOS and realised that it can solve issues like these much more nicely than Ansible does. I might replace my OS with NixOS some day, if I manage to rewrite my Ansible for it.

diff --git a/src/_site/archive/index.html b/src/_site/archive/index.html new file mode 100644 index 0000000..d39ca32 --- /dev/null +++ b/src/_site/archive/index.html @@ -0,0 +1 @@ + Archive - Pim Kunis diff --git a/src/_site/assets/css/fontawesome.all.min.css b/src/_site/assets/css/fontawesome.all.min.css new file mode 100644 index 0000000..84dbeb8 --- /dev/null +++ b/src/_site/assets/css/fontawesome.all.min.css @@ -0,0 +1,6 @@ +/*! + * Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2022 Fonticons, Inc. + */ +.fa{font-family:var(--fa-style-family,"Font Awesome 6 Free");font-weight:var(--fa-style,900)}.fa,.fa-brands,.fa-classic,.fa-regular,.fa-sharp,.fa-solid,.fab,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:var(--fa-display,inline-block);font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-classic,.fa-regular,.fa-solid,.far,.fas{font-family:"Font Awesome 6 Free"}.fa-brands,.fab{font-family:"Font Awesome 6 Brands"}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.08em) var(--fa-border-style,solid) var(--fa-border-color,#eee);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{-webkit-animation-name:fa-beat;animation-name:fa-beat;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{-webkit-animation-name:fa-bounce;animation-name:fa-bounce;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{-webkit-animation-name:fa-fade;animation-name:fa-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{-webkit-animation-name:fa-beat-fade;animation-name:fa-beat-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{-webkit-animation-name:fa-flip;animation-name:fa-flip;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{-webkit-animation-name:fa-shake;animation-name:fa-shake;-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-duration:var(--fa-animation-duration,2s);animation-duration:var(--fa-animation-duration,2s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,steps(8));animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{-webkit-animation-delay:-1ms;animation-delay:-1ms;-webkit-animation-duration:1ms;animation-duration:1ms;-webkit-animation-iteration-count:1;animation-iteration-count:1;transition-delay:0s;transition-duration:0s}}@-webkit-keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@-webkit-keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@-webkit-keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@-webkit-keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@-webkit-keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@-webkit-keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}.fa-rotate-by{-webkit-transform:rotate(var(--fa-rotate-angle,none));transform:rotate(var(--fa-rotate-angle,none))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)}.fa-0:before{content:"\30"}.fa-1:before{content:"\31"}.fa-2:before{content:"\32"}.fa-3:before{content:"\33"}.fa-4:before{content:"\34"}.fa-5:before{content:"\35"}.fa-6:before{content:"\36"}.fa-7:before{content:"\37"}.fa-8:before{content:"\38"}.fa-9:before{content:"\39"}.fa-fill-drip:before{content:"\f576"}.fa-arrows-to-circle:before{content:"\e4bd"}.fa-chevron-circle-right:before,.fa-circle-chevron-right:before{content:"\f138"}.fa-at:before{content:"\40"}.fa-trash-alt:before,.fa-trash-can:before{content:"\f2ed"}.fa-text-height:before{content:"\f034"}.fa-user-times:before,.fa-user-xmark:before{content:"\f235"}.fa-stethoscope:before{content:"\f0f1"}.fa-comment-alt:before,.fa-message:before{content:"\f27a"}.fa-info:before{content:"\f129"}.fa-compress-alt:before,.fa-down-left-and-up-right-to-center:before{content:"\f422"}.fa-explosion:before{content:"\e4e9"}.fa-file-alt:before,.fa-file-lines:before,.fa-file-text:before{content:"\f15c"}.fa-wave-square:before{content:"\f83e"}.fa-ring:before{content:"\f70b"}.fa-building-un:before{content:"\e4d9"}.fa-dice-three:before{content:"\f527"}.fa-calendar-alt:before,.fa-calendar-days:before{content:"\f073"}.fa-anchor-circle-check:before{content:"\e4aa"}.fa-building-circle-arrow-right:before{content:"\e4d1"}.fa-volleyball-ball:before,.fa-volleyball:before{content:"\f45f"}.fa-arrows-up-to-line:before{content:"\e4c2"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-circle-minus:before,.fa-minus-circle:before{content:"\f056"}.fa-door-open:before{content:"\f52b"}.fa-right-from-bracket:before,.fa-sign-out-alt:before{content:"\f2f5"}.fa-atom:before{content:"\f5d2"}.fa-soap:before{content:"\e06e"}.fa-heart-music-camera-bolt:before,.fa-icons:before{content:"\f86d"}.fa-microphone-alt-slash:before,.fa-microphone-lines-slash:before{content:"\f539"}.fa-bridge-circle-check:before{content:"\e4c9"}.fa-pump-medical:before{content:"\e06a"}.fa-fingerprint:before{content:"\f577"}.fa-hand-point-right:before{content:"\f0a4"}.fa-magnifying-glass-location:before,.fa-search-location:before{content:"\f689"}.fa-forward-step:before,.fa-step-forward:before{content:"\f051"}.fa-face-smile-beam:before,.fa-smile-beam:before{content:"\f5b8"}.fa-flag-checkered:before{content:"\f11e"}.fa-football-ball:before,.fa-football:before{content:"\f44e"}.fa-school-circle-exclamation:before{content:"\e56c"}.fa-crop:before{content:"\f125"}.fa-angle-double-down:before,.fa-angles-down:before{content:"\f103"}.fa-users-rectangle:before{content:"\e594"}.fa-people-roof:before{content:"\e537"}.fa-people-line:before{content:"\e534"}.fa-beer-mug-empty:before,.fa-beer:before{content:"\f0fc"}.fa-diagram-predecessor:before{content:"\e477"}.fa-arrow-up-long:before,.fa-long-arrow-up:before{content:"\f176"}.fa-burn:before,.fa-fire-flame-simple:before{content:"\f46a"}.fa-male:before,.fa-person:before{content:"\f183"}.fa-laptop:before{content:"\f109"}.fa-file-csv:before{content:"\f6dd"}.fa-menorah:before{content:"\f676"}.fa-truck-plane:before{content:"\e58f"}.fa-record-vinyl:before{content:"\f8d9"}.fa-face-grin-stars:before,.fa-grin-stars:before{content:"\f587"}.fa-bong:before{content:"\f55c"}.fa-pastafarianism:before,.fa-spaghetti-monster-flying:before{content:"\f67b"}.fa-arrow-down-up-across-line:before{content:"\e4af"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f2e5"}.fa-jar-wheat:before{content:"\e517"}.fa-envelopes-bulk:before,.fa-mail-bulk:before{content:"\f674"}.fa-file-circle-exclamation:before{content:"\e4eb"}.fa-circle-h:before,.fa-hospital-symbol:before{content:"\f47e"}.fa-pager:before{content:"\f815"}.fa-address-book:before,.fa-contact-book:before{content:"\f2b9"}.fa-strikethrough:before{content:"\f0cc"}.fa-k:before{content:"\4b"}.fa-landmark-flag:before{content:"\e51c"}.fa-pencil-alt:before,.fa-pencil:before{content:"\f303"}.fa-backward:before{content:"\f04a"}.fa-caret-right:before{content:"\f0da"}.fa-comments:before{content:"\f086"}.fa-file-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-code-pull-request:before{content:"\e13c"}.fa-clipboard-list:before{content:"\f46d"}.fa-truck-loading:before,.fa-truck-ramp-box:before{content:"\f4de"}.fa-user-check:before{content:"\f4fc"}.fa-vial-virus:before{content:"\e597"}.fa-sheet-plastic:before{content:"\e571"}.fa-blog:before{content:"\f781"}.fa-user-ninja:before{content:"\f504"}.fa-person-arrow-up-from-line:before{content:"\e539"}.fa-scroll-torah:before,.fa-torah:before{content:"\f6a0"}.fa-broom-ball:before,.fa-quidditch-broom-ball:before,.fa-quidditch:before{content:"\f458"}.fa-toggle-off:before{content:"\f204"}.fa-archive:before,.fa-box-archive:before{content:"\f187"}.fa-person-drowning:before{content:"\e545"}.fa-arrow-down-9-1:before,.fa-sort-numeric-desc:before,.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-face-grin-tongue-squint:before,.fa-grin-tongue-squint:before{content:"\f58a"}.fa-spray-can:before{content:"\f5bd"}.fa-truck-monster:before{content:"\f63b"}.fa-w:before{content:"\57"}.fa-earth-africa:before,.fa-globe-africa:before{content:"\f57c"}.fa-rainbow:before{content:"\f75b"}.fa-circle-notch:before{content:"\f1ce"}.fa-tablet-alt:before,.fa-tablet-screen-button:before{content:"\f3fa"}.fa-paw:before{content:"\f1b0"}.fa-cloud:before{content:"\f0c2"}.fa-trowel-bricks:before{content:"\e58a"}.fa-face-flushed:before,.fa-flushed:before{content:"\f579"}.fa-hospital-user:before{content:"\f80d"}.fa-tent-arrow-left-right:before{content:"\e57f"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-binoculars:before{content:"\f1e5"}.fa-microphone-slash:before{content:"\f131"}.fa-box-tissue:before{content:"\e05b"}.fa-motorcycle:before{content:"\f21c"}.fa-bell-concierge:before,.fa-concierge-bell:before{content:"\f562"}.fa-pen-ruler:before,.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-arrows-left-right:before,.fa-people-arrows:before{content:"\e068"}.fa-mars-and-venus-burst:before{content:"\e523"}.fa-caret-square-right:before,.fa-square-caret-right:before{content:"\f152"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-sun-plant-wilt:before{content:"\e57a"}.fa-toilets-portable:before{content:"\e584"}.fa-hockey-puck:before{content:"\f453"}.fa-table:before{content:"\f0ce"}.fa-magnifying-glass-arrow-right:before{content:"\e521"}.fa-digital-tachograph:before,.fa-tachograph-digital:before{content:"\f566"}.fa-users-slash:before{content:"\e073"}.fa-clover:before{content:"\e139"}.fa-mail-reply:before,.fa-reply:before{content:"\f3e5"}.fa-star-and-crescent:before{content:"\f699"}.fa-house-fire:before{content:"\e50c"}.fa-minus-square:before,.fa-square-minus:before{content:"\f146"}.fa-helicopter:before{content:"\f533"}.fa-compass:before{content:"\f14e"}.fa-caret-square-down:before,.fa-square-caret-down:before{content:"\f150"}.fa-file-circle-question:before{content:"\e4ef"}.fa-laptop-code:before{content:"\f5fc"}.fa-swatchbook:before{content:"\f5c3"}.fa-prescription-bottle:before{content:"\f485"}.fa-bars:before,.fa-navicon:before{content:"\f0c9"}.fa-people-group:before{content:"\e533"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-heart-broken:before,.fa-heart-crack:before{content:"\f7a9"}.fa-external-link-square-alt:before,.fa-square-up-right:before{content:"\f360"}.fa-face-kiss-beam:before,.fa-kiss-beam:before{content:"\f597"}.fa-film:before{content:"\f008"}.fa-ruler-horizontal:before{content:"\f547"}.fa-people-robbery:before{content:"\e536"}.fa-lightbulb:before{content:"\f0eb"}.fa-caret-left:before{content:"\f0d9"}.fa-circle-exclamation:before,.fa-exclamation-circle:before{content:"\f06a"}.fa-school-circle-xmark:before{content:"\e56d"}.fa-arrow-right-from-bracket:before,.fa-sign-out:before{content:"\f08b"}.fa-chevron-circle-down:before,.fa-circle-chevron-down:before{content:"\f13a"}.fa-unlock-alt:before,.fa-unlock-keyhole:before{content:"\f13e"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-headphones-alt:before,.fa-headphones-simple:before{content:"\f58f"}.fa-sitemap:before{content:"\f0e8"}.fa-circle-dollar-to-slot:before,.fa-donate:before{content:"\f4b9"}.fa-memory:before{content:"\f538"}.fa-road-spikes:before{content:"\e568"}.fa-fire-burner:before{content:"\e4f1"}.fa-flag:before{content:"\f024"}.fa-hanukiah:before{content:"\f6e6"}.fa-feather:before{content:"\f52d"}.fa-volume-down:before,.fa-volume-low:before{content:"\f027"}.fa-comment-slash:before{content:"\f4b3"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-compress:before{content:"\f066"}.fa-wheat-alt:before,.fa-wheat-awn:before{content:"\e2cd"}.fa-ankh:before{content:"\f644"}.fa-hands-holding-child:before{content:"\e4fa"}.fa-asterisk:before{content:"\2a"}.fa-check-square:before,.fa-square-check:before{content:"\f14a"}.fa-peseta-sign:before{content:"\e221"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-ghost:before{content:"\f6e2"}.fa-list-squares:before,.fa-list:before{content:"\f03a"}.fa-phone-square-alt:before,.fa-square-phone-flip:before{content:"\f87b"}.fa-cart-plus:before{content:"\f217"}.fa-gamepad:before{content:"\f11b"}.fa-circle-dot:before,.fa-dot-circle:before{content:"\f192"}.fa-dizzy:before,.fa-face-dizzy:before{content:"\f567"}.fa-egg:before{content:"\f7fb"}.fa-house-medical-circle-xmark:before{content:"\e513"}.fa-campground:before{content:"\f6bb"}.fa-folder-plus:before{content:"\f65e"}.fa-futbol-ball:before,.fa-futbol:before,.fa-soccer-ball:before{content:"\f1e3"}.fa-paint-brush:before,.fa-paintbrush:before{content:"\f1fc"}.fa-lock:before{content:"\f023"}.fa-gas-pump:before{content:"\f52f"}.fa-hot-tub-person:before,.fa-hot-tub:before{content:"\f593"}.fa-map-location:before,.fa-map-marked:before{content:"\f59f"}.fa-house-flood-water:before{content:"\e50e"}.fa-tree:before{content:"\f1bb"}.fa-bridge-lock:before{content:"\e4cc"}.fa-sack-dollar:before{content:"\f81d"}.fa-edit:before,.fa-pen-to-square:before{content:"\f044"}.fa-car-side:before{content:"\f5e4"}.fa-share-alt:before,.fa-share-nodes:before{content:"\f1e0"}.fa-heart-circle-minus:before{content:"\e4ff"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-microscope:before{content:"\f610"}.fa-sink:before{content:"\e06d"}.fa-bag-shopping:before,.fa-shopping-bag:before{content:"\f290"}.fa-arrow-down-z-a:before,.fa-sort-alpha-desc:before,.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-mitten:before{content:"\f7b5"}.fa-person-rays:before{content:"\e54d"}.fa-users:before{content:"\f0c0"}.fa-eye-slash:before{content:"\f070"}.fa-flask-vial:before{content:"\e4f3"}.fa-hand-paper:before,.fa-hand:before{content:"\f256"}.fa-om:before{content:"\f679"}.fa-worm:before{content:"\e599"}.fa-house-circle-xmark:before{content:"\e50b"}.fa-plug:before{content:"\f1e6"}.fa-chevron-up:before{content:"\f077"}.fa-hand-spock:before{content:"\f259"}.fa-stopwatch:before{content:"\f2f2"}.fa-face-kiss:before,.fa-kiss:before{content:"\f596"}.fa-bridge-circle-xmark:before{content:"\e4cb"}.fa-face-grin-tongue:before,.fa-grin-tongue:before{content:"\f589"}.fa-chess-bishop:before{content:"\f43a"}.fa-face-grin-wink:before,.fa-grin-wink:before{content:"\f58c"}.fa-deaf:before,.fa-deafness:before,.fa-ear-deaf:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-road-circle-check:before{content:"\e564"}.fa-dice-five:before{content:"\f523"}.fa-rss-square:before,.fa-square-rss:before{content:"\f143"}.fa-land-mine-on:before{content:"\e51b"}.fa-i-cursor:before{content:"\f246"}.fa-stamp:before{content:"\f5bf"}.fa-stairs:before{content:"\e289"}.fa-i:before{content:"\49"}.fa-hryvnia-sign:before,.fa-hryvnia:before{content:"\f6f2"}.fa-pills:before{content:"\f484"}.fa-face-grin-wide:before,.fa-grin-alt:before{content:"\f581"}.fa-tooth:before{content:"\f5c9"}.fa-v:before{content:"\56"}.fa-bangladeshi-taka-sign:before{content:"\e2e6"}.fa-bicycle:before{content:"\f206"}.fa-rod-asclepius:before,.fa-rod-snake:before,.fa-staff-aesculapius:before,.fa-staff-snake:before{content:"\e579"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-ambulance:before,.fa-truck-medical:before{content:"\f0f9"}.fa-wheat-awn-circle-exclamation:before{content:"\e598"}.fa-snowman:before{content:"\f7d0"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-road-barrier:before{content:"\e562"}.fa-school:before{content:"\f549"}.fa-igloo:before{content:"\f7ae"}.fa-joint:before{content:"\f595"}.fa-angle-right:before{content:"\f105"}.fa-horse:before{content:"\f6f0"}.fa-q:before{content:"\51"}.fa-g:before{content:"\47"}.fa-notes-medical:before{content:"\f481"}.fa-temperature-2:before,.fa-temperature-half:before,.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-dong-sign:before{content:"\e169"}.fa-capsules:before{content:"\f46b"}.fa-poo-bolt:before,.fa-poo-storm:before{content:"\f75a"}.fa-face-frown-open:before,.fa-frown-open:before{content:"\f57a"}.fa-hand-point-up:before{content:"\f0a6"}.fa-money-bill:before{content:"\f0d6"}.fa-bookmark:before{content:"\f02e"}.fa-align-justify:before{content:"\f039"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-helmet-un:before{content:"\e503"}.fa-bullseye:before{content:"\f140"}.fa-bacon:before{content:"\f7e5"}.fa-hand-point-down:before{content:"\f0a7"}.fa-arrow-up-from-bracket:before{content:"\e09a"}.fa-folder-blank:before,.fa-folder:before{content:"\f07b"}.fa-file-medical-alt:before,.fa-file-waveform:before{content:"\f478"}.fa-radiation:before{content:"\f7b9"}.fa-chart-simple:before{content:"\e473"}.fa-mars-stroke:before{content:"\f229"}.fa-vial:before{content:"\f492"}.fa-dashboard:before,.fa-gauge-med:before,.fa-gauge:before,.fa-tachometer-alt-average:before{content:"\f624"}.fa-magic-wand-sparkles:before,.fa-wand-magic-sparkles:before{content:"\e2ca"}.fa-e:before{content:"\45"}.fa-pen-alt:before,.fa-pen-clip:before{content:"\f305"}.fa-bridge-circle-exclamation:before{content:"\e4ca"}.fa-user:before{content:"\f007"}.fa-school-circle-check:before{content:"\e56b"}.fa-dumpster:before{content:"\f793"}.fa-shuttle-van:before,.fa-van-shuttle:before{content:"\f5b6"}.fa-building-user:before{content:"\e4da"}.fa-caret-square-left:before,.fa-square-caret-left:before{content:"\f191"}.fa-highlighter:before{content:"\f591"}.fa-key:before{content:"\f084"}.fa-bullhorn:before{content:"\f0a1"}.fa-globe:before{content:"\f0ac"}.fa-synagogue:before{content:"\f69b"}.fa-person-half-dress:before{content:"\e548"}.fa-road-bridge:before{content:"\e563"}.fa-location-arrow:before{content:"\f124"}.fa-c:before{content:"\43"}.fa-tablet-button:before{content:"\f10a"}.fa-building-lock:before{content:"\e4d6"}.fa-pizza-slice:before{content:"\f818"}.fa-money-bill-wave:before{content:"\f53a"}.fa-area-chart:before,.fa-chart-area:before{content:"\f1fe"}.fa-house-flag:before{content:"\e50d"}.fa-person-circle-minus:before{content:"\e540"}.fa-ban:before,.fa-cancel:before{content:"\f05e"}.fa-camera-rotate:before{content:"\e0d8"}.fa-air-freshener:before,.fa-spray-can-sparkles:before{content:"\f5d0"}.fa-star:before{content:"\f005"}.fa-repeat:before{content:"\f363"}.fa-cross:before{content:"\f654"}.fa-box:before{content:"\f466"}.fa-venus-mars:before{content:"\f228"}.fa-arrow-pointer:before,.fa-mouse-pointer:before{content:"\f245"}.fa-expand-arrows-alt:before,.fa-maximize:before{content:"\f31e"}.fa-charging-station:before{content:"\f5e7"}.fa-shapes:before,.fa-triangle-circle-square:before{content:"\f61f"}.fa-random:before,.fa-shuffle:before{content:"\f074"}.fa-person-running:before,.fa-running:before{content:"\f70c"}.fa-mobile-retro:before{content:"\e527"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-spider:before{content:"\f717"}.fa-hands-bound:before{content:"\e4f9"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-plane-circle-exclamation:before{content:"\e556"}.fa-x-ray:before{content:"\f497"}.fa-spell-check:before{content:"\f891"}.fa-slash:before{content:"\f715"}.fa-computer-mouse:before,.fa-mouse:before{content:"\f8cc"}.fa-arrow-right-to-bracket:before,.fa-sign-in:before{content:"\f090"}.fa-shop-slash:before,.fa-store-alt-slash:before{content:"\e070"}.fa-server:before{content:"\f233"}.fa-virus-covid-slash:before{content:"\e4a9"}.fa-shop-lock:before{content:"\e4a5"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-blender-phone:before{content:"\f6b6"}.fa-building-wheat:before{content:"\e4db"}.fa-person-breastfeeding:before{content:"\e53a"}.fa-right-to-bracket:before,.fa-sign-in-alt:before{content:"\f2f6"}.fa-venus:before{content:"\f221"}.fa-passport:before{content:"\f5ab"}.fa-heart-pulse:before,.fa-heartbeat:before{content:"\f21e"}.fa-people-carry-box:before,.fa-people-carry:before{content:"\f4ce"}.fa-temperature-high:before{content:"\f769"}.fa-microchip:before{content:"\f2db"}.fa-crown:before{content:"\f521"}.fa-weight-hanging:before{content:"\f5cd"}.fa-xmarks-lines:before{content:"\e59a"}.fa-file-prescription:before{content:"\f572"}.fa-weight-scale:before,.fa-weight:before{content:"\f496"}.fa-user-friends:before,.fa-user-group:before{content:"\f500"}.fa-arrow-up-a-z:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-chess-knight:before{content:"\f441"}.fa-face-laugh-squint:before,.fa-laugh-squint:before{content:"\f59b"}.fa-wheelchair:before{content:"\f193"}.fa-arrow-circle-up:before,.fa-circle-arrow-up:before{content:"\f0aa"}.fa-toggle-on:before{content:"\f205"}.fa-person-walking:before,.fa-walking:before{content:"\f554"}.fa-l:before{content:"\4c"}.fa-fire:before{content:"\f06d"}.fa-bed-pulse:before,.fa-procedures:before{content:"\f487"}.fa-shuttle-space:before,.fa-space-shuttle:before{content:"\f197"}.fa-face-laugh:before,.fa-laugh:before{content:"\f599"}.fa-folder-open:before{content:"\f07c"}.fa-heart-circle-plus:before{content:"\e500"}.fa-code-fork:before{content:"\e13b"}.fa-city:before{content:"\f64f"}.fa-microphone-alt:before,.fa-microphone-lines:before{content:"\f3c9"}.fa-pepper-hot:before{content:"\f816"}.fa-unlock:before{content:"\f09c"}.fa-colon-sign:before{content:"\e140"}.fa-headset:before{content:"\f590"}.fa-store-slash:before{content:"\e071"}.fa-road-circle-xmark:before{content:"\e566"}.fa-user-minus:before{content:"\f503"}.fa-mars-stroke-up:before,.fa-mars-stroke-v:before{content:"\f22a"}.fa-champagne-glasses:before,.fa-glass-cheers:before{content:"\f79f"}.fa-clipboard:before{content:"\f328"}.fa-house-circle-exclamation:before{content:"\e50a"}.fa-file-arrow-up:before,.fa-file-upload:before{content:"\f574"}.fa-wifi-3:before,.fa-wifi-strong:before,.fa-wifi:before{content:"\f1eb"}.fa-bath:before,.fa-bathtub:before{content:"\f2cd"}.fa-underline:before{content:"\f0cd"}.fa-user-edit:before,.fa-user-pen:before{content:"\f4ff"}.fa-signature:before{content:"\f5b7"}.fa-stroopwafel:before{content:"\f551"}.fa-bold:before{content:"\f032"}.fa-anchor-lock:before{content:"\e4ad"}.fa-building-ngo:before{content:"\e4d7"}.fa-manat-sign:before{content:"\e1d5"}.fa-not-equal:before{content:"\f53e"}.fa-border-style:before,.fa-border-top-left:before{content:"\f853"}.fa-map-location-dot:before,.fa-map-marked-alt:before{content:"\f5a0"}.fa-jedi:before{content:"\f669"}.fa-poll:before,.fa-square-poll-vertical:before{content:"\f681"}.fa-mug-hot:before{content:"\f7b6"}.fa-battery-car:before,.fa-car-battery:before{content:"\f5df"}.fa-gift:before{content:"\f06b"}.fa-dice-two:before{content:"\f528"}.fa-chess-queen:before{content:"\f445"}.fa-glasses:before{content:"\f530"}.fa-chess-board:before{content:"\f43c"}.fa-building-circle-check:before{content:"\e4d2"}.fa-person-chalkboard:before{content:"\e53d"}.fa-mars-stroke-h:before,.fa-mars-stroke-right:before{content:"\f22b"}.fa-hand-back-fist:before,.fa-hand-rock:before{content:"\f255"}.fa-caret-square-up:before,.fa-square-caret-up:before{content:"\f151"}.fa-cloud-showers-water:before{content:"\e4e4"}.fa-bar-chart:before,.fa-chart-bar:before{content:"\f080"}.fa-hands-bubbles:before,.fa-hands-wash:before{content:"\e05e"}.fa-less-than-equal:before{content:"\f537"}.fa-train:before{content:"\f238"}.fa-eye-low-vision:before,.fa-low-vision:before{content:"\f2a8"}.fa-crow:before{content:"\f520"}.fa-sailboat:before{content:"\e445"}.fa-window-restore:before{content:"\f2d2"}.fa-plus-square:before,.fa-square-plus:before{content:"\f0fe"}.fa-torii-gate:before{content:"\f6a1"}.fa-frog:before{content:"\f52e"}.fa-bucket:before{content:"\e4cf"}.fa-image:before{content:"\f03e"}.fa-microphone:before{content:"\f130"}.fa-cow:before{content:"\f6c8"}.fa-caret-up:before{content:"\f0d8"}.fa-screwdriver:before{content:"\f54a"}.fa-folder-closed:before{content:"\e185"}.fa-house-tsunami:before{content:"\e515"}.fa-square-nfi:before{content:"\e576"}.fa-arrow-up-from-ground-water:before{content:"\e4b5"}.fa-glass-martini-alt:before,.fa-martini-glass:before{content:"\f57b"}.fa-rotate-back:before,.fa-rotate-backward:before,.fa-rotate-left:before,.fa-undo-alt:before{content:"\f2ea"}.fa-columns:before,.fa-table-columns:before{content:"\f0db"}.fa-lemon:before{content:"\f094"}.fa-head-side-mask:before{content:"\e063"}.fa-handshake:before{content:"\f2b5"}.fa-gem:before{content:"\f3a5"}.fa-dolly-box:before,.fa-dolly:before{content:"\f472"}.fa-smoking:before{content:"\f48d"}.fa-compress-arrows-alt:before,.fa-minimize:before{content:"\f78c"}.fa-monument:before{content:"\f5a6"}.fa-snowplow:before{content:"\f7d2"}.fa-angle-double-right:before,.fa-angles-right:before{content:"\f101"}.fa-cannabis:before{content:"\f55f"}.fa-circle-play:before,.fa-play-circle:before{content:"\f144"}.fa-tablets:before{content:"\f490"}.fa-ethernet:before{content:"\f796"}.fa-eur:before,.fa-euro-sign:before,.fa-euro:before{content:"\f153"}.fa-chair:before{content:"\f6c0"}.fa-check-circle:before,.fa-circle-check:before{content:"\f058"}.fa-circle-stop:before,.fa-stop-circle:before{content:"\f28d"}.fa-compass-drafting:before,.fa-drafting-compass:before{content:"\f568"}.fa-plate-wheat:before{content:"\e55a"}.fa-icicles:before{content:"\f7ad"}.fa-person-shelter:before{content:"\e54f"}.fa-neuter:before{content:"\f22c"}.fa-id-badge:before{content:"\f2c1"}.fa-marker:before{content:"\f5a1"}.fa-face-laugh-beam:before,.fa-laugh-beam:before{content:"\f59a"}.fa-helicopter-symbol:before{content:"\e502"}.fa-universal-access:before{content:"\f29a"}.fa-chevron-circle-up:before,.fa-circle-chevron-up:before{content:"\f139"}.fa-lari-sign:before{content:"\e1c8"}.fa-volcano:before{content:"\f770"}.fa-person-walking-dashed-line-arrow-right:before{content:"\e553"}.fa-gbp:before,.fa-pound-sign:before,.fa-sterling-sign:before{content:"\f154"}.fa-viruses:before{content:"\e076"}.fa-square-person-confined:before{content:"\e577"}.fa-user-tie:before{content:"\f508"}.fa-arrow-down-long:before,.fa-long-arrow-down:before{content:"\f175"}.fa-tent-arrow-down-to-line:before{content:"\e57e"}.fa-certificate:before{content:"\f0a3"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-suitcase:before{content:"\f0f2"}.fa-person-skating:before,.fa-skating:before{content:"\f7c5"}.fa-filter-circle-dollar:before,.fa-funnel-dollar:before{content:"\f662"}.fa-camera-retro:before{content:"\f083"}.fa-arrow-circle-down:before,.fa-circle-arrow-down:before{content:"\f0ab"}.fa-arrow-right-to-file:before,.fa-file-import:before{content:"\f56f"}.fa-external-link-square:before,.fa-square-arrow-up-right:before{content:"\f14c"}.fa-box-open:before{content:"\f49e"}.fa-scroll:before{content:"\f70e"}.fa-spa:before{content:"\f5bb"}.fa-location-pin-lock:before{content:"\e51f"}.fa-pause:before{content:"\f04c"}.fa-hill-avalanche:before{content:"\e507"}.fa-temperature-0:before,.fa-temperature-empty:before,.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-bomb:before{content:"\f1e2"}.fa-registered:before{content:"\f25d"}.fa-address-card:before,.fa-contact-card:before,.fa-vcard:before{content:"\f2bb"}.fa-balance-scale-right:before,.fa-scale-unbalanced-flip:before{content:"\f516"}.fa-subscript:before{content:"\f12c"}.fa-diamond-turn-right:before,.fa-directions:before{content:"\f5eb"}.fa-burst:before{content:"\e4dc"}.fa-house-laptop:before,.fa-laptop-house:before{content:"\e066"}.fa-face-tired:before,.fa-tired:before{content:"\f5c8"}.fa-money-bills:before{content:"\e1f3"}.fa-smog:before{content:"\f75f"}.fa-crutch:before{content:"\f7f7"}.fa-cloud-arrow-up:before,.fa-cloud-upload-alt:before,.fa-cloud-upload:before{content:"\f0ee"}.fa-palette:before{content:"\f53f"}.fa-arrows-turn-right:before{content:"\e4c0"}.fa-vest:before{content:"\e085"}.fa-ferry:before{content:"\e4ea"}.fa-arrows-down-to-people:before{content:"\e4b9"}.fa-seedling:before,.fa-sprout:before{content:"\f4d8"}.fa-arrows-alt-h:before,.fa-left-right:before{content:"\f337"}.fa-boxes-packing:before{content:"\e4c7"}.fa-arrow-circle-left:before,.fa-circle-arrow-left:before{content:"\f0a8"}.fa-group-arrows-rotate:before{content:"\e4f6"}.fa-bowl-food:before{content:"\e4c6"}.fa-candy-cane:before{content:"\f786"}.fa-arrow-down-wide-short:before,.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-cloud-bolt:before,.fa-thunderstorm:before{content:"\f76c"}.fa-remove-format:before,.fa-text-slash:before{content:"\f87d"}.fa-face-smile-wink:before,.fa-smile-wink:before{content:"\f4da"}.fa-file-word:before{content:"\f1c2"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-arrows-h:before,.fa-arrows-left-right:before{content:"\f07e"}.fa-house-lock:before{content:"\e510"}.fa-cloud-arrow-down:before,.fa-cloud-download-alt:before,.fa-cloud-download:before{content:"\f0ed"}.fa-children:before{content:"\e4e1"}.fa-blackboard:before,.fa-chalkboard:before{content:"\f51b"}.fa-user-alt-slash:before,.fa-user-large-slash:before{content:"\f4fa"}.fa-envelope-open:before{content:"\f2b6"}.fa-handshake-alt-slash:before,.fa-handshake-simple-slash:before{content:"\e05f"}.fa-mattress-pillow:before{content:"\e525"}.fa-guarani-sign:before{content:"\e19a"}.fa-arrows-rotate:before,.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-fire-extinguisher:before{content:"\f134"}.fa-cruzeiro-sign:before{content:"\e152"}.fa-greater-than-equal:before{content:"\f532"}.fa-shield-alt:before,.fa-shield-halved:before{content:"\f3ed"}.fa-atlas:before,.fa-book-atlas:before{content:"\f558"}.fa-virus:before{content:"\e074"}.fa-envelope-circle-check:before{content:"\e4e8"}.fa-layer-group:before{content:"\f5fd"}.fa-arrows-to-dot:before{content:"\e4be"}.fa-archway:before{content:"\f557"}.fa-heart-circle-check:before{content:"\e4fd"}.fa-house-chimney-crack:before,.fa-house-damage:before{content:"\f6f1"}.fa-file-archive:before,.fa-file-zipper:before{content:"\f1c6"}.fa-square:before{content:"\f0c8"}.fa-glass-martini:before,.fa-martini-glass-empty:before{content:"\f000"}.fa-couch:before{content:"\f4b8"}.fa-cedi-sign:before{content:"\e0df"}.fa-italic:before{content:"\f033"}.fa-church:before{content:"\f51d"}.fa-comments-dollar:before{content:"\f653"}.fa-democrat:before{content:"\f747"}.fa-z:before{content:"\5a"}.fa-person-skiing:before,.fa-skiing:before{content:"\f7c9"}.fa-road-lock:before{content:"\e567"}.fa-a:before{content:"\41"}.fa-temperature-arrow-down:before,.fa-temperature-down:before{content:"\e03f"}.fa-feather-alt:before,.fa-feather-pointed:before{content:"\f56b"}.fa-p:before{content:"\50"}.fa-snowflake:before{content:"\f2dc"}.fa-newspaper:before{content:"\f1ea"}.fa-ad:before,.fa-rectangle-ad:before{content:"\f641"}.fa-arrow-circle-right:before,.fa-circle-arrow-right:before{content:"\f0a9"}.fa-filter-circle-xmark:before{content:"\e17b"}.fa-locust:before{content:"\e520"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-list-1-2:before,.fa-list-numeric:before,.fa-list-ol:before{content:"\f0cb"}.fa-person-dress-burst:before{content:"\e544"}.fa-money-check-alt:before,.fa-money-check-dollar:before{content:"\f53d"}.fa-vector-square:before{content:"\f5cb"}.fa-bread-slice:before{content:"\f7ec"}.fa-language:before{content:"\f1ab"}.fa-face-kiss-wink-heart:before,.fa-kiss-wink-heart:before{content:"\f598"}.fa-filter:before{content:"\f0b0"}.fa-question:before{content:"\3f"}.fa-file-signature:before{content:"\f573"}.fa-arrows-alt:before,.fa-up-down-left-right:before{content:"\f0b2"}.fa-house-chimney-user:before{content:"\e065"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-puzzle-piece:before{content:"\f12e"}.fa-money-check:before{content:"\f53c"}.fa-star-half-alt:before,.fa-star-half-stroke:before{content:"\f5c0"}.fa-code:before{content:"\f121"}.fa-glass-whiskey:before,.fa-whiskey-glass:before{content:"\f7a0"}.fa-building-circle-exclamation:before{content:"\e4d3"}.fa-magnifying-glass-chart:before{content:"\e522"}.fa-arrow-up-right-from-square:before,.fa-external-link:before{content:"\f08e"}.fa-cubes-stacked:before{content:"\e4e6"}.fa-krw:before,.fa-won-sign:before,.fa-won:before{content:"\f159"}.fa-virus-covid:before{content:"\e4a8"}.fa-austral-sign:before{content:"\e0a9"}.fa-f:before{content:"\46"}.fa-leaf:before{content:"\f06c"}.fa-road:before{content:"\f018"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-person-circle-plus:before{content:"\e541"}.fa-chart-pie:before,.fa-pie-chart:before{content:"\f200"}.fa-bolt-lightning:before{content:"\e0b7"}.fa-sack-xmark:before{content:"\e56a"}.fa-file-excel:before{content:"\f1c3"}.fa-file-contract:before{content:"\f56c"}.fa-fish-fins:before{content:"\e4f2"}.fa-building-flag:before{content:"\e4d5"}.fa-face-grin-beam:before,.fa-grin-beam:before{content:"\f582"}.fa-object-ungroup:before{content:"\f248"}.fa-poop:before{content:"\f619"}.fa-location-pin:before,.fa-map-marker:before{content:"\f041"}.fa-kaaba:before{content:"\f66b"}.fa-toilet-paper:before{content:"\f71e"}.fa-hard-hat:before,.fa-hat-hard:before,.fa-helmet-safety:before{content:"\f807"}.fa-eject:before{content:"\f052"}.fa-arrow-alt-circle-right:before,.fa-circle-right:before{content:"\f35a"}.fa-plane-circle-check:before{content:"\e555"}.fa-face-rolling-eyes:before,.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-object-group:before{content:"\f247"}.fa-chart-line:before,.fa-line-chart:before{content:"\f201"}.fa-mask-ventilator:before{content:"\e524"}.fa-arrow-right:before{content:"\f061"}.fa-map-signs:before,.fa-signs-post:before{content:"\f277"}.fa-cash-register:before{content:"\f788"}.fa-person-circle-question:before{content:"\e542"}.fa-h:before{content:"\48"}.fa-tarp:before{content:"\e57b"}.fa-screwdriver-wrench:before,.fa-tools:before{content:"\f7d9"}.fa-arrows-to-eye:before{content:"\e4bf"}.fa-plug-circle-bolt:before{content:"\e55b"}.fa-heart:before{content:"\f004"}.fa-mars-and-venus:before{content:"\f224"}.fa-home-user:before,.fa-house-user:before{content:"\e1b0"}.fa-dumpster-fire:before{content:"\f794"}.fa-house-crack:before{content:"\e3b1"}.fa-cocktail:before,.fa-martini-glass-citrus:before{content:"\f561"}.fa-face-surprise:before,.fa-surprise:before{content:"\f5c2"}.fa-bottle-water:before{content:"\e4c5"}.fa-circle-pause:before,.fa-pause-circle:before{content:"\f28b"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-apple-alt:before,.fa-apple-whole:before{content:"\f5d1"}.fa-kitchen-set:before{content:"\e51a"}.fa-r:before{content:"\52"}.fa-temperature-1:before,.fa-temperature-quarter:before,.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-cube:before{content:"\f1b2"}.fa-bitcoin-sign:before{content:"\e0b4"}.fa-shield-dog:before{content:"\e573"}.fa-solar-panel:before{content:"\f5ba"}.fa-lock-open:before{content:"\f3c1"}.fa-elevator:before{content:"\e16d"}.fa-money-bill-transfer:before{content:"\e528"}.fa-money-bill-trend-up:before{content:"\e529"}.fa-house-flood-water-circle-arrow-right:before{content:"\e50f"}.fa-poll-h:before,.fa-square-poll-horizontal:before{content:"\f682"}.fa-circle:before{content:"\f111"}.fa-backward-fast:before,.fa-fast-backward:before{content:"\f049"}.fa-recycle:before{content:"\f1b8"}.fa-user-astronaut:before{content:"\f4fb"}.fa-plane-slash:before{content:"\e069"}.fa-trademark:before{content:"\f25c"}.fa-basketball-ball:before,.fa-basketball:before{content:"\f434"}.fa-satellite-dish:before{content:"\f7c0"}.fa-arrow-alt-circle-up:before,.fa-circle-up:before{content:"\f35b"}.fa-mobile-alt:before,.fa-mobile-screen-button:before{content:"\f3cd"}.fa-volume-high:before,.fa-volume-up:before{content:"\f028"}.fa-users-rays:before{content:"\e593"}.fa-wallet:before{content:"\f555"}.fa-clipboard-check:before{content:"\f46c"}.fa-file-audio:before{content:"\f1c7"}.fa-burger:before,.fa-hamburger:before{content:"\f805"}.fa-wrench:before{content:"\f0ad"}.fa-bugs:before{content:"\e4d0"}.fa-rupee-sign:before,.fa-rupee:before{content:"\f156"}.fa-file-image:before{content:"\f1c5"}.fa-circle-question:before,.fa-question-circle:before{content:"\f059"}.fa-plane-departure:before{content:"\f5b0"}.fa-handshake-slash:before{content:"\e060"}.fa-book-bookmark:before{content:"\e0bb"}.fa-code-branch:before{content:"\f126"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-bridge:before{content:"\e4c8"}.fa-phone-alt:before,.fa-phone-flip:before{content:"\f879"}.fa-truck-front:before{content:"\e2b7"}.fa-cat:before{content:"\f6be"}.fa-anchor-circle-exclamation:before{content:"\e4ab"}.fa-truck-field:before{content:"\e58d"}.fa-route:before{content:"\f4d7"}.fa-clipboard-question:before{content:"\e4e3"}.fa-panorama:before{content:"\e209"}.fa-comment-medical:before{content:"\f7f5"}.fa-teeth-open:before{content:"\f62f"}.fa-file-circle-minus:before{content:"\e4ed"}.fa-tags:before{content:"\f02c"}.fa-wine-glass:before{content:"\f4e3"}.fa-fast-forward:before,.fa-forward-fast:before{content:"\f050"}.fa-face-meh-blank:before,.fa-meh-blank:before{content:"\f5a4"}.fa-parking:before,.fa-square-parking:before{content:"\f540"}.fa-house-signal:before{content:"\e012"}.fa-bars-progress:before,.fa-tasks-alt:before{content:"\f828"}.fa-faucet-drip:before{content:"\e006"}.fa-cart-flatbed:before,.fa-dolly-flatbed:before{content:"\f474"}.fa-ban-smoking:before,.fa-smoking-ban:before{content:"\f54d"}.fa-terminal:before{content:"\f120"}.fa-mobile-button:before{content:"\f10b"}.fa-house-medical-flag:before{content:"\e514"}.fa-basket-shopping:before,.fa-shopping-basket:before{content:"\f291"}.fa-tape:before{content:"\f4db"}.fa-bus-alt:before,.fa-bus-simple:before{content:"\f55e"}.fa-eye:before{content:"\f06e"}.fa-face-sad-cry:before,.fa-sad-cry:before{content:"\f5b3"}.fa-audio-description:before{content:"\f29e"}.fa-person-military-to-person:before{content:"\e54c"}.fa-file-shield:before{content:"\e4f0"}.fa-user-slash:before{content:"\f506"}.fa-pen:before{content:"\f304"}.fa-tower-observation:before{content:"\e586"}.fa-file-code:before{content:"\f1c9"}.fa-signal-5:before,.fa-signal-perfect:before,.fa-signal:before{content:"\f012"}.fa-bus:before{content:"\f207"}.fa-heart-circle-xmark:before{content:"\e501"}.fa-home-lg:before,.fa-house-chimney:before{content:"\e3af"}.fa-window-maximize:before{content:"\f2d0"}.fa-face-frown:before,.fa-frown:before{content:"\f119"}.fa-prescription:before{content:"\f5b1"}.fa-shop:before,.fa-store-alt:before{content:"\f54f"}.fa-floppy-disk:before,.fa-save:before{content:"\f0c7"}.fa-vihara:before{content:"\f6a7"}.fa-balance-scale-left:before,.fa-scale-unbalanced:before{content:"\f515"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-comment-dots:before,.fa-commenting:before{content:"\f4ad"}.fa-plant-wilt:before{content:"\e5aa"}.fa-diamond:before{content:"\f219"}.fa-face-grin-squint:before,.fa-grin-squint:before{content:"\f585"}.fa-hand-holding-dollar:before,.fa-hand-holding-usd:before{content:"\f4c0"}.fa-bacterium:before{content:"\e05a"}.fa-hand-pointer:before{content:"\f25a"}.fa-drum-steelpan:before{content:"\f56a"}.fa-hand-scissors:before{content:"\f257"}.fa-hands-praying:before,.fa-praying-hands:before{content:"\f684"}.fa-arrow-right-rotate:before,.fa-arrow-rotate-forward:before,.fa-arrow-rotate-right:before,.fa-redo:before{content:"\f01e"}.fa-biohazard:before{content:"\f780"}.fa-location-crosshairs:before,.fa-location:before{content:"\f601"}.fa-mars-double:before{content:"\f227"}.fa-child-dress:before{content:"\e59c"}.fa-users-between-lines:before{content:"\e591"}.fa-lungs-virus:before{content:"\e067"}.fa-face-grin-tears:before,.fa-grin-tears:before{content:"\f588"}.fa-phone:before{content:"\f095"}.fa-calendar-times:before,.fa-calendar-xmark:before{content:"\f273"}.fa-child-reaching:before{content:"\e59d"}.fa-head-side-virus:before{content:"\e064"}.fa-user-cog:before,.fa-user-gear:before{content:"\f4fe"}.fa-arrow-up-1-9:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-door-closed:before{content:"\f52a"}.fa-shield-virus:before{content:"\e06c"}.fa-dice-six:before{content:"\f526"}.fa-mosquito-net:before{content:"\e52c"}.fa-bridge-water:before{content:"\e4ce"}.fa-person-booth:before{content:"\f756"}.fa-text-width:before{content:"\f035"}.fa-hat-wizard:before{content:"\f6e8"}.fa-pen-fancy:before{content:"\f5ac"}.fa-digging:before,.fa-person-digging:before{content:"\f85e"}.fa-trash:before{content:"\f1f8"}.fa-gauge-simple-med:before,.fa-gauge-simple:before,.fa-tachometer-average:before{content:"\f629"}.fa-book-medical:before{content:"\f7e6"}.fa-poo:before{content:"\f2fe"}.fa-quote-right-alt:before,.fa-quote-right:before{content:"\f10e"}.fa-shirt:before,.fa-t-shirt:before,.fa-tshirt:before{content:"\f553"}.fa-cubes:before{content:"\f1b3"}.fa-divide:before{content:"\f529"}.fa-tenge-sign:before,.fa-tenge:before{content:"\f7d7"}.fa-headphones:before{content:"\f025"}.fa-hands-holding:before{content:"\f4c2"}.fa-hands-clapping:before{content:"\e1a8"}.fa-republican:before{content:"\f75e"}.fa-arrow-left:before{content:"\f060"}.fa-person-circle-xmark:before{content:"\e543"}.fa-ruler:before{content:"\f545"}.fa-align-left:before{content:"\f036"}.fa-dice-d6:before{content:"\f6d1"}.fa-restroom:before{content:"\f7bd"}.fa-j:before{content:"\4a"}.fa-users-viewfinder:before{content:"\e595"}.fa-file-video:before{content:"\f1c8"}.fa-external-link-alt:before,.fa-up-right-from-square:before{content:"\f35d"}.fa-table-cells:before,.fa-th:before{content:"\f00a"}.fa-file-pdf:before{content:"\f1c1"}.fa-bible:before,.fa-book-bible:before{content:"\f647"}.fa-o:before{content:"\4f"}.fa-medkit:before,.fa-suitcase-medical:before{content:"\f0fa"}.fa-user-secret:before{content:"\f21b"}.fa-otter:before{content:"\f700"}.fa-female:before,.fa-person-dress:before{content:"\f182"}.fa-comment-dollar:before{content:"\f651"}.fa-briefcase-clock:before,.fa-business-time:before{content:"\f64a"}.fa-table-cells-large:before,.fa-th-large:before{content:"\f009"}.fa-book-tanakh:before,.fa-tanakh:before{content:"\f827"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-clipboard-user:before{content:"\f7f3"}.fa-child:before{content:"\f1ae"}.fa-lira-sign:before{content:"\f195"}.fa-satellite:before{content:"\f7bf"}.fa-plane-lock:before{content:"\e558"}.fa-tag:before{content:"\f02b"}.fa-comment:before{content:"\f075"}.fa-birthday-cake:before,.fa-cake-candles:before,.fa-cake:before{content:"\f1fd"}.fa-envelope:before{content:"\f0e0"}.fa-angle-double-up:before,.fa-angles-up:before{content:"\f102"}.fa-paperclip:before{content:"\f0c6"}.fa-arrow-right-to-city:before{content:"\e4b3"}.fa-ribbon:before{content:"\f4d6"}.fa-lungs:before{content:"\f604"}.fa-arrow-up-9-1:before,.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-litecoin-sign:before{content:"\e1d3"}.fa-border-none:before{content:"\f850"}.fa-circle-nodes:before{content:"\e4e2"}.fa-parachute-box:before{content:"\f4cd"}.fa-indent:before{content:"\f03c"}.fa-truck-field-un:before{content:"\e58e"}.fa-hourglass-empty:before,.fa-hourglass:before{content:"\f254"}.fa-mountain:before{content:"\f6fc"}.fa-user-doctor:before,.fa-user-md:before{content:"\f0f0"}.fa-circle-info:before,.fa-info-circle:before{content:"\f05a"}.fa-cloud-meatball:before{content:"\f73b"}.fa-camera-alt:before,.fa-camera:before{content:"\f030"}.fa-square-virus:before{content:"\e578"}.fa-meteor:before{content:"\f753"}.fa-car-on:before{content:"\e4dd"}.fa-sleigh:before{content:"\f7cc"}.fa-arrow-down-1-9:before,.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-hand-holding-droplet:before,.fa-hand-holding-water:before{content:"\f4c1"}.fa-water:before{content:"\f773"}.fa-calendar-check:before{content:"\f274"}.fa-braille:before{content:"\f2a1"}.fa-prescription-bottle-alt:before,.fa-prescription-bottle-medical:before{content:"\f486"}.fa-landmark:before{content:"\f66f"}.fa-truck:before{content:"\f0d1"}.fa-crosshairs:before{content:"\f05b"}.fa-person-cane:before{content:"\e53c"}.fa-tent:before{content:"\e57d"}.fa-vest-patches:before{content:"\e086"}.fa-check-double:before{content:"\f560"}.fa-arrow-down-a-z:before,.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-money-bill-wheat:before{content:"\e52a"}.fa-cookie:before{content:"\f563"}.fa-arrow-left-rotate:before,.fa-arrow-rotate-back:before,.fa-arrow-rotate-backward:before,.fa-arrow-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-hard-drive:before,.fa-hdd:before{content:"\f0a0"}.fa-face-grin-squint-tears:before,.fa-grin-squint-tears:before{content:"\f586"}.fa-dumbbell:before{content:"\f44b"}.fa-list-alt:before,.fa-rectangle-list:before{content:"\f022"}.fa-tarp-droplet:before{content:"\e57c"}.fa-house-medical-circle-check:before{content:"\e511"}.fa-person-skiing-nordic:before,.fa-skiing-nordic:before{content:"\f7ca"}.fa-calendar-plus:before{content:"\f271"}.fa-plane-arrival:before{content:"\f5af"}.fa-arrow-alt-circle-left:before,.fa-circle-left:before{content:"\f359"}.fa-subway:before,.fa-train-subway:before{content:"\f239"}.fa-chart-gantt:before{content:"\e0e4"}.fa-indian-rupee-sign:before,.fa-indian-rupee:before,.fa-inr:before{content:"\e1bc"}.fa-crop-alt:before,.fa-crop-simple:before{content:"\f565"}.fa-money-bill-1:before,.fa-money-bill-alt:before{content:"\f3d1"}.fa-left-long:before,.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-dna:before{content:"\f471"}.fa-virus-slash:before{content:"\e075"}.fa-minus:before,.fa-subtract:before{content:"\f068"}.fa-chess:before{content:"\f439"}.fa-arrow-left-long:before,.fa-long-arrow-left:before{content:"\f177"}.fa-plug-circle-check:before{content:"\e55c"}.fa-street-view:before{content:"\f21d"}.fa-franc-sign:before{content:"\e18f"}.fa-volume-off:before{content:"\f026"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before,.fa-hands-american-sign-language-interpreting:before,.fa-hands-asl-interpreting:before{content:"\f2a3"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-droplet-slash:before,.fa-tint-slash:before{content:"\f5c7"}.fa-mosque:before{content:"\f678"}.fa-mosquito:before{content:"\e52b"}.fa-star-of-david:before{content:"\f69a"}.fa-person-military-rifle:before{content:"\e54b"}.fa-cart-shopping:before,.fa-shopping-cart:before{content:"\f07a"}.fa-vials:before{content:"\f493"}.fa-plug-circle-plus:before{content:"\e55f"}.fa-place-of-worship:before{content:"\f67f"}.fa-grip-vertical:before{content:"\f58e"}.fa-arrow-turn-up:before,.fa-level-up:before{content:"\f148"}.fa-u:before{content:"\55"}.fa-square-root-alt:before,.fa-square-root-variable:before{content:"\f698"}.fa-clock-four:before,.fa-clock:before{content:"\f017"}.fa-backward-step:before,.fa-step-backward:before{content:"\f048"}.fa-pallet:before{content:"\f482"}.fa-faucet:before{content:"\e005"}.fa-baseball-bat-ball:before{content:"\f432"}.fa-s:before{content:"\53"}.fa-timeline:before{content:"\e29c"}.fa-keyboard:before{content:"\f11c"}.fa-caret-down:before{content:"\f0d7"}.fa-clinic-medical:before,.fa-house-chimney-medical:before{content:"\f7f2"}.fa-temperature-3:before,.fa-temperature-three-quarters:before,.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-mobile-android-alt:before,.fa-mobile-screen:before{content:"\f3cf"}.fa-plane-up:before{content:"\e22d"}.fa-piggy-bank:before{content:"\f4d3"}.fa-battery-3:before,.fa-battery-half:before{content:"\f242"}.fa-mountain-city:before{content:"\e52e"}.fa-coins:before{content:"\f51e"}.fa-khanda:before{content:"\f66d"}.fa-sliders-h:before,.fa-sliders:before{content:"\f1de"}.fa-folder-tree:before{content:"\f802"}.fa-network-wired:before{content:"\f6ff"}.fa-map-pin:before{content:"\f276"}.fa-hamsa:before{content:"\f665"}.fa-cent-sign:before{content:"\e3f5"}.fa-flask:before{content:"\f0c3"}.fa-person-pregnant:before{content:"\e31e"}.fa-wand-sparkles:before{content:"\f72b"}.fa-ellipsis-v:before,.fa-ellipsis-vertical:before{content:"\f142"}.fa-ticket:before{content:"\f145"}.fa-power-off:before{content:"\f011"}.fa-long-arrow-alt-right:before,.fa-right-long:before{content:"\f30b"}.fa-flag-usa:before{content:"\f74d"}.fa-laptop-file:before{content:"\e51d"}.fa-teletype:before,.fa-tty:before{content:"\f1e4"}.fa-diagram-next:before{content:"\e476"}.fa-person-rifle:before{content:"\e54e"}.fa-house-medical-circle-exclamation:before{content:"\e512"}.fa-closed-captioning:before{content:"\f20a"}.fa-hiking:before,.fa-person-hiking:before{content:"\f6ec"}.fa-venus-double:before{content:"\f226"}.fa-images:before{content:"\f302"}.fa-calculator:before{content:"\f1ec"}.fa-people-pulling:before{content:"\e535"}.fa-n:before{content:"\4e"}.fa-cable-car:before,.fa-tram:before{content:"\f7da"}.fa-cloud-rain:before{content:"\f73d"}.fa-building-circle-xmark:before{content:"\e4d4"}.fa-ship:before{content:"\f21a"}.fa-arrows-down-to-line:before{content:"\e4b8"}.fa-download:before{content:"\f019"}.fa-face-grin:before,.fa-grin:before{content:"\f580"}.fa-backspace:before,.fa-delete-left:before{content:"\f55a"}.fa-eye-dropper-empty:before,.fa-eye-dropper:before,.fa-eyedropper:before{content:"\f1fb"}.fa-file-circle-check:before{content:"\e5a0"}.fa-forward:before{content:"\f04e"}.fa-mobile-android:before,.fa-mobile-phone:before,.fa-mobile:before{content:"\f3ce"}.fa-face-meh:before,.fa-meh:before{content:"\f11a"}.fa-align-center:before{content:"\f037"}.fa-book-dead:before,.fa-book-skull:before{content:"\f6b7"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-heart-circle-exclamation:before{content:"\e4fe"}.fa-home-alt:before,.fa-home-lg-alt:before,.fa-home:before,.fa-house:before{content:"\f015"}.fa-calendar-week:before{content:"\f784"}.fa-laptop-medical:before{content:"\f812"}.fa-b:before{content:"\42"}.fa-file-medical:before{content:"\f477"}.fa-dice-one:before{content:"\f525"}.fa-kiwi-bird:before{content:"\f535"}.fa-arrow-right-arrow-left:before,.fa-exchange:before{content:"\f0ec"}.fa-redo-alt:before,.fa-rotate-forward:before,.fa-rotate-right:before{content:"\f2f9"}.fa-cutlery:before,.fa-utensils:before{content:"\f2e7"}.fa-arrow-up-wide-short:before,.fa-sort-amount-up:before{content:"\f161"}.fa-mill-sign:before{content:"\e1ed"}.fa-bowl-rice:before{content:"\e2eb"}.fa-skull:before{content:"\f54c"}.fa-broadcast-tower:before,.fa-tower-broadcast:before{content:"\f519"}.fa-truck-pickup:before{content:"\f63c"}.fa-long-arrow-alt-up:before,.fa-up-long:before{content:"\f30c"}.fa-stop:before{content:"\f04d"}.fa-code-merge:before{content:"\f387"}.fa-upload:before{content:"\f093"}.fa-hurricane:before{content:"\f751"}.fa-mound:before{content:"\e52d"}.fa-toilet-portable:before{content:"\e583"}.fa-compact-disc:before{content:"\f51f"}.fa-file-arrow-down:before,.fa-file-download:before{content:"\f56d"}.fa-caravan:before{content:"\f8ff"}.fa-shield-cat:before{content:"\e572"}.fa-bolt:before,.fa-zap:before{content:"\f0e7"}.fa-glass-water:before{content:"\e4f4"}.fa-oil-well:before{content:"\e532"}.fa-vault:before{content:"\e2c5"}.fa-mars:before{content:"\f222"}.fa-toilet:before{content:"\f7d8"}.fa-plane-circle-xmark:before{content:"\e557"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen-sign:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble-sign:before,.fa-ruble:before{content:"\f158"}.fa-sun:before{content:"\f185"}.fa-guitar:before{content:"\f7a6"}.fa-face-laugh-wink:before,.fa-laugh-wink:before{content:"\f59c"}.fa-horse-head:before{content:"\f7ab"}.fa-bore-hole:before{content:"\e4c3"}.fa-industry:before{content:"\f275"}.fa-arrow-alt-circle-down:before,.fa-circle-down:before{content:"\f358"}.fa-arrows-turn-to-dots:before{content:"\e4c1"}.fa-florin-sign:before{content:"\e184"}.fa-arrow-down-short-wide:before,.fa-sort-amount-desc:before,.fa-sort-amount-down-alt:before{content:"\f884"}.fa-less-than:before{content:"\3c"}.fa-angle-down:before{content:"\f107"}.fa-car-tunnel:before{content:"\e4de"}.fa-head-side-cough:before{content:"\e061"}.fa-grip-lines:before{content:"\f7a4"}.fa-thumbs-down:before{content:"\f165"}.fa-user-lock:before{content:"\f502"}.fa-arrow-right-long:before,.fa-long-arrow-right:before{content:"\f178"}.fa-anchor-circle-xmark:before{content:"\e4ac"}.fa-ellipsis-h:before,.fa-ellipsis:before{content:"\f141"}.fa-chess-pawn:before{content:"\f443"}.fa-first-aid:before,.fa-kit-medical:before{content:"\f479"}.fa-person-through-window:before{content:"\e5a9"}.fa-toolbox:before{content:"\f552"}.fa-hands-holding-circle:before{content:"\e4fb"}.fa-bug:before{content:"\f188"}.fa-credit-card-alt:before,.fa-credit-card:before{content:"\f09d"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-hand-holding-hand:before{content:"\e4f7"}.fa-book-open-reader:before,.fa-book-reader:before{content:"\f5da"}.fa-mountain-sun:before{content:"\e52f"}.fa-arrows-left-right-to-line:before{content:"\e4ba"}.fa-dice-d20:before{content:"\f6cf"}.fa-truck-droplet:before{content:"\e58c"}.fa-file-circle-xmark:before{content:"\e5a1"}.fa-temperature-arrow-up:before,.fa-temperature-up:before{content:"\e040"}.fa-medal:before{content:"\f5a2"}.fa-bed:before{content:"\f236"}.fa-h-square:before,.fa-square-h:before{content:"\f0fd"}.fa-podcast:before{content:"\f2ce"}.fa-temperature-4:before,.fa-temperature-full:before,.fa-thermometer-4:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-bell:before{content:"\f0f3"}.fa-superscript:before{content:"\f12b"}.fa-plug-circle-xmark:before{content:"\e560"}.fa-star-of-life:before{content:"\f621"}.fa-phone-slash:before{content:"\f3dd"}.fa-paint-roller:before{content:"\f5aa"}.fa-hands-helping:before,.fa-handshake-angle:before{content:"\f4c4"}.fa-location-dot:before,.fa-map-marker-alt:before{content:"\f3c5"}.fa-file:before{content:"\f15b"}.fa-greater-than:before{content:"\3e"}.fa-person-swimming:before,.fa-swimmer:before{content:"\f5c4"}.fa-arrow-down:before{content:"\f063"}.fa-droplet:before,.fa-tint:before{content:"\f043"}.fa-eraser:before{content:"\f12d"}.fa-earth-america:before,.fa-earth-americas:before,.fa-earth:before,.fa-globe-americas:before{content:"\f57d"}.fa-person-burst:before{content:"\e53b"}.fa-dove:before{content:"\f4ba"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-socks:before{content:"\f696"}.fa-inbox:before{content:"\f01c"}.fa-section:before{content:"\e447"}.fa-gauge-high:before,.fa-tachometer-alt-fast:before,.fa-tachometer-alt:before{content:"\f625"}.fa-envelope-open-text:before{content:"\f658"}.fa-hospital-alt:before,.fa-hospital-wide:before,.fa-hospital:before{content:"\f0f8"}.fa-wine-bottle:before{content:"\f72f"}.fa-chess-rook:before{content:"\f447"}.fa-bars-staggered:before,.fa-reorder:before,.fa-stream:before{content:"\f550"}.fa-dharmachakra:before{content:"\f655"}.fa-hotdog:before{content:"\f80f"}.fa-blind:before,.fa-person-walking-with-cane:before{content:"\f29d"}.fa-drum:before{content:"\f569"}.fa-ice-cream:before{content:"\f810"}.fa-heart-circle-bolt:before{content:"\e4fc"}.fa-fax:before{content:"\f1ac"}.fa-paragraph:before{content:"\f1dd"}.fa-check-to-slot:before,.fa-vote-yea:before{content:"\f772"}.fa-star-half:before{content:"\f089"}.fa-boxes-alt:before,.fa-boxes-stacked:before,.fa-boxes:before{content:"\f468"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-assistive-listening-systems:before,.fa-ear-listen:before{content:"\f2a2"}.fa-tree-city:before{content:"\e587"}.fa-play:before{content:"\f04b"}.fa-font:before{content:"\f031"}.fa-rupiah-sign:before{content:"\e23d"}.fa-magnifying-glass:before,.fa-search:before{content:"\f002"}.fa-ping-pong-paddle-ball:before,.fa-table-tennis-paddle-ball:before,.fa-table-tennis:before{content:"\f45d"}.fa-diagnoses:before,.fa-person-dots-from-line:before{content:"\f470"}.fa-trash-can-arrow-up:before,.fa-trash-restore-alt:before{content:"\f82a"}.fa-naira-sign:before{content:"\e1f6"}.fa-cart-arrow-down:before{content:"\f218"}.fa-walkie-talkie:before{content:"\f8ef"}.fa-file-edit:before,.fa-file-pen:before{content:"\f31c"}.fa-receipt:before{content:"\f543"}.fa-pen-square:before,.fa-pencil-square:before,.fa-square-pen:before{content:"\f14b"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-person-circle-exclamation:before{content:"\e53f"}.fa-chevron-down:before{content:"\f078"}.fa-battery-5:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-skull-crossbones:before{content:"\f714"}.fa-code-compare:before{content:"\e13a"}.fa-list-dots:before,.fa-list-ul:before{content:"\f0ca"}.fa-school-lock:before{content:"\e56f"}.fa-tower-cell:before{content:"\e585"}.fa-down-long:before,.fa-long-arrow-alt-down:before{content:"\f309"}.fa-ranking-star:before{content:"\e561"}.fa-chess-king:before{content:"\f43f"}.fa-person-harassing:before{content:"\e549"}.fa-brazilian-real-sign:before{content:"\e46c"}.fa-landmark-alt:before,.fa-landmark-dome:before{content:"\f752"}.fa-arrow-up:before{content:"\f062"}.fa-television:before,.fa-tv-alt:before,.fa-tv:before{content:"\f26c"}.fa-shrimp:before{content:"\e448"}.fa-list-check:before,.fa-tasks:before{content:"\f0ae"}.fa-jug-detergent:before{content:"\e519"}.fa-circle-user:before,.fa-user-circle:before{content:"\f2bd"}.fa-user-shield:before{content:"\f505"}.fa-wind:before{content:"\f72e"}.fa-car-burst:before,.fa-car-crash:before{content:"\f5e1"}.fa-y:before{content:"\59"}.fa-person-snowboarding:before,.fa-snowboarding:before{content:"\f7ce"}.fa-shipping-fast:before,.fa-truck-fast:before{content:"\f48b"}.fa-fish:before{content:"\f578"}.fa-user-graduate:before{content:"\f501"}.fa-adjust:before,.fa-circle-half-stroke:before{content:"\f042"}.fa-clapperboard:before{content:"\e131"}.fa-circle-radiation:before,.fa-radiation-alt:before{content:"\f7ba"}.fa-baseball-ball:before,.fa-baseball:before{content:"\f433"}.fa-jet-fighter-up:before{content:"\e518"}.fa-diagram-project:before,.fa-project-diagram:before{content:"\f542"}.fa-copy:before{content:"\f0c5"}.fa-volume-mute:before,.fa-volume-times:before,.fa-volume-xmark:before{content:"\f6a9"}.fa-hand-sparkles:before{content:"\e05d"}.fa-grip-horizontal:before,.fa-grip:before{content:"\f58d"}.fa-share-from-square:before,.fa-share-square:before{content:"\f14d"}.fa-child-combatant:before,.fa-child-rifle:before{content:"\e4e0"}.fa-gun:before{content:"\e19b"}.fa-phone-square:before,.fa-square-phone:before{content:"\f098"}.fa-add:before,.fa-plus:before{content:"\2b"}.fa-expand:before{content:"\f065"}.fa-computer:before{content:"\e4e5"}.fa-close:before,.fa-multiply:before,.fa-remove:before,.fa-times:before,.fa-xmark:before{content:"\f00d"}.fa-arrows-up-down-left-right:before,.fa-arrows:before{content:"\f047"}.fa-chalkboard-teacher:before,.fa-chalkboard-user:before{content:"\f51c"}.fa-peso-sign:before{content:"\e222"}.fa-building-shield:before{content:"\e4d8"}.fa-baby:before{content:"\f77c"}.fa-users-line:before{content:"\e592"}.fa-quote-left-alt:before,.fa-quote-left:before{content:"\f10d"}.fa-tractor:before{content:"\f722"}.fa-trash-arrow-up:before,.fa-trash-restore:before{content:"\f829"}.fa-arrow-down-up-lock:before{content:"\e4b0"}.fa-lines-leaning:before{content:"\e51e"}.fa-ruler-combined:before{content:"\f546"}.fa-copyright:before{content:"\f1f9"}.fa-equals:before{content:"\3d"}.fa-blender:before{content:"\f517"}.fa-teeth:before{content:"\f62e"}.fa-ils:before,.fa-shekel-sign:before,.fa-shekel:before,.fa-sheqel-sign:before,.fa-sheqel:before{content:"\f20b"}.fa-map:before{content:"\f279"}.fa-rocket:before{content:"\f135"}.fa-photo-film:before,.fa-photo-video:before{content:"\f87c"}.fa-folder-minus:before{content:"\f65d"}.fa-store:before{content:"\f54e"}.fa-arrow-trend-up:before{content:"\e098"}.fa-plug-circle-minus:before{content:"\e55e"}.fa-sign-hanging:before,.fa-sign:before{content:"\f4d9"}.fa-bezier-curve:before{content:"\f55b"}.fa-bell-slash:before{content:"\f1f6"}.fa-tablet-android:before,.fa-tablet:before{content:"\f3fb"}.fa-school-flag:before{content:"\e56e"}.fa-fill:before{content:"\f575"}.fa-angle-up:before{content:"\f106"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-holly-berry:before{content:"\f7aa"}.fa-chevron-left:before{content:"\f053"}.fa-bacteria:before{content:"\e059"}.fa-hand-lizard:before{content:"\f258"}.fa-notdef:before{content:"\e1fe"}.fa-disease:before{content:"\f7fa"}.fa-briefcase-medical:before{content:"\f469"}.fa-genderless:before{content:"\f22d"}.fa-chevron-right:before{content:"\f054"}.fa-retweet:before{content:"\f079"}.fa-car-alt:before,.fa-car-rear:before{content:"\f5de"}.fa-pump-soap:before{content:"\e06b"}.fa-video-slash:before{content:"\f4e2"}.fa-battery-2:before,.fa-battery-quarter:before{content:"\f243"}.fa-radio:before{content:"\f8d7"}.fa-baby-carriage:before,.fa-carriage-baby:before{content:"\f77d"}.fa-traffic-light:before{content:"\f637"}.fa-thermometer:before{content:"\f491"}.fa-vr-cardboard:before{content:"\f729"}.fa-hand-middle-finger:before{content:"\f806"}.fa-percent:before,.fa-percentage:before{content:"\25"}.fa-truck-moving:before{content:"\f4df"}.fa-glass-water-droplet:before{content:"\e4f5"}.fa-display:before{content:"\e163"}.fa-face-smile:before,.fa-smile:before{content:"\f118"}.fa-thumb-tack:before,.fa-thumbtack:before{content:"\f08d"}.fa-trophy:before{content:"\f091"}.fa-person-praying:before,.fa-pray:before{content:"\f683"}.fa-hammer:before{content:"\f6e3"}.fa-hand-peace:before{content:"\f25b"}.fa-rotate:before,.fa-sync-alt:before{content:"\f2f1"}.fa-spinner:before{content:"\f110"}.fa-robot:before{content:"\f544"}.fa-peace:before{content:"\f67c"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-warehouse:before{content:"\f494"}.fa-arrow-up-right-dots:before{content:"\e4b7"}.fa-splotch:before{content:"\f5bc"}.fa-face-grin-hearts:before,.fa-grin-hearts:before{content:"\f584"}.fa-dice-four:before{content:"\f524"}.fa-sim-card:before{content:"\f7c4"}.fa-transgender-alt:before,.fa-transgender:before{content:"\f225"}.fa-mercury:before{content:"\f223"}.fa-arrow-turn-down:before,.fa-level-down:before{content:"\f149"}.fa-person-falling-burst:before{content:"\e547"}.fa-award:before{content:"\f559"}.fa-ticket-alt:before,.fa-ticket-simple:before{content:"\f3ff"}.fa-building:before{content:"\f1ad"}.fa-angle-double-left:before,.fa-angles-left:before{content:"\f100"}.fa-qrcode:before{content:"\f029"}.fa-clock-rotate-left:before,.fa-history:before{content:"\f1da"}.fa-face-grin-beam-sweat:before,.fa-grin-beam-sweat:before{content:"\f583"}.fa-arrow-right-from-file:before,.fa-file-export:before{content:"\f56e"}.fa-shield-blank:before,.fa-shield:before{content:"\f132"}.fa-arrow-up-short-wide:before,.fa-sort-amount-up-alt:before{content:"\f885"}.fa-house-medical:before{content:"\e3b2"}.fa-golf-ball-tee:before,.fa-golf-ball:before{content:"\f450"}.fa-chevron-circle-left:before,.fa-circle-chevron-left:before{content:"\f137"}.fa-house-chimney-window:before{content:"\e00d"}.fa-pen-nib:before{content:"\f5ad"}.fa-tent-arrow-turn-left:before{content:"\e580"}.fa-tents:before{content:"\e582"}.fa-magic:before,.fa-wand-magic:before{content:"\f0d0"}.fa-dog:before{content:"\f6d3"}.fa-carrot:before{content:"\f787"}.fa-moon:before{content:"\f186"}.fa-wine-glass-alt:before,.fa-wine-glass-empty:before{content:"\f5ce"}.fa-cheese:before{content:"\f7ef"}.fa-yin-yang:before{content:"\f6ad"}.fa-music:before{content:"\f001"}.fa-code-commit:before{content:"\f386"}.fa-temperature-low:before{content:"\f76b"}.fa-biking:before,.fa-person-biking:before{content:"\f84a"}.fa-broom:before{content:"\f51a"}.fa-shield-heart:before{content:"\e574"}.fa-gopuram:before{content:"\f664"}.fa-earth-oceania:before,.fa-globe-oceania:before{content:"\e47b"}.fa-square-xmark:before,.fa-times-square:before,.fa-xmark-square:before{content:"\f2d3"}.fa-hashtag:before{content:"\23"}.fa-expand-alt:before,.fa-up-right-and-down-left-from-center:before{content:"\f424"}.fa-oil-can:before{content:"\f613"}.fa-t:before{content:"\54"}.fa-hippo:before{content:"\f6ed"}.fa-chart-column:before{content:"\e0e3"}.fa-infinity:before{content:"\f534"}.fa-vial-circle-check:before{content:"\e596"}.fa-person-arrow-down-to-line:before{content:"\e538"}.fa-voicemail:before{content:"\f897"}.fa-fan:before{content:"\f863"}.fa-person-walking-luggage:before{content:"\e554"}.fa-arrows-alt-v:before,.fa-up-down:before{content:"\f338"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-calendar:before{content:"\f133"}.fa-trailer:before{content:"\e041"}.fa-bahai:before,.fa-haykal:before{content:"\f666"}.fa-sd-card:before{content:"\f7c2"}.fa-dragon:before{content:"\f6d5"}.fa-shoe-prints:before{content:"\f54b"}.fa-circle-plus:before,.fa-plus-circle:before{content:"\f055"}.fa-face-grin-tongue-wink:before,.fa-grin-tongue-wink:before{content:"\f58b"}.fa-hand-holding:before{content:"\f4bd"}.fa-plug-circle-exclamation:before{content:"\e55d"}.fa-chain-broken:before,.fa-chain-slash:before,.fa-link-slash:before,.fa-unlink:before{content:"\f127"}.fa-clone:before{content:"\f24d"}.fa-person-walking-arrow-loop-left:before{content:"\e551"}.fa-arrow-up-z-a:before,.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-fire-alt:before,.fa-fire-flame-curved:before{content:"\f7e4"}.fa-tornado:before{content:"\f76f"}.fa-file-circle-plus:before{content:"\e494"}.fa-book-quran:before,.fa-quran:before{content:"\f687"}.fa-anchor:before{content:"\f13d"}.fa-border-all:before{content:"\f84c"}.fa-angry:before,.fa-face-angry:before{content:"\f556"}.fa-cookie-bite:before{content:"\f564"}.fa-arrow-trend-down:before{content:"\e097"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-draw-polygon:before{content:"\f5ee"}.fa-balance-scale:before,.fa-scale-balanced:before{content:"\f24e"}.fa-gauge-simple-high:before,.fa-tachometer-fast:before,.fa-tachometer:before{content:"\f62a"}.fa-shower:before{content:"\f2cc"}.fa-desktop-alt:before,.fa-desktop:before{content:"\f390"}.fa-m:before{content:"\4d"}.fa-table-list:before,.fa-th-list:before{content:"\f00b"}.fa-comment-sms:before,.fa-sms:before{content:"\f7cd"}.fa-book:before{content:"\f02d"}.fa-user-plus:before{content:"\f234"}.fa-check:before{content:"\f00c"}.fa-battery-4:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-house-circle-check:before{content:"\e509"}.fa-angle-left:before{content:"\f104"}.fa-diagram-successor:before{content:"\e47a"}.fa-truck-arrow-right:before{content:"\e58b"}.fa-arrows-split-up-and-left:before{content:"\e4bc"}.fa-fist-raised:before,.fa-hand-fist:before{content:"\f6de"}.fa-cloud-moon:before{content:"\f6c3"}.fa-briefcase:before{content:"\f0b1"}.fa-person-falling:before{content:"\e546"}.fa-image-portrait:before,.fa-portrait:before{content:"\f3e0"}.fa-user-tag:before{content:"\f507"}.fa-rug:before{content:"\e569"}.fa-earth-europe:before,.fa-globe-europe:before{content:"\f7a2"}.fa-cart-flatbed-suitcase:before,.fa-luggage-cart:before{content:"\f59d"}.fa-rectangle-times:before,.fa-rectangle-xmark:before,.fa-times-rectangle:before,.fa-window-close:before{content:"\f410"}.fa-baht-sign:before{content:"\e0ac"}.fa-book-open:before{content:"\f518"}.fa-book-journal-whills:before,.fa-journal-whills:before{content:"\f66a"}.fa-handcuffs:before{content:"\e4f8"}.fa-exclamation-triangle:before,.fa-triangle-exclamation:before,.fa-warning:before{content:"\f071"}.fa-database:before{content:"\f1c0"}.fa-arrow-turn-right:before,.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-bottle-droplet:before{content:"\e4c4"}.fa-mask-face:before{content:"\e1d7"}.fa-hill-rockslide:before{content:"\e508"}.fa-exchange-alt:before,.fa-right-left:before{content:"\f362"}.fa-paper-plane:before{content:"\f1d8"}.fa-road-circle-exclamation:before{content:"\e565"}.fa-dungeon:before{content:"\f6d9"}.fa-align-right:before{content:"\f038"}.fa-money-bill-1-wave:before,.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-life-ring:before{content:"\f1cd"}.fa-hands:before,.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-calendar-day:before{content:"\f783"}.fa-ladder-water:before,.fa-swimming-pool:before,.fa-water-ladder:before{content:"\f5c5"}.fa-arrows-up-down:before,.fa-arrows-v:before{content:"\f07d"}.fa-face-grimace:before,.fa-grimace:before{content:"\f57f"}.fa-wheelchair-alt:before,.fa-wheelchair-move:before{content:"\e2ce"}.fa-level-down-alt:before,.fa-turn-down:before{content:"\f3be"}.fa-person-walking-arrow-right:before{content:"\e552"}.fa-envelope-square:before,.fa-square-envelope:before{content:"\f199"}.fa-dice:before{content:"\f522"}.fa-bowling-ball:before{content:"\f436"}.fa-brain:before{content:"\f5dc"}.fa-band-aid:before,.fa-bandage:before{content:"\f462"}.fa-calendar-minus:before{content:"\f272"}.fa-circle-xmark:before,.fa-times-circle:before,.fa-xmark-circle:before{content:"\f057"}.fa-gifts:before{content:"\f79c"}.fa-hotel:before{content:"\f594"}.fa-earth-asia:before,.fa-globe-asia:before{content:"\f57e"}.fa-id-card-alt:before,.fa-id-card-clip:before{content:"\f47f"}.fa-magnifying-glass-plus:before,.fa-search-plus:before{content:"\f00e"}.fa-thumbs-up:before{content:"\f164"}.fa-user-clock:before{content:"\f4fd"}.fa-allergies:before,.fa-hand-dots:before{content:"\f461"}.fa-file-invoice:before{content:"\f570"}.fa-window-minimize:before{content:"\f2d1"}.fa-coffee:before,.fa-mug-saucer:before{content:"\f0f4"}.fa-brush:before{content:"\f55d"}.fa-mask:before{content:"\f6fa"}.fa-magnifying-glass-minus:before,.fa-search-minus:before{content:"\f010"}.fa-ruler-vertical:before{content:"\f548"}.fa-user-alt:before,.fa-user-large:before{content:"\f406"}.fa-train-tram:before{content:"\e5b4"}.fa-user-nurse:before{content:"\f82f"}.fa-syringe:before{content:"\f48e"}.fa-cloud-sun:before{content:"\f6c4"}.fa-stopwatch-20:before{content:"\e06f"}.fa-square-full:before{content:"\f45c"}.fa-magnet:before{content:"\f076"}.fa-jar:before{content:"\e516"}.fa-note-sticky:before,.fa-sticky-note:before{content:"\f249"}.fa-bug-slash:before{content:"\e490"}.fa-arrow-up-from-water-pump:before{content:"\e4b6"}.fa-bone:before{content:"\f5d7"}.fa-user-injured:before{content:"\f728"}.fa-face-sad-tear:before,.fa-sad-tear:before{content:"\f5b4"}.fa-plane:before{content:"\f072"}.fa-tent-arrows-down:before{content:"\e581"}.fa-exclamation:before{content:"\21"}.fa-arrows-spin:before{content:"\e4bb"}.fa-print:before{content:"\f02f"}.fa-try:before,.fa-turkish-lira-sign:before,.fa-turkish-lira:before{content:"\e2bb"}.fa-dollar-sign:before,.fa-dollar:before,.fa-usd:before{content:"\24"}.fa-x:before{content:"\58"}.fa-magnifying-glass-dollar:before,.fa-search-dollar:before{content:"\f688"}.fa-users-cog:before,.fa-users-gear:before{content:"\f509"}.fa-person-military-pointing:before{content:"\e54a"}.fa-bank:before,.fa-building-columns:before,.fa-institution:before,.fa-museum:before,.fa-university:before{content:"\f19c"}.fa-umbrella:before{content:"\f0e9"}.fa-trowel:before{content:"\e589"}.fa-d:before{content:"\44"}.fa-stapler:before{content:"\e5af"}.fa-masks-theater:before,.fa-theater-masks:before{content:"\f630"}.fa-kip-sign:before{content:"\e1c4"}.fa-hand-point-left:before{content:"\f0a5"}.fa-handshake-alt:before,.fa-handshake-simple:before{content:"\f4c6"}.fa-fighter-jet:before,.fa-jet-fighter:before{content:"\f0fb"}.fa-share-alt-square:before,.fa-square-share-nodes:before{content:"\f1e1"}.fa-barcode:before{content:"\f02a"}.fa-plus-minus:before{content:"\e43c"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-person-circle-check:before{content:"\e53e"}.fa-level-up-alt:before,.fa-turn-up:before{content:"\f3bf"}.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host,:root{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-weight:400}.fa-monero:before{content:"\f3d0"}.fa-hooli:before{content:"\f427"}.fa-yelp:before{content:"\f1e9"}.fa-cc-visa:before{content:"\f1f0"}.fa-lastfm:before{content:"\f202"}.fa-shopware:before{content:"\f5b5"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-aws:before{content:"\f375"}.fa-redhat:before{content:"\f7bc"}.fa-yoast:before{content:"\f2b1"}.fa-cloudflare:before{content:"\e07d"}.fa-ups:before{content:"\f7e0"}.fa-wpexplorer:before{content:"\f2de"}.fa-dyalog:before{content:"\f399"}.fa-bity:before{content:"\f37a"}.fa-stackpath:before{content:"\f842"}.fa-buysellads:before{content:"\f20d"}.fa-first-order:before{content:"\f2b0"}.fa-modx:before{content:"\f285"}.fa-guilded:before{content:"\e07e"}.fa-vnv:before{content:"\f40b"}.fa-js-square:before,.fa-square-js:before{content:"\f3b9"}.fa-microsoft:before{content:"\f3ca"}.fa-qq:before{content:"\f1d6"}.fa-orcid:before{content:"\f8d2"}.fa-java:before{content:"\f4e4"}.fa-invision:before{content:"\f7b0"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-centercode:before{content:"\f380"}.fa-glide-g:before{content:"\f2a6"}.fa-drupal:before{content:"\f1a9"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-unity:before{content:"\e049"}.fa-whmcs:before{content:"\f40d"}.fa-rocketchat:before{content:"\f3e8"}.fa-vk:before{content:"\f189"}.fa-untappd:before{content:"\f405"}.fa-mailchimp:before{content:"\f59e"}.fa-css3-alt:before{content:"\f38b"}.fa-reddit-square:before,.fa-square-reddit:before{content:"\f1a2"}.fa-vimeo-v:before{content:"\f27d"}.fa-contao:before{content:"\f26d"}.fa-square-font-awesome:before{content:"\e5ad"}.fa-deskpro:before{content:"\f38f"}.fa-sistrix:before{content:"\f3ee"}.fa-instagram-square:before,.fa-square-instagram:before{content:"\e055"}.fa-battle-net:before{content:"\f835"}.fa-the-red-yeti:before{content:"\f69d"}.fa-hacker-news-square:before,.fa-square-hacker-news:before{content:"\f3af"}.fa-edge:before{content:"\f282"}.fa-napster:before{content:"\f3d2"}.fa-snapchat-square:before,.fa-square-snapchat:before{content:"\f2ad"}.fa-google-plus-g:before{content:"\f0d5"}.fa-artstation:before{content:"\f77a"}.fa-markdown:before{content:"\f60f"}.fa-sourcetree:before{content:"\f7d3"}.fa-google-plus:before{content:"\f2b3"}.fa-diaspora:before{content:"\f791"}.fa-foursquare:before{content:"\f180"}.fa-stack-overflow:before{content:"\f16c"}.fa-github-alt:before{content:"\f113"}.fa-phoenix-squadron:before{content:"\f511"}.fa-pagelines:before{content:"\f18c"}.fa-algolia:before{content:"\f36c"}.fa-red-river:before{content:"\f3e3"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-safari:before{content:"\f267"}.fa-google:before{content:"\f1a0"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-atlassian:before{content:"\f77b"}.fa-linkedin-in:before{content:"\f0e1"}.fa-digital-ocean:before{content:"\f391"}.fa-nimblr:before{content:"\f5a8"}.fa-chromecast:before{content:"\f838"}.fa-evernote:before{content:"\f839"}.fa-hacker-news:before{content:"\f1d4"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-adversal:before{content:"\f36a"}.fa-creative-commons:before{content:"\f25e"}.fa-watchman-monitoring:before{content:"\e087"}.fa-fonticons:before{content:"\f280"}.fa-weixin:before{content:"\f1d7"}.fa-shirtsinbulk:before{content:"\f214"}.fa-codepen:before{content:"\f1cb"}.fa-git-alt:before{content:"\f841"}.fa-lyft:before{content:"\f3c3"}.fa-rev:before{content:"\f5b2"}.fa-windows:before{content:"\f17a"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-square-viadeo:before,.fa-viadeo-square:before{content:"\f2aa"}.fa-meetup:before{content:"\f2e0"}.fa-centos:before{content:"\f789"}.fa-adn:before{content:"\f170"}.fa-cloudsmith:before{content:"\f384"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-dribbble-square:before,.fa-square-dribbble:before{content:"\f397"}.fa-codiepie:before{content:"\f284"}.fa-node:before{content:"\f419"}.fa-mix:before{content:"\f3cb"}.fa-steam:before{content:"\f1b6"}.fa-cc-apple-pay:before{content:"\f416"}.fa-scribd:before{content:"\f28a"}.fa-openid:before{content:"\f19b"}.fa-instalod:before{content:"\e081"}.fa-expeditedssl:before{content:"\f23e"}.fa-sellcast:before{content:"\f2da"}.fa-square-twitter:before,.fa-twitter-square:before{content:"\f081"}.fa-r-project:before{content:"\f4f7"}.fa-delicious:before{content:"\f1a5"}.fa-freebsd:before{content:"\f3a4"}.fa-vuejs:before{content:"\f41f"}.fa-accusoft:before{content:"\f369"}.fa-ioxhost:before{content:"\f208"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-app-store:before{content:"\f36f"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-itunes-note:before{content:"\f3b5"}.fa-golang:before{content:"\e40f"}.fa-kickstarter:before{content:"\f3bb"}.fa-grav:before{content:"\f2d6"}.fa-weibo:before{content:"\f18a"}.fa-uncharted:before{content:"\e084"}.fa-firstdraft:before{content:"\f3a1"}.fa-square-youtube:before,.fa-youtube-square:before{content:"\f431"}.fa-wikipedia-w:before{content:"\f266"}.fa-rendact:before,.fa-wpressr:before{content:"\f3e4"}.fa-angellist:before{content:"\f209"}.fa-galactic-republic:before{content:"\f50c"}.fa-nfc-directional:before{content:"\e530"}.fa-skype:before{content:"\f17e"}.fa-joget:before{content:"\f3b7"}.fa-fedora:before{content:"\f798"}.fa-stripe-s:before{content:"\f42a"}.fa-meta:before{content:"\e49b"}.fa-laravel:before{content:"\f3bd"}.fa-hotjar:before{content:"\f3b1"}.fa-bluetooth-b:before{content:"\f294"}.fa-sticker-mule:before{content:"\f3f7"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-hips:before{content:"\f452"}.fa-behance:before{content:"\f1b4"}.fa-reddit:before{content:"\f1a1"}.fa-discord:before{content:"\f392"}.fa-chrome:before{content:"\f268"}.fa-app-store-ios:before{content:"\f370"}.fa-cc-discover:before{content:"\f1f2"}.fa-wpbeginner:before{content:"\f297"}.fa-confluence:before{content:"\f78d"}.fa-mdb:before{content:"\f8ca"}.fa-dochub:before{content:"\f394"}.fa-accessible-icon:before{content:"\f368"}.fa-ebay:before{content:"\f4f4"}.fa-amazon:before{content:"\f270"}.fa-unsplash:before{content:"\e07c"}.fa-yarn:before{content:"\f7e3"}.fa-square-steam:before,.fa-steam-square:before{content:"\f1b7"}.fa-500px:before{content:"\f26e"}.fa-square-vimeo:before,.fa-vimeo-square:before{content:"\f194"}.fa-asymmetrik:before{content:"\f372"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-gratipay:before{content:"\f184"}.fa-apple:before{content:"\f179"}.fa-hive:before{content:"\e07f"}.fa-gitkraken:before{content:"\f3a6"}.fa-keybase:before{content:"\f4f5"}.fa-apple-pay:before{content:"\f415"}.fa-padlet:before{content:"\e4a0"}.fa-amazon-pay:before{content:"\f42c"}.fa-github-square:before,.fa-square-github:before{content:"\f092"}.fa-stumbleupon:before{content:"\f1a4"}.fa-fedex:before{content:"\f797"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-shopify:before{content:"\e057"}.fa-neos:before{content:"\f612"}.fa-hackerrank:before{content:"\f5f7"}.fa-researchgate:before{content:"\f4f8"}.fa-swift:before{content:"\f8e1"}.fa-angular:before{content:"\f420"}.fa-speakap:before{content:"\f3f3"}.fa-angrycreative:before{content:"\f36e"}.fa-y-combinator:before{content:"\f23b"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-gitlab-square:before,.fa-square-gitlab:before{content:"\e5ae"}.fa-studiovinari:before{content:"\f3f8"}.fa-pied-piper:before{content:"\f2ae"}.fa-wordpress:before{content:"\f19a"}.fa-product-hunt:before{content:"\f288"}.fa-firefox:before{content:"\f269"}.fa-linode:before{content:"\f2b8"}.fa-goodreads:before{content:"\f3a8"}.fa-odnoklassniki-square:before,.fa-square-odnoklassniki:before{content:"\f264"}.fa-jsfiddle:before{content:"\f1cc"}.fa-sith:before{content:"\f512"}.fa-themeisle:before{content:"\f2b2"}.fa-page4:before{content:"\f3d7"}.fa-hashnode:before{content:"\e499"}.fa-react:before{content:"\f41b"}.fa-cc-paypal:before{content:"\f1f4"}.fa-squarespace:before{content:"\f5be"}.fa-cc-stripe:before{content:"\f1f5"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-bitcoin:before{content:"\f379"}.fa-keycdn:before{content:"\f3ba"}.fa-opera:before{content:"\f26a"}.fa-itch-io:before{content:"\f83a"}.fa-umbraco:before{content:"\f8e8"}.fa-galactic-senate:before{content:"\f50d"}.fa-ubuntu:before{content:"\f7df"}.fa-draft2digital:before{content:"\f396"}.fa-stripe:before{content:"\f429"}.fa-houzz:before{content:"\f27c"}.fa-gg:before{content:"\f260"}.fa-dhl:before{content:"\f790"}.fa-pinterest-square:before,.fa-square-pinterest:before{content:"\f0d3"}.fa-xing:before{content:"\f168"}.fa-blackberry:before{content:"\f37b"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-playstation:before{content:"\f3df"}.fa-quinscape:before{content:"\f459"}.fa-less:before{content:"\f41d"}.fa-blogger-b:before{content:"\f37d"}.fa-opencart:before{content:"\f23d"}.fa-vine:before{content:"\f1ca"}.fa-paypal:before{content:"\f1ed"}.fa-gitlab:before{content:"\f296"}.fa-typo3:before{content:"\f42b"}.fa-reddit-alien:before{content:"\f281"}.fa-yahoo:before{content:"\f19e"}.fa-dailymotion:before{content:"\e052"}.fa-affiliatetheme:before{content:"\f36b"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-bootstrap:before{content:"\f836"}.fa-odnoklassniki:before{content:"\f263"}.fa-nfc-symbol:before{content:"\e531"}.fa-ethereum:before{content:"\f42e"}.fa-speaker-deck:before{content:"\f83c"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-patreon:before{content:"\f3d9"}.fa-avianex:before{content:"\f374"}.fa-ello:before{content:"\f5f1"}.fa-gofore:before{content:"\f3a7"}.fa-bimobject:before{content:"\f378"}.fa-facebook-f:before{content:"\f39e"}.fa-google-plus-square:before,.fa-square-google-plus:before{content:"\f0d4"}.fa-mandalorian:before{content:"\f50f"}.fa-first-order-alt:before{content:"\f50a"}.fa-osi:before{content:"\f41a"}.fa-google-wallet:before{content:"\f1ee"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-periscope:before{content:"\f3da"}.fa-fulcrum:before{content:"\f50b"}.fa-cloudscale:before{content:"\f383"}.fa-forumbee:before{content:"\f211"}.fa-mizuni:before{content:"\f3cc"}.fa-schlix:before{content:"\f3ea"}.fa-square-xing:before,.fa-xing-square:before{content:"\f169"}.fa-bandcamp:before{content:"\f2d5"}.fa-wpforms:before{content:"\f298"}.fa-cloudversify:before{content:"\f385"}.fa-usps:before{content:"\f7e1"}.fa-megaport:before{content:"\f5a3"}.fa-magento:before{content:"\f3c4"}.fa-spotify:before{content:"\f1bc"}.fa-optin-monster:before{content:"\f23c"}.fa-fly:before{content:"\f417"}.fa-aviato:before{content:"\f421"}.fa-itunes:before{content:"\f3b4"}.fa-cuttlefish:before{content:"\f38c"}.fa-blogger:before{content:"\f37c"}.fa-flickr:before{content:"\f16e"}.fa-viber:before{content:"\f409"}.fa-soundcloud:before{content:"\f1be"}.fa-digg:before{content:"\f1a6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-symfony:before{content:"\f83d"}.fa-maxcdn:before{content:"\f136"}.fa-etsy:before{content:"\f2d7"}.fa-facebook-messenger:before{content:"\f39f"}.fa-audible:before{content:"\f373"}.fa-think-peaks:before{content:"\f731"}.fa-bilibili:before{content:"\e3d9"}.fa-erlang:before{content:"\f39d"}.fa-cotton-bureau:before{content:"\f89e"}.fa-dashcube:before{content:"\f210"}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-stack-exchange:before{content:"\f18d"}.fa-elementor:before{content:"\f430"}.fa-pied-piper-square:before,.fa-square-pied-piper:before{content:"\e01e"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-palfed:before{content:"\f3d8"}.fa-superpowers:before{content:"\f2dd"}.fa-resolving:before{content:"\f3e7"}.fa-xbox:before{content:"\f412"}.fa-searchengin:before{content:"\f3eb"}.fa-tiktok:before{content:"\e07b"}.fa-facebook-square:before,.fa-square-facebook:before{content:"\f082"}.fa-renren:before{content:"\f18b"}.fa-linux:before{content:"\f17c"}.fa-glide:before{content:"\f2a5"}.fa-linkedin:before{content:"\f08c"}.fa-hubspot:before{content:"\f3b2"}.fa-deploydog:before{content:"\f38e"}.fa-twitch:before{content:"\f1e8"}.fa-ravelry:before{content:"\f2d9"}.fa-mixer:before{content:"\e056"}.fa-lastfm-square:before,.fa-square-lastfm:before{content:"\f203"}.fa-vimeo:before{content:"\f40a"}.fa-mendeley:before{content:"\f7b3"}.fa-uniregistry:before{content:"\f404"}.fa-figma:before{content:"\f799"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-dropbox:before{content:"\f16b"}.fa-instagram:before{content:"\f16d"}.fa-cmplid:before{content:"\e360"}.fa-facebook:before{content:"\f09a"}.fa-gripfire:before{content:"\f3ac"}.fa-jedi-order:before{content:"\f50e"}.fa-uikit:before{content:"\f403"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-phabricator:before{content:"\f3db"}.fa-ussunnah:before{content:"\f407"}.fa-earlybirds:before{content:"\f39a"}.fa-trade-federation:before{content:"\f513"}.fa-autoprefixer:before{content:"\f41c"}.fa-whatsapp:before{content:"\f232"}.fa-slideshare:before{content:"\f1e7"}.fa-google-play:before{content:"\f3ab"}.fa-viadeo:before{content:"\f2a9"}.fa-line:before{content:"\f3c0"}.fa-google-drive:before{content:"\f3aa"}.fa-servicestack:before{content:"\f3ec"}.fa-simplybuilt:before{content:"\f215"}.fa-bitbucket:before{content:"\f171"}.fa-imdb:before{content:"\f2d8"}.fa-deezer:before{content:"\e077"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-jira:before{content:"\f7b1"}.fa-docker:before{content:"\f395"}.fa-screenpal:before{content:"\e570"}.fa-bluetooth:before{content:"\f293"}.fa-gitter:before{content:"\f426"}.fa-d-and-d:before{content:"\f38d"}.fa-microblog:before{content:"\e01a"}.fa-cc-diners-club:before{content:"\f24c"}.fa-gg-circle:before{content:"\f261"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-yandex:before{content:"\f413"}.fa-readme:before{content:"\f4d5"}.fa-html5:before{content:"\f13b"}.fa-sellsy:before{content:"\f213"}.fa-sass:before{content:"\f41e"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-buromobelexperte:before{content:"\f37f"}.fa-salesforce:before{content:"\f83b"}.fa-octopus-deploy:before{content:"\e082"}.fa-medapps:before{content:"\f3c6"}.fa-ns8:before{content:"\f3d5"}.fa-pinterest-p:before{content:"\f231"}.fa-apper:before{content:"\f371"}.fa-fort-awesome:before{content:"\f286"}.fa-waze:before{content:"\f83f"}.fa-cc-jcb:before{content:"\f24b"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-rust:before{content:"\e07a"}.fa-wix:before{content:"\f5cf"}.fa-behance-square:before,.fa-square-behance:before{content:"\f1b5"}.fa-supple:before{content:"\f3f9"}.fa-rebel:before{content:"\f1d0"}.fa-css3:before{content:"\f13c"}.fa-staylinked:before{content:"\f3f5"}.fa-kaggle:before{content:"\f5fa"}.fa-space-awesome:before{content:"\e5ac"}.fa-deviantart:before{content:"\f1bd"}.fa-cpanel:before{content:"\f388"}.fa-goodreads-g:before{content:"\f3a9"}.fa-git-square:before,.fa-square-git:before{content:"\f1d2"}.fa-square-tumblr:before,.fa-tumblr-square:before{content:"\f174"}.fa-trello:before{content:"\f181"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-get-pocket:before{content:"\f265"}.fa-perbyte:before{content:"\e083"}.fa-grunt:before{content:"\f3ad"}.fa-weebly:before{content:"\f5cc"}.fa-connectdevelop:before{content:"\f20e"}.fa-leanpub:before{content:"\f212"}.fa-black-tie:before{content:"\f27e"}.fa-themeco:before{content:"\f5c6"}.fa-python:before{content:"\f3e2"}.fa-android:before{content:"\f17b"}.fa-bots:before{content:"\e340"}.fa-free-code-camp:before{content:"\f2c5"}.fa-hornbill:before{content:"\f592"}.fa-js:before{content:"\f3b8"}.fa-ideal:before{content:"\e013"}.fa-git:before{content:"\f1d3"}.fa-dev:before{content:"\f6cc"}.fa-sketch:before{content:"\f7c6"}.fa-yandex-international:before{content:"\f414"}.fa-cc-amex:before{content:"\f1f3"}.fa-uber:before{content:"\f402"}.fa-github:before{content:"\f09b"}.fa-php:before{content:"\f457"}.fa-alipay:before{content:"\f642"}.fa-youtube:before{content:"\f167"}.fa-skyatlas:before{content:"\f216"}.fa-firefox-browser:before{content:"\e007"}.fa-replyd:before{content:"\f3e6"}.fa-suse:before{content:"\f7d6"}.fa-jenkins:before{content:"\f3b6"}.fa-twitter:before{content:"\f099"}.fa-rockrms:before{content:"\f3e9"}.fa-pinterest:before{content:"\f0d2"}.fa-buffer:before{content:"\f837"}.fa-npm:before{content:"\f3d4"}.fa-yammer:before{content:"\f840"}.fa-btc:before{content:"\f15a"}.fa-dribbble:before{content:"\f17d"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-internet-explorer:before{content:"\f26b"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-old-republic:before{content:"\f510"}.fa-square-whatsapp:before,.fa-whatsapp-square:before{content:"\f40c"}.fa-node-js:before{content:"\f3d3"}.fa-edge-legacy:before{content:"\e078"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-medrt:before{content:"\f3c8"}.fa-usb:before{content:"\f287"}.fa-tumblr:before{content:"\f173"}.fa-vaadin:before{content:"\f408"}.fa-quora:before{content:"\f2c4"}.fa-reacteurope:before{content:"\f75d"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-amilia:before{content:"\f36d"}.fa-mixcloud:before{content:"\f289"}.fa-flipboard:before{content:"\f44d"}.fa-viacoin:before{content:"\f237"}.fa-critical-role:before{content:"\f6c9"}.fa-sitrox:before{content:"\e44a"}.fa-discourse:before{content:"\f393"}.fa-joomla:before{content:"\f1aa"}.fa-mastodon:before{content:"\f4f6"}.fa-airbnb:before{content:"\f834"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-buy-n-large:before{content:"\f8a6"}.fa-gulp:before{content:"\f3ae"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-strava:before{content:"\f428"}.fa-ember:before{content:"\f423"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-teamspeak:before{content:"\f4f9"}.fa-pushed:before{content:"\f3e1"}.fa-wordpress-simple:before{content:"\f411"}.fa-nutritionix:before{content:"\f3d6"}.fa-wodu:before{content:"\e088"}.fa-google-pay:before{content:"\e079"}.fa-intercom:before{content:"\f7af"}.fa-zhihu:before{content:"\f63f"}.fa-korvue:before{content:"\f42f"}.fa-pix:before{content:"\e43a"}.fa-steam-symbol:before{content:"\f3f6"}:host,:root{--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a} \ No newline at end of file diff --git a/src/_site/assets/css/style.css b/src/_site/assets/css/style.css new file mode 100644 index 0000000..0bbdf5c --- /dev/null +++ b/src/_site/assets/css/style.css @@ -0,0 +1,3 @@ +*{-webkit-transition:background-color 75ms ease-in, border-color 75ms ease-in;-moz-transition:background-color 75ms ease-in, border-color 75ms ease-in;-ms-transition:background-color 75ms ease-in, border-color 75ms ease-in;-o-transition:background-color 75ms ease-in, border-color 75ms ease-in;transition:background-color 75ms ease-in, border-color 75ms ease-in}.notransition{-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none}html{overflow-x:hidden;width:100%}body,h1,h2,h3,h4,h5,h6,p,blockquote,pre,hr,dl,dd,ol,ul,figure{margin:0;padding:0}body{min-height:100vh;overflow-x:hidden;position:relative;color:#434648;background-color:#fff;font:400 16px/1.85 Roboto,sans-serif;-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;-webkit-font-feature-settings:"kern" 1;-moz-font-feature-settings:"kern" 1;-o-font-feature-settings:"kern" 1;font-feature-settings:"kern" 1;font-kerning:normal;box-sizing:border-box}h1,h2,h3,h4,h5,h6,p,blockquote,pre,ul,ol,dl,figure{margin-top:10px;margin-bottom:10px}strong,b{font-weight:700;color:#0d122b}hr{border-bottom:0;border-style:solid;border-color:#ececec}kbd{-moz-border-radius:3px;-webkit-border-radius:3px;border:1px solid #ececec;border-radius:2px;color:#0d122b;display:inline-block;font-size:14px;line-height:1.4;font-family:Consolas,monospace;margin:0 0.1em;font-weight:700;padding:0.01em 0.4em;text-shadow:0 1px 0 #fff}img{max-width:100%;vertical-align:middle;-webkit-user-drag:none;margin:0 auto;text-align:center}figure{position:relative}figure>img{display:block;position:relative}figcaption{font-size:13px;text-align:center}ul{list-style:none}ul li{display:list-item;text-align:-webkit-match-parent}ul li::before{content:"\FE63";display:inline-block;top:-1px;width:1.2em;position:relative;margin-left:-1.3em;font-weight:700}ol{list-style:none;counter-reset:li}ol li{position:relative;counter-increment:li}ol li::before{content:counter(li);display:inline-block;width:1em;margin-right:0.5em;margin-left:-1.6em;text-align:right;direction:rtl;font-weight:700;font-size:14px}ul,ol{margin-top:0;margin-left:30px}li{padding-bottom:1px;padding-top:1px}li:before{color:#0d122b}li>ul,li>ol{margin-bottom:2px;margin-top:0}h1,h2,h3,h4,h5,h6{color:#0d122b;font-weight:700}h1+ul,h1+ol,h2+ul,h2+ol,h3+ul,h3+ol,h4+ul,h4+ol,h5+ul,h5+ol,h6+ul,h6+ol{margin-top:10px}@media screen and (max-width: 768px){h1,h2,h3,h4,h5,h6{scroll-margin-top:65px}}h1>a,h2>a,h3>a,h4>a,h5>a,h6>a{text-decoration:none;border:none}h1>a:hover,h2>a:hover,h3>a:hover,h4>a:hover,h5>a:hover,h6>a:hover{text-decoration:none;border:none}a{color:inherit;text-decoration-color:#d2c7c7}a:hover{color:#003fff}a:focus{outline:3px solid rgba(0,54,199,0.6);outline-offset:2px}del{color:inherit}em{color:inherit}blockquote{color:#6b7886;font-style:italic;text-align:center;opacity:0.9;border-top:1px solid #ececec;border-bottom:1px solid #ececec;padding:10px;margin-left:10px;margin-right:10px;font-size:1em}blockquote>:last-child{margin-bottom:0;margin-top:0}.wrapper{max-width:-webkit-calc(720px - (30px * 2));max-width:calc(720px - (30px * 2));position:relative;margin-right:auto;margin-left:auto;padding-right:30px;padding-left:30px}@media screen and (max-width: 768px){.wrapper{max-width:-webkit-calc(720px - (30px));max-width:calc(720px - (30px));padding-right:20px;padding-left:20px}.wrapper.blurry{animation:0.2s ease-in forwards blur;-webkit-animation:0.2s ease-in forwards blur}}u{text-decoration-color:#d2c7c7}small{font-size:14px}sup{border-radius:10%;top:-3px;left:2px;font-size:small;position:relative;margin-right:2px}.overflow-table{overflow-x:auto}table{width:100%;margin-top:15px;border-collapse:collapse;font-size:14px}table thead{font-weight:700;color:#0d122b;border-bottom:1px solid #ececec}table th,table td,table tr{border:1px solid #ececec;padding:2px 7px}.post-item:after,.navbar:after,.wrapper:after{content:"";display:table;clear:both}mark,::selection{background:#fffba0;color:#0d122b}.gist table{border:0}.gist table tr,.gist table td{border:0}.navbar{height:auto;max-width:calc(890px - (30px * 2));max-width:-webkit-calc(890px - (30px * 2));position:relative;margin-right:auto;margin-left:auto;border-bottom:1px solid #ececec;padding:15px 30px}.menu{user-select:none;-ms-user-select:none;-webkit-user-select:none}.menu a#mode{float:left;left:8px;top:6px;position:relative;clear:both;-webkit-transform:scale(1, 1);transform:scale(1, 1);opacity:0.7;z-index:1}.menu a#mode:hover{cursor:pointer;opacity:1}.menu a#mode:active{-webkit-transform:scale(0.9, 0.9);transform:scale(0.9, 0.9)}.menu a#mode .mode-moon{display:block}.menu a#mode .mode-moon line{stroke:#0d122b;fill:none}.menu a#mode .mode-moon circle{fill:#0d122b;stroke:#0d122b}.menu a#mode .mode-sunny{display:none}.menu a#mode .mode-sunny line{stroke:#eaeaea;fill:none}.menu a#mode .mode-sunny circle{fill:none;stroke:#eaeaea}.menu .trigger{float:right}.menu .menu-trigger{display:none}.menu .menu-icon{display:none}.menu .menu-link{color:#0d122b;line-height:2.25;text-decoration:none;padding:5px 8px;opacity:0.7;letter-spacing:0.3px}.menu .menu-link:hover{opacity:1}.menu .menu-link:not(:last-child){margin-right:5px}.menu .menu-link.rss{position:relative;bottom:-3px;outline:none}@media screen and (max-width: 768px){.menu .menu-link{opacity:0.8}}.menu .menu-link.active{opacity:1;font-weight:600}@media screen and (max-width: 768px){.menu{position:fixed;top:0;left:0;right:0;z-index:2;text-align:center;height:50px;background-color:#fff;border-bottom:1px solid #ececec}.menu a#mode{left:10px;top:12px}.menu .menu-icon{display:block;position:absolute;right:0;width:50px;height:23px;line-height:0;padding-top:13px;padding-bottom:15px;cursor:pointer;text-align:center;z-index:1}.menu .menu-icon>svg{fill:#0d122b;opacity:0.7}.menu .menu-icon:hover>svg{opacity:1}.menu .menu-icon:active{-webkit-transform:scale(0.9, 0.9);transform:scale(0.9, 0.9)}.menu input[type="checkbox"]:not(:checked)~.trigger{clear:both;visibility:hidden}.menu input[type="checkbox"]:checked~.trigger{position:fixed;animation:0.2s ease-in forwards fadein;-webkit-animation:0.2s ease-in forwards fadein;display:flex;flex-direction:row;justify-content:center;align-items:center;background-color:#fff;height:100vh;width:100%;top:0}.menu .menu-link{display:block;box-sizing:border-box;font-size:1.1em}.menu .menu-link:not(:last-child){margin:0;padding:2px 0}}.author{margin-top:6.3rem;margin-bottom:7.2rem;text-align:center}@media screen and (max-width: 768px){.author{margin-bottom:3em}}.author .author-avatar{width:70px;height:70px;border-radius:100%;user-select:none;-ms-user-select:none;-webkit-user-select:none;-webkit-animation:0.5s ease-in forwards fadein;animation:0.5s ease-in forwards fadein;opacity:1}.author .author-name{font-size:1.7em;margin-bottom:2px}.author .author-bio{margin:0 auto;opacity:0.9;max-width:393px;line-height:1.688}.posts-item-note{font-size:16px;font-weight:700;margin-bottom:5px;color:#0d122b}.post-item{display:flex;padding-top:5px;padding-bottom:6px}.post-item:not(:first-child){border-top:1px solid #ececec}.post-item .post-item-date{min-width:96px;color:#0d122b;font-weight:700;padding-right:10px}@media screen and (max-width: 768px){.post-item .post-item-date{font-size:16px}}.post-item .post-item-title{margin:0;border:0;padding:0;font-size:16px;font-weight:normal;letter-spacing:0.1px}.post-item .post-item-title a{color:#434648}.post-item .post-item-title a:hover,.post-item .post-item-title afocus{color:#0d122b}.footer{margin-top:8em;margin-bottom:2em;text-align:center}@media screen and (max-width: 768px){.footer{margin-top:3em}}.footer span.footer_item{color:#0d122b;opacity:0.8;font-weight:700;font-size:14px}.footer a.footer_item{color:#0d122b;opacity:0.8;text-decoration:none}.footer a.footer_item:not(:last-child){margin-right:10px}.footer a.footer_item:not(:last-child):hover{opacity:1}.footer .footer_copyright{font-size:13px;margin-top:3px;display:block;color:#6b7886;opacity:0.8}.not-found{text-align:center;display:flex;justify-content:center;flex-direction:column;height:75vh}.not-found .title{font-size:5em;font-weight:700;line-height:1.1;color:#0d122b;text-shadow:1px 0px 0px #003fff}.not-found .phrase{color:#434648}.not-found .solution{color:#003fff;letter-spacing:0.5px}.not-found .solution:hover{color:#0036c7}.search-article{position:relative;margin-bottom:50px}.search-article label[for="search-input"]{position:relative;top:10px;left:11px}.search-article input[type="search"]{top:0;left:0;border:0;width:100%;height:40px;outline:none;position:absolute;border-radius:5px;padding:10px 10px 10px 35px;color:#434648;-webkit-appearance:none;font-size:16px;background-color:rgba(128,128,128,0.1);border:1px solid rgba(128,128,128,0.1)}.search-article input[type="search"]::-webkit-input-placeholder{color:#808080}.search-article input[type="search"]::-webkit-search-decoration,.search-article input[type="search"]::-webkit-search-results-decoration{display:none}#search-results{text-align:center}#search-results li{text-align:left}.archive-tags{height:auto}.archive-tags .tag-item{padding:1px 3px;border-radius:2px;border:1px solid rgba(128,128,128,0.1);background-color:rgba(128,128,128,0.1)}@media screen and (max-width: 768px){.wrapper.post{padding-left:15px;padding-right:15px}}.header{margin-top:7.8em;margin-bottom:3em}.header .tags{margin-left:3px;letter-spacing:0.5px}.header .tags .tag{font-weight:700;font-size:12px}.header .tags .tag:hover{text-decoration:none}.header .header-title{font-size:2em;line-height:1.2;margin-top:10px;margin-bottom:20px}.header .header-title.center{text-align:center}@media screen and (max-width: 768px){.header .header-title{font-size:1.9em}}.post-meta{padding-top:3px;line-height:1.3;color:#6b7886}.post-meta time{position:relative;margin-right:1.5em}.post-meta time::after{background:#ececec;bottom:1px;content:" ";height:2px;position:absolute;right:-20px;width:12px}.post-meta span[itemprop="author"]{border-bottom:1px dotted #ececec}.page-content{padding-top:8px}.page-content iframe{text-align:center}.page-content figure img{border-radius:2px}.page-content figure figcaption{margin-top:5px;font-style:italic;font-size:14px}.page-content a{color:#003fff;text-decoration:none}.page-content a[target="_blank"]::after{content:" \2197";font-size:14px;line-height:0;position:relative;bottom:5px;vertical-align:baseline}.page-content a:hover{color:#0036c7}.page-content a:focus{color:#003fff}.page-content>p{margin:0;padding-top:15px;padding-bottom:15px}.page-content ul.task-list{list-style:none;margin:0}.page-content ul.task-list li::before{content:""}.page-content ul.task-list li input[type="checkbox"]{margin-right:10px}.page-content dl dt{font-weight:700}.page-content h1,.page-content h2,.page-content h3,.page-content h4,.page-content h5,.page-content h6{color:#0d122b;font-weight:700;margin-top:30px;margin-bottom:0}.page-content h1:hover .anchor-head,.page-content h2:hover .anchor-head,.page-content h3:hover .anchor-head,.page-content h4:hover .anchor-head,.page-content h5:hover .anchor-head,.page-content h6:hover .anchor-head{color:#003fff;opacity:1}.page-content h1 .anchor-head,.page-content h2 .anchor-head,.page-content h3 .anchor-head,.page-content h4 .anchor-head,.page-content h5 .anchor-head,.page-content h6 .anchor-head{position:relative;opacity:0;outline:none}.page-content h1 .anchor-head::before,.page-content h2 .anchor-head::before,.page-content h3 .anchor-head::before,.page-content h4 .anchor-head::before,.page-content h5 .anchor-head::before,.page-content h6 .anchor-head::before{content:"#";position:absolute;right:-3px;width:1em;font-weight:700}.page-content h1{font-size:24px}.page-content h2{font-size:22px}.page-content h3{font-size:20px;border-bottom:1px solid #ececec;padding-bottom:4px}.page-content h4{font-size:20px}.page-content h5{font-size:16px}.page-content h6{font-size:14px}.post-nav{display:flex;position:relative;margin-top:5em;border-top:1px solid #ececec;line-height:1.4}.post-nav .post-nav-item{border-bottom:0;font-weight:700;padding-bottom:10px;width:50%;padding-top:10px;text-decoration:none;box-sizing:border-box}.post-nav .post-nav-item .post-title{color:#0d122b}.post-nav .post-nav-item:hover .post-title,.post-nav .post-nav-item:focus .post-title{color:#0036c7;opacity:0.9}.post-nav .post-nav-item .nav-arrow{font-weight:400;font-size:14px;color:#6b7886;margin-bottom:3px}.post-nav .post-nav-item:nth-child(odd){padding-left:0;padding-right:20px}.post-nav .post-nav-item:nth-child(even){text-align:right;padding-right:0;padding-left:20px}@media screen and (max-width: 768px){.post-nav{display:block;font-size:14px}.post-nav .post-nav-item{display:block;width:100%}.post-nav .post-nav-item:nth-child(even){border-left:0;padding-left:0;border-top:1px solid #ececec}}.post-updated-at{font-family:"Ubuntu mono", "monospace"}@keyframes fadein{0%{opacity:0.2}100%{opacity:0.8}}@keyframes blur{0%{filter:blur(0px)}100%{filter:blur(4px)}}.embed-responsive{height:0;max-width:100%;overflow:hidden;position:relative;padding-bottom:56.25%;margin-top:20px}.embed-responsive iframe,.embed-responsive object,.embed-responsive embed{top:0;left:0;width:100%;height:100%;position:absolute}code{font-family:Consolas,monospace;text-rendering:optimizeLegibility;font-feature-settings:"calt" 1;font-variant-ligatures:normal;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;font-size:inherit}code.highlighter-rouge{padding:1px 3px;position:relative;top:-1px;background-color:#f6f6f6;border-radius:2px;border:1px solid rgba(128,128,128,0.1)}pre.highlight,pre{margin:0 -27px;border:1px solid rgba(128,128,128,0.1);background-color:#1a1b21;border-radius:2px;padding:10px;display:block;overflow-x:auto}@media screen and (max-width: 768px){pre.highlight,pre{margin:0 calc(51% - 51vw);padding-left:20px}}pre.highlight>code,pre>code{width:100%;max-width:50rem;margin-left:auto;margin-right:auto;line-height:1.5;display:block;border:0}.highlight table td{padding:5px}.highlight table pre{margin:0}.highlight,.highlight .w{color:#fbf1c7}.highlight .err{color:#fb4934;font-weight:bold}.highlight .c,.highlight .cd,.highlight .cm,.highlight .c1,.highlight .cs{color:#928374;font-style:italic}.highlight .cp{color:#8ec07c}.highlight .nt{color:#fb4934}.highlight .o,.highlight .ow{color:#fbf1c7}.highlight .p,.highlight .pi{color:#fbf1c7}.highlight .gi{color:#b8bb26;background-color:#282828}.highlight .gd{color:#fb4934;background-color:#282828}.highlight .gh{color:#b8bb26;font-weight:bold}.highlight .k,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kv{color:#fb4934}.highlight .kc{color:#d3869b}.highlight .kt{color:#fabd2f}.highlight .kd{color:#fe8019}.highlight .s,.highlight .sb,.highlight .sc,.highlight .sd,.highlight .s2,.highlight .sh,.highlight .sx,.highlight .s1{color:#b8bb26;font-style:italic}.highlight .si{color:#b8bb26;font-style:italic}.highlight .sr{color:#b8bb26;font-style:italic}.highlight .se{color:#fe8019}.highlight .nn{color:#8ec07c}.highlight .nc{color:#8ec07c}.highlight .no{color:#d3869b}.highlight .na{color:#b8bb26}.highlight .m,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .il,.highlight .mo,.highlight .mb,.highlight .mx{color:#d3869b}.highlight .ss{color:#83a598}body[data-theme="dark"]{color:#c7bebe;background-color:#131418}body[data-theme="dark"] h1,body[data-theme="dark"] h2,body[data-theme="dark"] h3,body[data-theme="dark"] h4,body[data-theme="dark"] h5,body[data-theme="dark"] h6{color:#eaeaea}body[data-theme="dark"] table thead{color:#eaeaea;border-color:#1b1d25}body[data-theme="dark"] table th,body[data-theme="dark"] table td,body[data-theme="dark"] table tr{border-color:#1b1d25}body[data-theme="dark"] .page-content a{color:#ff5277}body[data-theme="dark"] .page-content a:hover,body[data-theme="dark"] .page-content a:active,body[data-theme="dark"] .page-content a:focus{color:#ff2957}body[data-theme="dark"] .page-content h3{border-color:#1b1d25}body[data-theme="dark"] .page-content h1 .anchor-head,body[data-theme="dark"] .page-content h2 .anchor-head,body[data-theme="dark"] .page-content h3 .anchor-head,body[data-theme="dark"] .page-content h4 .anchor-head,body[data-theme="dark"] .page-content h5 .anchor-head,body[data-theme="dark"] .page-content h6 .anchor-head{color:#ff5277}body[data-theme="dark"] code.highlighter-rouge{background-color:#1b1d25}body[data-theme="dark"] kbd{border-color:#1b1d25;color:#eaeaea;text-shadow:0 1px 0 #131418}body[data-theme="dark"] hr{border-color:#1b1d25}body[data-theme="dark"] .post-meta{color:#767f87}body[data-theme="dark"] .post-meta time::after{background-color:#1b1d25}body[data-theme="dark"] .post-meta span[itemprop="author"]{border-color:#1b1d25}body[data-theme="dark"] a{color:inherit;text-decoration-color:#4a4d56}body[data-theme="dark"] a:hover{color:#ff5277}body[data-theme="dark"] a:focus{outline-color:rgba(255,82,119,0.6)}body[data-theme="dark"] li:before{color:#eaeaea}body[data-theme="dark"] blockquote{color:#767f87;border-color:#1b1d25}body[data-theme="dark"] strong,body[data-theme="dark"] b{color:#eaeaea}body[data-theme="dark"] .navbar{border-color:#1b1d25}body[data-theme="dark"] .navbar .menu a#mode .mode-sunny{display:block}body[data-theme="dark"] .navbar .menu a#mode .mode-moon{display:none}body[data-theme="dark"] .navbar .menu .menu-link{color:#eaeaea}@media screen and (max-width: 768px){body[data-theme="dark"] .navbar .menu{background-color:#131418;border-color:#1b1d25}body[data-theme="dark"] .navbar .menu .menu-icon>svg{fill:#eaeaea}body[data-theme="dark"] .navbar .menu input[type="checkbox"]:checked~.trigger{background:#131418}}body[data-theme="dark"] .post-item:not(:first-child){border-color:#1b1d25}body[data-theme="dark"] .post-item .post-item-date{color:#eaeaea}body[data-theme="dark"] .post-item .post-item-title a{color:#c7bebe}body[data-theme="dark"] .post-item .post-item-title a:hover,body[data-theme="dark"] .post-item .post-item-title afocus{color:#eaeaea}body[data-theme="dark"] .post-nav{border-color:#1b1d25}body[data-theme="dark"] .post-nav .post-nav-item{font-weight:700}body[data-theme="dark"] .post-nav .post-nav-item .post-title{color:#eaeaea;opacity:0.9}body[data-theme="dark"] .post-nav .post-nav-item:hover .post-title,body[data-theme="dark"] .post-nav .post-nav-item:focus .post-title{color:#ff2957}body[data-theme="dark"] .post-nav .post-nav-item .nav-arrow{color:#767f87}@media screen and (max-width: 768px){body[data-theme="dark"] .post-nav .post-nav-item:nth-child(even){border-color:#1b1d25}}body[data-theme="dark"] .footer span.footer_item{color:#eaeaea}body[data-theme="dark"] .footer a.footer_item:not(:last-child){color:#eaeaea}body[data-theme="dark"] .footer .footer_copyright{color:#767f87;opacity:1}body[data-theme="dark"] .not-found .title{color:#eaeaea;text-shadow:1px 0px 0px #ff5277}body[data-theme="dark"] .not-found .phrase{color:#c7bebe}body[data-theme="dark"] .not-found .solution{color:#ff5277}body[data-theme="dark"] .not-found .solution:hover{color:#ff2957}body[data-theme="dark"] .search-article input[type="search"]{color:#c7bebe}body[data-theme="dark"] .search-article input[type="search"]::-webkit-input-placeholder{color:rgba(128,128,128,0.8)} + +/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/src/_site/assets/css/style.css.map b/src/_site/assets/css/style.css.map new file mode 100644 index 0000000..8480762 --- /dev/null +++ b/src/_site/assets/css/style.css.map @@ -0,0 +1,28 @@ +{ + "version": 3, + "file": "style.css", + "sources": [ + "style.scss", + "_sass/main.scss", + "_sass/klise/_fonts.scss", + "_sass/klise/_base.scss", + "_sass/klise/_layout.scss", + "_sass/klise/_post.scss", + "_sass/klise/_miscellaneous.scss", + "_sass/klise/_syntax.scss", + "_sass/klise/_dark.scss" + ], + "sourcesContent": [ + "@import \"main\";\n", + "// Fonts preferences\n$sans-family: Roboto, sans-serif;\n$mono-family: Consolas, monospace;\n$base-font-size: 16px;\n$medium-font-size: $base-font-size * 0.938;\n$small-font-size: $base-font-size * 0.875;\n$base-line-height: 1.85;\n\n// Font weight\n// $light-weight: 300; // uncomment if necessary\n$normal-weight: 400;\n$bold-weight: 700;\n// $black-weight: 900; // uncomment if necessary\n\n//Light Colors\n$text-base-color: #434648;\n$text-link-blue: #003fff;\n$text-link-blue-active: #0036c7;\n\n$black: #0d122b;\n$light: #ececec;\n$smoke: #d2c7c7;\n$gray: #6b7886;\n$white: #fff;\n\n// Dark Colors\n$dark-text-base-color: #c7bebe;\n$dark-text-link-blue: #ff5277;\n$dark-text-link-blue-active: #ff2957;\n\n$dark-black: #131418;\n$dark-white: #eaeaea;\n$dark-light: #1b1d25;\n$dark-smoke: #4a4d56;\n$dark-gray: #767f87;\n\n// Width of the content area\n$wide-size: 890px;\n$narrow-size: 720px;\n\n// Padding unit\n$spacing-full: 30px;\n$spacing-half: $spacing-full / 2;\n\n// State of devices\n$on-mobile: 768px;\n$on-tablet: 769px;\n$on-desktop: 1024px;\n$on-widescreen: 1152px;\n\n@mixin media-query($device) {\n @media screen and (max-width: $device) {\n @content;\n }\n}\n\n@mixin relative-font-size($ratio) {\n font-size: $base-font-size * $ratio;\n}\n\n// Import sass files\n@import \"klise/fonts\", \"klise/base\", \"klise/layout\", \"klise/post\",\n \"klise/miscellaneous\", \"klise/syntax\", \"klise/dark\";\n", + "@charset \"utf-8\";\n", + "// Reset some basic elements\n* {\n -webkit-transition: background-color 75ms ease-in, border-color 75ms ease-in;\n -moz-transition: background-color 75ms ease-in, border-color 75ms ease-in;\n -ms-transition: background-color 75ms ease-in, border-color 75ms ease-in;\n -o-transition: background-color 75ms ease-in, border-color 75ms ease-in;\n transition: background-color 75ms ease-in, border-color 75ms ease-in;\n}\n\n.notransition {\n -webkit-transition: none;\n -moz-transition: none; \n -ms-transition: none;\n -o-transition: none;\n transition: none;\n}\n\nhtml {\n overflow-x: hidden;\n width: 100%;\n}\n\nbody,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote,\npre,\nhr,\ndl,\ndd,\nol,\nul,\nfigure {\n margin: 0;\n padding: 0;\n}\n\n// Basic styling\nbody {\n min-height: 100vh;\n overflow-x: hidden;\n position: relative;\n color: $text-base-color;\n background-color: $white;\n font: $normal-weight #{$base-font-size}/#{$base-line-height} $sans-family;\n -webkit-text-size-adjust: 100%;\n -webkit-font-smoothing: antialiased;\n -webkit-font-feature-settings: \"kern\" 1;\n -moz-font-feature-settings: \"kern\" 1;\n -o-font-feature-settings: \"kern\" 1;\n font-feature-settings: \"kern\" 1;\n font-kerning: normal;\n box-sizing: border-box;\n}\n\n// Set `margin-bottom` to maintain vertical rhythm\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote,\npre,\nul,\nol,\ndl,\nfigure,\n%vertical-rhythm {\n margin-top: $spacing-full - 20;\n margin-bottom: $spacing-full - 20;\n}\n\n// strong | bold\nstrong,\nb {\n font-weight: $bold-weight;\n color: $black;\n}\n\n// horizontal rule\nhr {\n border-bottom: 0;\n border-style: solid;\n border-color: $light;\n}\n\n// kbd tag\nkbd {\n -moz-border-radius: 3px;\n -webkit-border-radius: 3px;\n border: 1px solid $light;\n border-radius: 2px;\n color: $black;\n display: inline-block;\n font-size: $small-font-size;\n line-height: 1.4;\n font-family: $mono-family;\n margin: 0 0.1em;\n font-weight: $bold-weight;\n padding: 0.01em 0.4em;\n text-shadow: 0 1px 0 $white;\n}\n\n// Image\nimg {\n max-width: 100%;\n vertical-align: middle;\n -webkit-user-drag: none;\n margin: 0 auto;\n text-align: center;\n}\n\n// Figure\nfigure {\n position: relative;\n}\n\n// Image inside Figure tag\nfigure > img {\n display: block;\n position: relative;\n}\n\n// Image caption\nfigcaption {\n font-size: 13px;\n text-align: center;\n}\n\n// List\nul {\n list-style: none;\n li {\n display: list-item;\n text-align: -webkit-match-parent;\n }\n li::before {\n content: \"\\FE63\";\n display: inline-block;\n top: -1px;\n width: 1.2em;\n position: relative;\n margin-left: -1.3em;\n font-weight: 700;\n }\n}\n\nol {\n list-style: none;\n counter-reset: li;\n li {\n position: relative;\n counter-increment: li;\n &::before {\n content: counter(li);\n display: inline-block;\n width: 1em;\n margin-right: 0.5em;\n margin-left: -1.6em;\n text-align: right;\n direction: rtl;\n font-weight: $bold-weight;\n font-size: $small-font-size;\n }\n }\n}\n\nul,\nol {\n margin-top: 0;\n margin-left: $spacing-full;\n}\n\nli {\n padding-bottom: 1px;\n padding-top: 1px;\n\n &:before {\n color: $black;\n }\n\n > ul,\n > ol {\n margin-bottom: 2px;\n margin-top: 0;\n }\n}\n\n// Headings\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n color: $black;\n font-weight: $bold-weight;\n & + ul,\n & + ol {\n margin-top: 10px;\n }\n\n @include media-query($on-mobile) {\n scroll-margin-top: 65px;\n }\n}\n\n// Headings with link\nh1 > a,\nh2 > a,\nh3 > a,\nh4 > a,\nh5 > a,\nh6 > a {\n text-decoration: none;\n border: none;\n\n &:hover {\n text-decoration: none;\n border: none;\n }\n}\n\n// Link\na {\n color: inherit;\n text-decoration-color: $smoke;\n\n &:hover {\n color: $text-link-blue;\n }\n\n &:focus {\n outline: 3px solid rgba(0, 54, 199, 0.6);\n outline-offset: 2px;\n }\n}\n\n// Del\ndel {\n color: inherit;\n}\n\n// Em\nem {\n color: inherit;\n}\n\n// Blockquotes\nblockquote {\n color: $gray;\n font-style: italic;\n text-align: center;\n opacity: 0.9;\n border-top: 1px solid $light;\n border-bottom: 1px solid $light;\n padding: 10px;\n margin-left: 10px;\n margin-right: 10px;\n font-size: 1em;\n\n > :last-child {\n margin-bottom: 0;\n margin-top: 0;\n }\n}\n\n// Wrapper\n.wrapper {\n max-width: -webkit-calc(#{$narrow-size} - (#{$spacing-full} * 2));\n max-width: calc(#{$narrow-size} - (#{$spacing-full} * 2));\n position: relative;\n margin-right: auto;\n margin-left: auto;\n padding-right: $spacing-full;\n padding-left: $spacing-full;\n @extend %clearfix;\n\n @include media-query($on-mobile) {\n max-width: -webkit-calc(#{$narrow-size} - (#{$spacing-full}));\n max-width: calc(#{$narrow-size} - (#{$spacing-full}));\n padding-right: $spacing-full - 10;\n padding-left: $spacing-full - 10;\n\n &.blurry {\n animation: 0.2s ease-in forwards blur;\n -webkit-animation: 0.2s ease-in forwards blur;\n }\n }\n}\n\n// Underline\nu {\n text-decoration-color: #d2c7c7;\n}\n\n// Small\nsmall {\n font-size: $small-font-size;\n}\n\n// Superscript\nsup {\n border-radius: 10%;\n top: -3px;\n left: 2px;\n font-size: small;\n position: relative;\n margin-right: 2px;\n}\n\n// Table\n.overflow-table {\n overflow-x: auto;\n}\n\ntable {\n width: 100%;\n margin-top: $spacing-half;\n border-collapse: collapse;\n font-size: $small-font-size;\n\n thead {\n font-weight: $bold-weight;\n color: $black;\n border-bottom: 1px solid $light;\n }\n\n th,\n td,\n tr {\n border: 1px solid $light;\n padding: 2px 7px;\n }\n}\n\n// Clearfix\n%clearfix:after {\n content: \"\";\n display: table;\n clear: both;\n}\n\n// When mouse block a text set this color\nmark,\n::selection {\n background: #fffba0;\n color: $black;\n}\n\n// Github Gist clear border\n.gist {\n table {\n border: 0;\n\n tr,\n td {\n border: 0;\n }\n }\n}\n", + "// Navbar\n.navbar {\n height: auto;\n max-width: calc(#{$wide-size} - (#{$spacing-full} * 2));\n max-width: -webkit-calc(#{$wide-size} - (#{$spacing-full} * 2));\n position: relative;\n margin-right: auto;\n margin-left: auto;\n border-bottom: 1px solid $light;\n padding: $spacing-full - 15px $spacing-full;\n @extend %clearfix;\n}\n\n// Navigation\n.menu {\n user-select: none;\n -ms-user-select: none;\n -webkit-user-select: none;\n\n a#mode {\n float: left;\n left: 8px;\n top: 6px;\n position: relative;\n clear: both;\n -webkit-transform: scale(1, 1);\n transform: scale(1, 1);\n opacity: 0.7;\n z-index: 1;\n &:hover {\n cursor: pointer;\n opacity: 1;\n }\n &:active {\n -webkit-transform: scale(0.9, 0.9);\n transform: scale(0.9, 0.9);\n }\n .mode-moon {\n display: block;\n line {\n stroke: $black;\n fill: none;\n }\n\n circle {\n fill: $black;\n stroke: $black;\n }\n }\n .mode-sunny {\n display: none;\n line {\n stroke: $dark-white;\n fill: none;\n }\n circle {\n fill: none;\n stroke: $dark-white;\n }\n }\n }\n\n .trigger {\n float: right;\n }\n\n .menu-trigger {\n display: none;\n }\n\n .menu-icon {\n display: none;\n }\n\n .menu-link {\n color: $black;\n line-height: $base-line-height + 0.4;\n text-decoration: none;\n padding: 5px 8px;\n opacity: 0.7;\n letter-spacing: 0.3px;\n\n &:hover {\n opacity: 1;\n }\n\n &:not(:last-child) {\n margin-right: 5px;\n }\n\n &.rss {\n position: relative;\n bottom: -3px;\n outline: none;\n }\n\n @include media-query($on-mobile) {\n opacity: 0.8;\n }\n }\n\n .menu-link.active {\n opacity: 1;\n font-weight: 600;\n }\n\n @include media-query($on-mobile) {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n z-index: 2;\n text-align: center;\n height: 50px;\n background-color: $white;\n border-bottom: 1px solid $light;\n\n a#mode {\n left: 10px;\n top: 12px;\n }\n\n .menu-icon {\n display: block;\n position: absolute;\n right: 0;\n width: 50px;\n height: 23px;\n line-height: 0;\n padding-top: 13px;\n padding-bottom: 15px;\n cursor: pointer;\n text-align: center;\n z-index: 1;\n > svg {\n fill: $black;\n opacity: 0.7;\n }\n &:hover {\n > svg {\n opacity: 1;\n }\n }\n &:active {\n -webkit-transform: scale(0.9, 0.9);\n transform: scale(0.9, 0.9);\n }\n }\n\n input[type=\"checkbox\"]:not(:checked) ~ .trigger {\n clear: both;\n visibility: hidden;\n }\n\n input[type=\"checkbox\"]:checked ~ .trigger {\n position: fixed;\n animation: 0.2s ease-in forwards fadein;\n -webkit-animation: 0.2s ease-in forwards fadein;\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n background-color: $white;\n height: 100vh;\n width: 100%;\n top: 0;\n }\n\n .menu-link {\n display: block;\n box-sizing: border-box;\n font-size: 1.1em;\n\n &:not(:last-child) {\n margin: 0;\n padding: 2px 0;\n }\n }\n }\n}\n\n// Author\n.author {\n margin-top: 6.3rem;\n margin-bottom: 7.2rem;\n text-align: center;\n\n @include media-query($on-mobile) {\n margin-bottom: 3em;\n }\n\n .author-avatar {\n width: 70px;\n height: 70px;\n border-radius: 100%;\n user-select: none;\n // background-color: $black;\n -ms-user-select: none;\n -webkit-user-select: none;\n -webkit-animation: 0.5s ease-in forwards fadein;\n animation: 0.5s ease-in forwards fadein;\n opacity: 1;\n }\n\n .author-name {\n font-size: 1.7em;\n margin-bottom: 2px;\n }\n\n .author-bio {\n margin: 0 auto;\n opacity: 0.9;\n max-width: 393px;\n line-height: 1.688;\n }\n}\n\n// Content\n.posts-item-note {\n font-size: $base-font-size;\n font-weight: 700;\n margin-bottom: 5px;\n color: $black;\n}\n\n// List of posts\n.post-item {\n display: flex;\n padding-top: 5px;\n padding-bottom: 6px;\n @extend %clearfix;\n\n &:not(:first-child) {\n border-top: 1px solid $light;\n }\n\n .post-item-date {\n min-width: 96px;\n color: $black;\n font-weight: 700;\n padding-right: 10px;\n\n @include media-query($on-mobile) {\n font-size: 16px;\n }\n }\n\n .post-item-title {\n margin: 0;\n border: 0;\n padding: 0;\n font-size: $base-font-size;\n font-weight: normal;\n letter-spacing: 0.1px;\n\n a {\n color: $text-base-color;\n\n &:hover,\n &focus {\n color: $black;\n }\n }\n }\n}\n\n// Footer\n.footer {\n margin-top: 8em;\n margin-bottom: 2em;\n text-align: center;\n\n @include media-query($on-mobile) {\n margin-top: 3em;\n }\n span.footer_item {\n color: $black;\n opacity: 0.8;\n font-weight: $bold-weight;\n font-size: $small-font-size;\n }\n a.footer_item {\n color: $black;\n opacity: 0.8;\n text-decoration: none;\n\n &:not(:last-child) {\n margin-right: 10px;\n &:hover {\n opacity: 1;\n }\n }\n }\n\n .footer_copyright {\n font-size: $small-font-size - 1;\n margin-top: 3px;\n display: block;\n color: $gray;\n opacity: 0.8;\n }\n}\n\n.not-found {\n text-align: center;\n display: flex;\n justify-content: center;\n flex-direction: column;\n height: 75vh;\n .title {\n font-size: 5em;\n font-weight: $bold-weight;\n line-height: 1.1;\n color: $black;\n text-shadow: 1px 0px 0px $text-link-blue;\n }\n .phrase {\n color: $text-base-color;\n }\n .solution {\n color: $text-link-blue;\n letter-spacing: 0.5px;\n }\n .solution:hover {\n color: $text-link-blue-active;\n }\n}\n\n\n.search-article {\n position: relative;\n margin-bottom: 50px;\n\n label[for=\"search-input\"] {\n position: relative;\n top: 10px;\n left: 11px;\n }\n\n input[type=\"search\"] {\n top: 0;\n left: 0;\n border: 0;\n width: 100%;\n height: 40px;\n outline: none;\n position: absolute;\n border-radius: 5px;\n padding: 10px 10px 10px 35px;\n color: $text-base-color;\n -webkit-appearance: none;\n font-size: $base-font-size;\n background-color: rgba(128, 128, 128, 0.1);\n border: 1px solid rgba(128, 128, 128, 0.1);\n &::-webkit-input-placeholder {\n color: #808080;\n }\n &::-webkit-search-decoration,\n &::-webkit-search-results-decoration {\n display: none;\n }\n }\n}\n\n#search-results {\n text-align: center;\n li {\n text-align: left;\n }\n}\n\n.archive-tags {\n height: auto;\n .tag-item {\n padding: 1px 3px;\n border-radius: 2px;\n border: 1px solid rgba(128, 128, 128, 0.1);\n background-color: rgba(128, 128, 128, 0.1);\n }\n}\n", + "// Post wrapper\n.wrapper.post {\n @include media-query($on-mobile) {\n padding-left: $spacing-half;\n padding-right: $spacing-half;\n }\n}\n\n// Post title\n.header {\n margin-top: 7.8em;\n margin-bottom: 3em;\n\n .tags {\n margin-left: 3px;\n letter-spacing: 0.5px;\n\n .tag {\n font-weight: $bold-weight;\n font-size: $small-font-size - 2;\n\n &:hover {\n text-decoration: none;\n }\n }\n }\n\n .header-title {\n font-size: 2em;\n line-height: 1.2;\n margin-top: 10px;\n margin-bottom: 20px;\n\n &.center {\n text-align: center;\n }\n\n @include media-query($on-mobile) {\n font-size: 1.9em;\n }\n }\n}\n\n// Post meta\n.post-meta {\n padding-top: 3px;\n line-height: 1.3;\n color: $gray;\n\n time {\n position: relative;\n margin-right: 1.5em;\n\n &::after {\n background: $light;\n bottom: 1px;\n content: \" \";\n height: 2px;\n position: absolute;\n right: -20px;\n width: 12px;\n }\n }\n\n span[itemprop=\"author\"] {\n border-bottom: 1px dotted $light;\n }\n}\n\n// Post content\n.page-content {\n padding-top: 8px;\n\n iframe {\n text-align: center;\n }\n\n figure {\n img {\n border-radius: 2px;\n }\n\n figcaption {\n margin-top: 5px;\n font-style: italic;\n font-size: $small-font-size;\n }\n }\n\n a {\n color: $text-link-blue;\n text-decoration: none;\n &[target=\"_blank\"]::after {\n content: \" \\2197\";\n font-size: $small-font-size;\n line-height: 0;\n position: relative;\n bottom: 5px;\n vertical-align: baseline;\n }\n\n &:hover {\n color: $text-link-blue-active;\n }\n\n &:focus {\n color: $text-link-blue;\n }\n }\n\n > p {\n margin: 0;\n padding-top: $spacing-full - 15;\n padding-bottom: $spacing-full - 15;\n }\n\n ul.task-list {\n list-style: none; \n margin: 0;\n\n li::before {\n content: \"\";\n }\n\n li input[type=\"checkbox\"] {\n margin-right: 10px;\n }\n }\n\n dl dt {\n font-weight: $bold-weight;\n }\n\n h1,\n h2,\n h3,\n h4,\n h5,\n h6 {\n color: $black;\n font-weight: $bold-weight;\n margin-top: $spacing-full;\n margin-bottom: 0;\n\n &:hover {\n .anchor-head {\n color: $text-link-blue;\n opacity: 1;\n }\n }\n\n .anchor-head {\n position: relative;\n opacity: 0;\n outline: none;\n\n &::before {\n content: \"#\";\n position: absolute;\n right: -3px;\n width: 1em;\n font-weight: $bold-weight;\n }\n }\n }\n\n h1 {\n @include relative-font-size(1.5);\n }\n\n h2 {\n @include relative-font-size(1.375);\n }\n\n h3 {\n @include relative-font-size(1.25);\n border-bottom: 1px solid $light;\n padding-bottom: 4px;\n }\n\n h4 {\n @include relative-font-size(1.25);\n }\n\n h5 {\n @include relative-font-size(1);\n }\n\n h6 {\n @include relative-font-size(0.875);\n }\n}\n\n.post-nav {\n display: flex;\n position: relative;\n margin-top: 5em;\n border-top: 1px solid $light;\n line-height: 1.4;\n\n .post-nav-item {\n border-bottom: 0;\n font-weight: $bold-weight;\n padding-bottom: 10px;\n\n .post-title {\n color: $black;\n }\n\n &:hover,\n &:focus {\n .post-title {\n color: $text-link-blue-active;\n opacity: 0.9;\n }\n }\n\n .nav-arrow {\n font-weight: $normal-weight;\n font-size: $small-font-size;\n color: $gray;\n margin-bottom: 3px;\n }\n\n width: 50%;\n padding-top: 10px;\n text-decoration: none;\n box-sizing: border-box;\n\n &:nth-child(odd) {\n padding-left: 0;\n padding-right: 20px;\n }\n\n &:nth-child(even) {\n text-align: right;\n padding-right: 0;\n padding-left: 20px;\n }\n }\n\n @include media-query($on-mobile) {\n display: block;\n font-size: $small-font-size;\n\n .post-nav-item {\n display: block;\n width: 100%;\n }\n\n .post-nav-item:nth-child(even) {\n border-left: 0;\n padding-left: 0;\n border-top: 1px solid $light;\n }\n }\n}\n\n.post-updated-at {\n font-family: \"Ubuntu mono\", \"monospace\";\n}\n", + "// Animation fade-in\n@keyframes fadein {\n 0% {\n opacity: 0.2;\n }\n\n 100% {\n opacity: 0.8;\n }\n}\n\n// Animation blur\n@keyframes blur {\n 0% {\n filter: blur(0px);\n }\n\n 100% {\n filter: blur(4px);\n }\n}\n\n// Responsive embed video\n.embed-responsive {\n height: 0;\n max-width: 100%;\n overflow: hidden;\n position: relative;\n padding-bottom: 56.25%;\n margin-top: 20px;\n\n iframe,\n object,\n embed {\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n position: absolute;\n }\n}\n", + "// Code\ncode {\n font-family: $mono-family;\n text-rendering: optimizeLegibility;\n font-feature-settings: \"calt\" 1;\n font-variant-ligatures: normal;\n white-space: pre;\n word-spacing: normal;\n word-break: normal;\n word-wrap: normal;\n font-size: inherit;\n\n &.highlighter-rouge {\n padding: 1px 3px;\n position: relative;\n top: -1px;\n background-color: #f6f6f6;\n border-radius: 2px;\n border: 1px solid rgba(128,128,128,0.1);\n }\n}\n\n// Codeblock Theme\npre.highlight, pre {\n margin: 0 -27px;\n @include media-query($on-mobile) {\n margin: 0 calc(51% - 51vw);\n padding-left: 20px;\n }\n border: 1px solid rgba(128,128,128,0.1);\n background-color: #1a1b21;\n border-radius: 2px;\n padding: 10px;\n display: block;\n overflow-x: auto;\n\n > code {\n width: 100%;\n max-width: 50rem;\n margin-left: auto;\n margin-right: auto;\n line-height: 1.5;\n display: block;\n border: 0;\n }\n}\n\n.highlight table td {\n padding: 5px;\n}\n\n.highlight table pre {\n margin: 0;\n}\n\n.highlight,\n.highlight .w {\n color: #fbf1c7;\n // background-color: #1a1b21;\n}\n\n.highlight .err {\n color: #fb4934;\n // background-color: #1a1b21;\n font-weight: bold;\n}\n\n.highlight .c,\n.highlight .cd,\n.highlight .cm,\n.highlight .c1,\n.highlight .cs {\n color: #928374;\n font-style: italic;\n}\n\n.highlight .cp {\n color: #8ec07c;\n}\n\n.highlight .nt {\n color: #fb4934;\n}\n\n.highlight .o,\n.highlight .ow {\n color: #fbf1c7;\n}\n\n.highlight .p,\n.highlight .pi {\n color: #fbf1c7;\n}\n\n.highlight .gi {\n color: #b8bb26;\n background-color: #282828;\n}\n\n.highlight .gd {\n color: #fb4934;\n background-color: #282828;\n}\n\n.highlight .gh {\n color: #b8bb26;\n font-weight: bold;\n}\n\n.highlight .k,\n.highlight .kn,\n.highlight .kp,\n.highlight .kr,\n.highlight .kv {\n color: #fb4934;\n}\n\n.highlight .kc {\n color: #d3869b;\n}\n\n.highlight .kt {\n color: #fabd2f;\n}\n\n.highlight .kd {\n color: #fe8019;\n}\n\n.highlight .s,\n.highlight .sb,\n.highlight .sc,\n.highlight .sd,\n.highlight .s2,\n.highlight .sh,\n.highlight .sx,\n.highlight .s1 {\n color: #b8bb26;\n font-style: italic;\n}\n\n.highlight .si {\n color: #b8bb26;\n font-style: italic;\n}\n\n.highlight .sr {\n color: #b8bb26;\n font-style: italic;\n}\n\n.highlight .se {\n color: #fe8019;\n}\n\n.highlight .nn {\n color: #8ec07c;\n}\n\n.highlight .nc {\n color: #8ec07c;\n}\n\n.highlight .no {\n color: #d3869b;\n}\n\n.highlight .na {\n color: #b8bb26;\n}\n\n.highlight .m,\n.highlight .mf,\n.highlight .mh,\n.highlight .mi,\n.highlight .il,\n.highlight .mo,\n.highlight .mb,\n.highlight .mx {\n color: #d3869b;\n}\n\n.highlight .ss {\n color: #83a598;\n}\n", + "body[data-theme=\"dark\"] {\n color: $dark-text-base-color;\n background-color: $dark-black;\n\n // Heading\n h1,\n h2,\n h3,\n h4,\n h5,\n h6 {\n color: $dark-white;\n }\n\n // Table\n table {\n thead {\n color: $dark-white;\n border-color: $dark-light;\n }\n\n th,\n td,\n tr {\n border-color: $dark-light;\n }\n }\n\n // Post\n .page-content {\n a {\n color: $dark-text-link-blue;\n\n &:hover,\n &:active,\n &:focus {\n color: $dark-text-link-blue-active;\n }\n }\n\n h3 {\n border-color: $dark-light;\n }\n h1,\n h2,\n h3,\n h4,\n h5,\n h6 {\n .anchor-head {\n color: $dark-text-link-blue;\n }\n }\n }\n\n // Syntax\n code {\n &.highlighter-rouge {\n background-color: $dark-light;\n }\n }\n\n // kbd tag\n kbd {\n border-color: $dark-light;\n color: $dark-white;\n text-shadow: 0 1px 0 $dark-black;\n }\n\n // horizontal rule\n hr {\n border-color: $dark-light;\n }\n\n // Post Meta\n .post-meta {\n color: $dark-gray;\n\n time {\n &::after {\n background-color: $dark-light;\n }\n }\n\n span[itemprop=\"author\"] {\n border-color: $dark-light;\n }\n }\n\n // Link\n a {\n color: inherit;\n text-decoration-color: $dark-smoke;\n\n &:hover {\n color: $dark-text-link-blue;\n }\n\n &:focus {\n outline-color: rgba(255, 82, 119, 0.6);\n }\n }\n\n // List\n li {\n &:before {\n color: $dark-white;\n }\n }\n\n // Blockquote\n blockquote {\n color: $dark-gray;\n border-color: $dark-light;\n }\n\n // Strong, Bold\n strong,\n b {\n color: $dark-white;\n }\n\n // Navbar\n .navbar {\n border-color: $dark-light;\n .menu {\n a#mode {\n .mode-sunny {\n display: block;\n }\n .mode-moon {\n display: none;\n }\n }\n\n .menu-link {\n color: $dark-white;\n }\n @include media-query($on-mobile) {\n background-color: $dark-black;\n border-color: $dark-light;\n\n .menu-icon {\n > svg {\n fill: $dark-white;\n }\n }\n\n input[type=\"checkbox\"]:checked ~ .trigger {\n background: $dark-black;\n }\n }\n }\n }\n\n // Post Item\n .post-item {\n &:not(:first-child) {\n border-color: $dark-light;\n }\n\n .post-item-date {\n color: $dark-white;\n }\n .post-item-title {\n a {\n color: $dark-text-base-color;\n\n &:hover,\n &focus {\n color: $dark-white;\n }\n }\n }\n }\n\n // Post Navigation\n .post-nav {\n border-color: $dark-light;\n\n .post-nav-item {\n font-weight: $bold-weight;\n\n .post-title {\n color: $dark-white;\n opacity: 0.9;\n }\n\n &:hover,\n &:focus {\n .post-title {\n color: $dark-text-link-blue-active;\n }\n }\n\n .nav-arrow {\n color: $dark-gray;\n }\n }\n\n @include media-query($on-mobile) {\n .post-nav-item:nth-child(even) {\n border-color: $dark-light;\n }\n }\n }\n\n // Footer\n .footer {\n span.footer_item {\n color: $dark-white;\n }\n a.footer_item:not(:last-child) {\n color: $dark-white;\n }\n .footer_copyright {\n color: $dark-gray;\n opacity: 1;\n }\n }\n\n // 404 Page\n .not-found {\n .title {\n color: $dark-white;\n text-shadow: 1px 0px 0px $dark-text-link-blue;\n }\n .phrase {\n color: $dark-text-base-color;\n }\n .solution {\n color: $dark-text-link-blue;\n }\n .solution:hover {\n color: $dark-text-link-blue-active;\n }\n } \n\n .search-article {\n input[type=\"search\"] {\n color: $dark-text-base-color;\n &::-webkit-input-placeholder {\n color: rgba(128,128,128,0.8);\n }\n }\n }\n}\n" + ], + "names": [], + "mappings": "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" +} \ No newline at end of file diff --git a/src/_site/assets/favicons/android-chrome-192x192.png b/src/_site/assets/favicons/android-chrome-192x192.png new file mode 100644 index 0000000..9100b0e Binary files /dev/null and b/src/_site/assets/favicons/android-chrome-192x192.png differ diff --git a/src/_site/assets/favicons/android-chrome-384x384.png b/src/_site/assets/favicons/android-chrome-384x384.png new file mode 100644 index 0000000..babf68c Binary files /dev/null and b/src/_site/assets/favicons/android-chrome-384x384.png differ diff --git a/src/_site/assets/favicons/android-chrome-512x512.png b/src/_site/assets/favicons/android-chrome-512x512.png new file mode 100644 index 0000000..e886216 Binary files /dev/null and b/src/_site/assets/favicons/android-chrome-512x512.png differ diff --git a/src/_site/assets/favicons/apple-touch-icon.png b/src/_site/assets/favicons/apple-touch-icon.png new file mode 100644 index 0000000..6e70307 Binary files /dev/null and b/src/_site/assets/favicons/apple-touch-icon.png differ diff --git a/src/_site/assets/favicons/browserconfig.xml b/src/_site/assets/favicons/browserconfig.xml new file mode 100644 index 0000000..5cd27e3 --- /dev/null +++ b/src/_site/assets/favicons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #603cba + + + diff --git a/src/_site/assets/favicons/favicon-16x16.png b/src/_site/assets/favicons/favicon-16x16.png new file mode 100644 index 0000000..b81a8dc Binary files /dev/null and b/src/_site/assets/favicons/favicon-16x16.png differ diff --git a/src/_site/assets/favicons/favicon-32x32.png b/src/_site/assets/favicons/favicon-32x32.png new file mode 100644 index 0000000..532571d Binary files /dev/null and b/src/_site/assets/favicons/favicon-32x32.png differ diff --git a/src/_site/assets/favicons/favicon.ico b/src/_site/assets/favicons/favicon.ico new file mode 100644 index 0000000..fbc9922 Binary files /dev/null and b/src/_site/assets/favicons/favicon.ico differ diff --git a/src/_site/assets/favicons/mstile-150x150.png b/src/_site/assets/favicons/mstile-150x150.png new file mode 100644 index 0000000..455d5ed Binary files /dev/null and b/src/_site/assets/favicons/mstile-150x150.png differ diff --git a/src/_site/assets/favicons/safari-pinned-tab.svg b/src/_site/assets/favicons/safari-pinned-tab.svg new file mode 100644 index 0000000..ec6019b --- /dev/null +++ b/src/_site/assets/favicons/safari-pinned-tab.svg @@ -0,0 +1,25 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/src/_site/assets/favicons/site.webmanifest b/src/_site/assets/favicons/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/src/_site/assets/favicons/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/src/_site/assets/img/avatar.jpg b/src/_site/assets/img/avatar.jpg new file mode 100644 index 0000000..e13d044 Binary files /dev/null and b/src/_site/assets/img/avatar.jpg differ diff --git a/src/_site/assets/js/disqus.js b/src/_site/assets/js/disqus.js new file mode 100644 index 0000000..f490611 --- /dev/null +++ b/src/_site/assets/js/disqus.js @@ -0,0 +1 @@ +!function(t,e,n){"use strict";var o=function(t,e){var n,o;return function(){var i=this,r=arguments,d=+new Date;n&&dt.innerHeight*r||i-l-u.offsetHeight-t.innerHeight*r>0)return!0;var c,f,p,y=e.getElementById("disqus_thread");y&&y.removeAttribute("id"),u.setAttribute("id","disqus_thread"),u.disqusLoaderStatus="loaded","loaded"==a?DISQUS.reset({reload:!0,config:d}):(t.disqus_config=d,"unloaded"==a&&(a="loading",c=s,f=function(){a="loaded"},(p=e.createElement("script")).src=c,p.async=!0,p.setAttribute("data-timestamp",+new Date),p.addEventListener("load",function(){"function"==typeof f&&f()}),(e.head||e.body).appendChild(p)))};t.addEventListener("scroll",o(i,l)),t.addEventListener("resize",o(i,l)),t.disqusLoader=function(t,n){n=function(t,e){var n,o={};for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(o[n]=t[n]);for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(o[n]=e[n]);return o}({laziness:1,throttle:250,scriptUrl:!1,disqusConfig:!1},n),r=n.laziness+1,i=n.throttle,d=n.disqusConfig,s=!1===s?n.scriptUrl:s,(u="string"==typeof t?e.querySelector(t):"number"==typeof t.length?t[0]:t)&&(u.disqusLoaderStatus="unloaded"),l()}}(window,document); \ No newline at end of file diff --git a/src/_site/assets/js/main.js b/src/_site/assets/js/main.js new file mode 100644 index 0000000..b58aca9 --- /dev/null +++ b/src/_site/assets/js/main.js @@ -0,0 +1,31 @@ +(() => { + // Theme switch + const body = document.body; + const lamp = document.getElementById("mode"); + + const toggleTheme = (state) => { + if (state === "dark") { + localStorage.setItem("theme", "light"); + body.removeAttribute("data-theme"); + } else if (state === "light") { + localStorage.setItem("theme", "dark"); + body.setAttribute("data-theme", "dark"); + } else { + initTheme(state); + } + }; + + lamp.addEventListener("click", () => + toggleTheme(localStorage.getItem("theme")) + ); + + // Blur the content when the menu is open + const cbox = document.getElementById("menu-trigger"); + + cbox.addEventListener("change", function () { + const area = document.querySelector(".wrapper"); + this.checked + ? area.classList.add("blurry") + : area.classList.remove("blurry"); + }); +})(); diff --git a/src/_site/assets/js/search.min.js b/src/_site/assets/js/search.min.js new file mode 100644 index 0000000..7b47ead --- /dev/null +++ b/src/_site/assets/js/search.min.js @@ -0,0 +1,6 @@ +/*! + * Simple-Jekyll-Search + * Copyright 2015-2020, Christian Fei + * Licensed under the MIT License. + */ + !function(){"use strict";var i={compile:function(r){return o.template.replace(o.pattern,function(t,e){var n=o.middleware(e,r[e],o.template);return void 0!==n?n:r[e]||t})},setOptions:function(t){o.pattern=t.pattern||o.pattern,o.template=t.template||o.template,"function"==typeof t.middleware&&(o.middleware=t.middleware)}},o={};o.pattern=/\{(.*?)\}/g,o.template="",o.middleware=function(){};var n=function(t,e){var n=e.length,r=t.length;if(n{title}',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,exclude:[]},w=function j(t){if(!((e=t)&&"undefined"!=typeof e.required&&e.required instanceof Array))throw new Error("-- OptionsValidator: required options missing");var e;if(!(this instanceof j))return new j(t);var r=t.required;this.getRequiredOptions=function(){return r},this.validate=function(e){var n=[];return r.forEach(function(t){"undefined"==typeof e[t]&&n.push(t)}),n}}({required:v=["searchInput","resultsContainer","json"]}),t.SimpleJekyllSearch=function(t){var n;0 Error Handling in Borgmatic - Pim Kunis

Error Handling in Borgmatic

BorgBackup and Borgmatic have been my go-to tools to create backups for my home lab since I started creating backups. Using Systemd Timers, I regularly create a backup every night. I also monitor successful execution of the backup process, in case some error occurs. However, the way I set this up resulted in not receiving notifications. Even though it boils down to RTFM, I’d like to explain my error and how to handle errors correctly.

I was using the on_error option to handle errors, like so:

on_error:
+  - 'apprise --body="Error while performing backup" <URL> || true'
+

However, on_error does not handle errors from the execution of before_everything and after_everything hooks. My solution to this was moving the error handling up to the Systemd service that calls Borgmatic. This results in the following Systemd service:

[Unit]
+Description=Backup data using Borgmatic
+# Added
+OnFailure=backup-failure.service
+
+[Service]
+ExecStart=/usr/bin/borgmatic --config /root/backup.yml
+Type=oneshot
+

This handles any error, be it from Borgmatic’s hooks or itself. The backup-failure service is very simple, and just calls Apprise to send a notification:

[Unit]
+Description=Send backup failure notification
+
+[Service]
+Type=oneshot
+ExecStart=apprise --body="Failed to create backup!" <URL>
+
+[Install]
+WantedBy=multi-user.target
+

The Aftermath (or what I learned)

Because the error handling and alerting weren’t working propertly, my backups didn’t succeed for two weeks straight. And, of course, you only notice your backups aren’t working when you actually need them. This is exactly what happened: my disk was full and a MariaDB database crashed as a result of that. Actually, the whole database seemed to be corrupt and I find it worrying MariaDB does not seem to be very resilient to failures (in comparison a PostgreSQL database was able to recover automatically). I then tried to recover the data using last night’s backup, only to find out there was no such backup. Fortunately, I had other means to recover the data so I incurred no data loss.

I already knew it is important to test backups, but I learned it is also important to test failures during backups!

diff --git a/src/_site/browserconfig.xml b/src/_site/browserconfig.xml new file mode 100644 index 0000000..4c5c4ec --- /dev/null +++ b/src/_site/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #2c2c2c + + + diff --git a/src/_site/concourse-apprise-notifier/index.html b/src/_site/concourse-apprise-notifier/index.html new file mode 100644 index 0000000..c0dc6bd --- /dev/null +++ b/src/_site/concourse-apprise-notifier/index.html @@ -0,0 +1,59 @@ + Sending Apprise Notifications from Concourse CI - Pim Kunis

Sending Apprise Notifications from Concourse CI

Recently, I deployed Concourse CI because I wanted to get my feet wet with a CI/CD pipeline. However, I had a practical use case lying around for a long time: automatically compiling my static website and deploying it to my docker Swarm. This took some time getting right, but the result works like a charm (source code).

It’s comforting to know I don’t have move a finger and my website is automatically deployed. However, I would still like to receive some indication of what’s happening. And what’s a better way to do that, than using my Apprise service to keep me up to date. There’s a little snag though: I could not find any Concourse resource that does this. That’s when I decided to just create it myself.

The Plagiarism Hunt

As any good computer person, I am lazy. I’d rather just copy someone’s work, so that’s what I did. I found this GitHub repository that does the same thing but for Slack notifications. For some reason it’s archived, but it seemed like it should work. I actually noticed lots of repositories for Concourse resource types are archived, so not sure what’s going on there.

Getting to know Concourse

Let’s first understand what we need to do reach our end goal of sending Apprise notifications from Concourse.

A Concourse pipeline takes some inputs, performs some operations on them which result in some outputs. These inputs and outputs are called resources in Concourse. For example, a Git repository could be a resource. Each resource is an instance of a resource type. A resource type therefore is simply a blueprint that can create multiple resources. To continue the example, a resource type could be “Git repository”.

We therefore need to create our own resource type that can send Apprise notifications. A resource type is simply a container that includes three scripts:

  • check: check for a new version of a resource
  • in: retrieve a version of the resource
  • out: create a version of the resource

As Apprise notifications are basically fire-and-forget, we will only implement the out script.

Writing the out script

The whole script can be found here, but I will explain the most important bits of it. Note that I only use Apprise’s persistent storage solution, and not its stateless solution.

Concourse provides us with the working directory, which we cd to:

cd "${1}"
+

We create a timestamp, formatted in JSON, which we will use for the resource’s new version later. Concourse requires us to set a version for the resource, but since Apprise notifications don’t have that, we use the timestamp:

timestamp="$(jq -n "{version:{timestamp:\"$(date +%s)\"}}")"
+

First some black magic Bash to redirect file descriptors. Not sure why this is needed, but I copied it anyways. After that, we create a temporary file holding resource’s parameters.

exec 3>&1
+exec 1>&2
+
+payload=$(mktemp /tmp/resource-in.XXXXXX)
+cat > "${payload}" <&0
+

We then extract the individual parameters. The source key contains values how the resource type was specified, while the params key specifies parameters for this specific resource.

apprise_host="$(jq -r '.source.host' < "${payload}")"
+apprise_key="$(jq -r '.source.key' < "${payload}")"
+
+alert_body="$(jq -r '.params.body' < "${payload}")"
+alert_title="$(jq -r '.params.title // null' < "${payload}")"
+alert_type="$(jq -r '.params.type // null' < "${payload}")"
+alert_tag="$(jq -r '.params.tag // null' < "${payload}")"
+alert_format="$(jq -r '.params.format // null' < "${payload}")"
+

We then format the different parameters using JSON:

alert_body="$(eval "printf \"${alert_body}\"" | jq -R -s .)"
+[ "${alert_title}" != "null" ] && alert_title="$(eval "printf \"${alert_title}\"" | jq -R -s .)"
+[ "${alert_type}" != "null" ] && alert_type="$(eval "printf \"${alert_type}\"" | jq -R -s .)"
+[ "${alert_tag}" != "null" ] && alert_tag="$(eval "printf \"${alert_tag}\"" | jq -R -s .)"
+[ "${alert_format}" != "null" ] && alert_format="$(eval "printf \"${alert_format}\"" | jq -R -s .)"
+

Next, from the individual parameters we construct the final JSON message body we send to the Apprise endpoint.

body="$(cat <<EOF
+{
+  "body": ${alert_body},
+  "title": ${alert_title},
+  "type": ${alert_type},
+  "tag": ${alert_tag},
+  "format": ${alert_format}
+}
+EOF
+)"
+

Before sending it just yet, we compact the JSON and remove any values that are null:

compact_body="$(echo "${body}" | jq -c '.')"
+echo "$compact_body" | jq 'del(..|nulls)' > /tmp/compact_body.json
+

Here is the most important line, where we send the payload to the Apprise endpoint. It’s quite straight-forward.

curl -v -X POST -T /tmp/compact_body.json -H "Content-Type: application/json" "${apprise_host}/notify/${apprise_key}"
+

Finally, we print the timestamp (fake version) in order to appease the Concourse gods.

echo "${timestamp}" >&3
+

Building the Container

As said earlier, to actually use this script, we need to add it to a image. I won’t be explaining this whole process, but the source can be found here. The most important take-aways are these:

  • Use concourse/oci-build-task to build a image from a Dockerfile.
  • Use registry-image to push the image to an image registry.

Using the Resource Type

Using our newly created resource type is surprisingly simple. I use it for the blog you are reading right now and the pipeline definition can be found here. Here we specify the resource type in a Concourse pipeline:

resource_types:
+- name: apprise
+  type: registry-image
+  source:
+    repository: git.kun.is/pim/concourse-apprise-notifier
+    tag: "1.1.1"
+

We simply have to tell Concourse where to find the image, and which tag we want. Next, we instantiate the resource type to create a resource:

resources:
+- name: apprise-notification
+  type: apprise
+  source:
+    host: https://apprise.kun.is:444
+    key: concourse
+  icon: bell
+

We simply specify the host to send Apprise notifications to. Yeah, I even gave it a little bell because it’s cute.

All that’s left to do, is actually send the notification. Let’s see how that is done:

- name: deploy-static-website
+  plan:
+    - task: deploy-site
+      config: ...
+
+      on_success:
+	put: apprise-notification
+	params:
+	  title: "Static website deployed!"
+	  body: "New version: $(cat version/version)"
+	no_get: true
+

As can be seen, the Apprise notification can be triggered when a task is executed successfully. We do this using the put command, which execute the out script underwater. We set the notification’s title and body, and send it! The result is seen below in my Ntfy app, which Apprise forwards the message to: picture showing my Ntfy app with the Apprise notification

And to finish this off, here is what it looks like in the Concourse web UI: the concourse web gui showing the pipeline of my static website including the the apprise notification resources

Conclusion

Concourse’s way of representing everything as an image/container is really interesting in my opinion. A resource type is quite easily implemented as well, although Bash might not be the optimal way to do this. I’ve seen some people implement it in Rust, which might be a good excuse to finally learn that language :)

Apart from Apprise notifications, I’m planning on creating a resource type to deploy to a Docker swarm eventually. This seems like a lot harder than simply sending notifications though.

diff --git a/src/_site/concourse-apprise-notifier/ntfy.png b/src/_site/concourse-apprise-notifier/ntfy.png new file mode 100644 index 0000000..3b47f51 Binary files /dev/null and b/src/_site/concourse-apprise-notifier/ntfy.png differ diff --git a/src/_site/concourse-apprise-notifier/pipeline.png b/src/_site/concourse-apprise-notifier/pipeline.png new file mode 100644 index 0000000..68d0d14 Binary files /dev/null and b/src/_site/concourse-apprise-notifier/pipeline.png differ diff --git a/src/_site/feed.xml b/src/_site/feed.xml new file mode 100644 index 0000000..3c3ea5d --- /dev/null +++ b/src/_site/feed.xml @@ -0,0 +1,738 @@ +Jekyll2024-04-26T10:58:13+02:00http://localhost:4000/feed.xmlPim KunisA pig's gotta flyPim KunisIt’s alive!2024-04-21T10:02:00+02:002024-04-21T10:02:00+02:00http://localhost:4000/its-aliveFinally, after several months this website is up and running again!

+

My homelab has completely changed, but the reason why it initially went offline is because of my failing CI installation. +I was using Concourse CI which I was initially interested in due to the reproducible nature of its builds using containers. +However, for some reason pipelines were sporadically getting stuck when I reboot the virtual machine it was running on. +The fix was very annoying: I had to re-create the pipelines manually (which feels very backwards for a CI/CD system!) +Additionally, my virtual machine setup back then was also quite fragile and I decided to get rid of that as well.

+

I have learned that having an escape hatch to deploy something is probably a good idea đź… +Expect a new overview of my homelab soon, in the same vein as this post from last year!

]]>
Pim Kunis
Home Lab Infrastructure Snapshot August 20232023-08-27T22:23:00+02:002023-08-27T22:23:00+02:00http://localhost:4000/infrastructure-snapshotI have been meaning to write about the current state of my home lab infrastructure for a while now. +Now that the most important parts are quite stable, I think the opportunity is ripe. +I expect this post to get quite long, so I might have to leave out some details along the way.

+

This post will be a starting point for future infrastructure snapshots which I can hopefully put out periodically. +That is, if there is enough worth talking about.

+

Keep an eye out for the icon, which links to the source code and configuration of anything mentioned. +Oh yeah, did I mention everything I do is open source?

+

Networking and Infrastructure Overview

+

Hardware and Operating Systems

+

Let’s start with the basics: what kind of hardware do I use for my home lab? +The most important servers are my three Gigabyte Brix GB-BLCE-4105. +Two of them have 16 GB of memory, and one 8 GB. +I named these servers as follows:

+
    +
  • Atlas: because this server was going to “lift” a lot of virtual machines.
  • +
  • Lewis: we started out with a “Max” server named after the Formula 1 driver Max Verstappen, but it kind of became an unmanagable behemoth without infrastructure-as-code. Our second server we subsequently named Lewis after his colleague Lewis Hamilton. Note: people around me vetoed these names and I am no F1 fan!
  • +
  • Jefke: it’s a funny Belgian name. That’s all.
  • +
+

Here is a picture of them sitting in their cosy closet:

+

A picture of my servers.

+

If you look look to the left, you will also see a Raspberry pi 4B. +I use this Pi to do some rudimentary monitoring whether servers and services are running. +More on this in the relevant section below. +The Pi is called Iris because it’s a messenger for the other servers.

+

I used to run Ubuntu on these systems, but I have since migrated away to Debian. +The main reasons were Canonical putting advertisements in my terminal and pushing Snap which has a proprietry backend. +Two of my servers run the newly released Debian Bookworm, while one still runs Debian Bullseye.

+

Networking

+

For networking, I wanted hypervisors and virtual machines separated by VLANs for security reasons. +The following picture shows a simplified view of the VLANs present in my home lab:

+

Picture showing the VLANS in my home lab.

+

All virtual machines are connected to a virtual bridge which tags network traffic with the DMZ VLAN. +The hypervisors VLAN is used for traffic to and from the hypervisors. +Devices from the hypervisors VLAN are allowed to connect to devices in the DMZ, but not vice versa. +The hypervisors are connected to a switch using a trunk link, allows both DMZ and hypervisors traffic.

+

I realised the above design using ifupdown. +Below is the configuration for each hypervisor, which creates a new enp3s0.30 interface with all DMZ traffic from the enp3s0 interface .

+
auto enp3s0.30
+iface enp3s0.30 inet manual
+iface enp3s0.30 inet6 auto
+	accept_ra 0
+	dhcp 0
+	request_prefix 0
+	privext 0
+	pre-up sysctl -w net/ipv6/conf/enp3s0.30/disable_ipv6=1
+
+

This configuration seems more complex than it actually is. +Most of it is to make sure the interface is not assigned an IPv4/6 address on the hypervisor host. +The magic .30 at the end of the interface name makes this interface tagged with VLAN ID 30 (DMZ for me).

+

Now that we have an interface tagged for the DMZ VLAN, we can create a bridge where future virtual machines can connect to:

+
auto dmzbr
+iface dmzbr inet manual
+	bridge_ports enp3s0.30
+	bridge_stp off
+iface dmzbr inet6 auto
+	accept_ra 0
+	dhcp 0
+	request_prefix 0
+	privext 0
+	pre-up sysctl -w net/ipv6/conf/dmzbr/disable_ipv6=1
+
+

Just like the previous config, this is quite bloated because I don’t want the interface to be assigned an IP address on the host. +Most importantly, the bridge_ports enp3s0.30 line here makes this interface a virtual bridge for the enp3s0.30 interface.

+

And voilĂ , we now have a virtual bridge on each machine, where only DMZ traffic will flow. +Here I verify whether this configuration works:

+
+ Show +

We can see that the two virtual interfaces are created, and are only assigned a MAC address and not a IP address:

+
root@atlas:~# ip a show enp3s0.30
+4: enp3s0.30@enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master dmzbr state UP group default qlen 1000
+    link/ether d8:5e:d3:4c:70:38 brd ff:ff:ff:ff:ff:ff
+5: dmzbr: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
+    link/ether 4e:f7:1f:0f:ad:17 brd ff:ff:ff:ff:ff:ff
+
+

Pinging a VM from a hypervisor works:

+
root@atlas:~# ping -c1 maestro.dmz
+PING maestro.dmz (192.168.30.8) 56(84) bytes of data.
+64 bytes from 192.168.30.8 (192.168.30.8): icmp_seq=1 ttl=63 time=0.457 ms
+
+

Pinging a hypervisor from a VM does not work:

+
root@maestro:~# ping  -c1 atlas.hyp
+PING atlas.hyp (192.168.40.2) 56(84) bytes of data.
+
+--- atlas.hyp ping statistics ---
+1 packets transmitted, 0 received, 100% packet loss, time 0ms
+
+
+

DNS and DHCP

+

Now that we have a working DMZ network, let’s build on it to get DNS and DHCP working. +This will enable new virtual machines to obtain a static or dynamic IP address and register their host in DNS. +This has actually been incredibly annoying due to our friend Network address translation (NAT).

+
+ NAT recap +

Network address translation (NAT) is a function of a router which allows multiple hosts to share a single IP address. +This is needed for IPv4, because IPv4 addresses are scarce and usually one household is only assigned a single IPv4 address. +This is one of the problems IPv6 attempts to solve (mainly by having so many IP addresses that they should never run out). +To solve the problem for IPv4, each host in a network is assigned a private IPv4 address, which can be reused for every network.

+

Then, the router must perform address translation. +It does this by keeping track of ports opened by hosts in its private network. +If a packet from the internet arrives at the router for such a port, it forwards this packet to the correct host.

+
+

I would like to host my own DNS on a virtual machine (called hermes, more on VMs later) in the DMZ network. +This basically gives two problems:

+
    +
  1. The upstream DNS server will refer to the public internet-accessible IP address of our DNS server. +This IP-address has no meaning inside the private network due to NAT and the router will reject the packet.
  2. +
  3. Our DNS resolves hosts to their public internet-accessible IP address. +This is similar to the previous problem as the public IP address has no meaning.
  4. +
+

The first problem can be remediated by overriding the location of the DNS server for hosts inside the DMZ network. +This can be achieved on my router, which uses Unbound as its recursive DNS server:

+

Unbound overides for kun.is and dmz domains.

+

Any DNS requests to Unbound to domains in either dmz or kun.is will now be forwarded 192.168.30.7 (port 5353). +This is the virtual machine hosting my DNS.

+

The second problem can be solved at the DNS server. +We need to do some magic overriding, which dnsmasq is perfect for :

+
alias=84.245.14.149,192.168.30.8
+server=/kun.is/192.168.30.7
+
+

This always overrides the public IPv4 address to the private one. +It also overrides the DNS server for kun.is to 192.168.30.7.

+

Finally, behind the dnsmasq server, I run Powerdns as authoritative DNS server . +I like this DNS server because I can manage it with Terraform .

+

Here is a small diagram showing my setup (my networking teacher would probably kill me for this): +Shitty diagram showing my DNS setup.

+

Virtualization

+

https://github.com/containrrr/shepherd +Now that we have laid out the basic networking, let’s talk virtualization. +Each of my servers are configured to run KVM virtual machines, orchestrated using Libvirt. +Configuration of the physical hypervisor servers, including KVM/Libvirt is done using Ansible. +The VMs are spun up using Terraform and the dmacvicar/libvirt Terraform provider.

+

This all isn’t too exciting, except that I created a Terraform module that abstracts the Terraform Libvirt provider for my specific scenario :

+
module "maestro" {
+  source          = "git::https://git.kun.is/home/tf-modules.git//debian"
+  name            = "maestro"
+  domain_name     = "tf-maestro"
+  memory          = 10240
+  mac             = "CA:FE:C0:FF:EE:08"
+}
+
+

This automatically creates a Debian virtual machines with the properties specified. +It also sets up certificate-based SSH authentication which I talked about before.

+

Clustering

+

With virtualization explained, let’s move up one level further. +Each of my three physical servers hosts a virtual machine running Docker, which together form a Docker Swarm. +I use Traefik as a reverse proxy which routes requests to the correct container.

+

All data is hosted on a single machine and made available to containers using NFS. +This might not be very secure (as NFS is not encrypted and no proper authentication), it is quite fast.

+

As of today, I host the following services on my Docker Swarm :

+ +

CI / CD

+

For CI / CD, I run Concourse CI in a separate VM. +This is needed, because Concourse heavily uses containers to create reproducible builds.

+

Although I should probably use it for more, I currently use my Concourse for three pipelines:

+
    +
  • A pipeline to build this static website and create a container image of it. +The image is then uploaded to the image registry of my Forgejo instance. +I love it when I can use stuff I previously built :) +The pipeline finally deploys this new image to the Docker Swarm .
  • +
  • A pipeline to create a Concourse resource that sends Apprise alerts (Concourse-ception?)
  • +
  • A pipeline to build a custom Fluentd image with plugins installed
  • +
+

Backups

+

To create backups, I use Borg. +As I keep all data on one machine, this backup process is quite simple. +In fact, all this data is stored in a single Libvirt volume. +To configure Borg with a simple declarative script, I use Borgmatic.

+

In order to back up the data inside the Libvirt volume, I create a snapshot to a file. +Then I can mount this snapshot in my file system. +The files can then be backed up while the system is still running. +It is also possible to simply back up the Libvirt image, but this takes more time and storage .

+

Monitoring and Alerting

+

The last topic I would like to talk about is monitoring and alerting. +This is something I’m still actively improving and only just set up properly.

+

Alerting

+

For alerting, I wanted something that runs entirely on my own infrastructure. +I settled for Apprise + Ntfy.

+

Apprise is a server that is able to send notifications to dozens of services. +For application developers, it is thus only necessary to implement the Apprise API to gain access to all these services. +The Apprise API itself is also very simple. +By using Apprise, I can also easily switch to another notification service later. +Ntfy is free software made for mobile push notifications.

+

I use this alerting system in quite a lot of places in my infrastructure, for example when creating backups.

+

Uptime Monitoring

+

The first monitoring setup I created, was using Uptime Kuma. +Uptime Kuma periodically pings a service to see whether it is still running. +You can do a literal ping, test HTTP response codes, check database connectivity and much more. +I use it to check whether my services and VMs are online. +And the best part is, Uptime Kuma supports Apprise so I get push notifications on my phone whenever something goes down!

+

Metrics and Log Monitoring

+

A new monitoring system I am still in the process of deploying is focused on metrics and logs. +I plan on creating a separate blog post about this, so keep an eye out on that (for example using RSS :)). +Safe to say, it is no basic ELK stack!

+

Conclusion

+

That’s it for now! +Hopefully I inspired someone to build something… or how not to :)

]]>
Pim Kunis
Hashicorp’s License Change and my Home Lab - Update2023-08-17T18:15:00+02:002023-08-17T18:15:00+02:00http://localhost:4000/hashicorp-license-changeSee the Update at the end of the article.

+

Already a week ago, Hashicorp announced it would change the license on almost all its projects. +Unlike their previous license, which was the Mozilla Public License 2.0, their new license is no longer truly open source. +It is called the Business Source License™ and restricts use of their software for competitors. +In their own words:

+
+

Vendors who provide competitive services built on our community products will no longer be able to incorporate future releases, bug fixes, or security patches contributed to our products.

+
+

I found a great article by MeshedInsights that names this behaviour the “rights ratchet model”. +They define a script start-ups use to garner the interest of open source enthusiasts but eventually turn their back on them for profit. +The reason why Hashicorp can do this, is because contributors signed a copyright license agreement (CLA). +This agreement transfers the copyright of contributors’ code to Hashicorp, allowing them to change the license if they want to.

+

I find this action really regrettable because I like their products. +This sort of action was also why I wanted to avoid using an Elastic stack, which also had their license changed.1 +These companies do not respect their contributors and the software stack beneath they built their product on, which is actually open source (Golang, Linux, etc.).

+

Impact on my Home Lab

+

I am using Terraform in my home lab to manage several important things:

+
    +
  • Libvirt virtual machines
  • +
  • PowerDNS records
  • +
  • Elasticsearch configuration
  • +
+

With Hashicorp’s anti open source move, I intend to move away from Terraform in the future. +While I will not use Hashicorp’s products for new personal projects, I will leave my current setup as-is for some time because there is no real need to quickly migrate.

+

I might also investigate some of Terraform’s competitors, like Pulumi. +Hopefully there is a project that respects open source which I can use in the future.

+

Update

+

A promising fork of Terraform has been announced called OpenTF. +They intend to take part of the Cloud Native Computing Foundation, which I think is a good effort because Terraform is so important for modern cloud infrastructures.

+

Footnotes

+
+
    +
  1. +

    While I am still using Elasticsearch, I don’t use the rest of the Elastic stack in order to prevent a vendor lock-in. ↩

    +
  2. +
+
]]>
Pim Kunis
Monitoring Correct Memory Usage in Fluent Bit2023-08-09T16:19:00+02:002023-08-09T16:19:00+02:00http://localhost:4000/fluent-bit-memoryPreviously, I have used Prometheus’ node_exporter to monitor the memory usage of my servers. +However, I am currently in the process of moving away from Prometheus to a new Monioring stack. +While I understand the advantages, I felt like Prometheus’ pull architecture does not scale nicely. +Everytime I spin up a new machine, I would have to centrally change Prometheus’ configuration in order for it to query the new server.

+

In order to collect metrics from my servers, I am now using Fluent Bit. +I love Fluent Bit’s way of configuration which I can easily express as code and automate, its focus on effiency and being vendor agnostic. +However, I have stumbled upon one, in my opinion, big issue with Fluent Bit: its mem plugin to monitor memory usage is completely useless. +In this post I will go over the problem and my temporary solution.

+

The Problem with Fluent Bit’s mem Plugin

+

As can be seen in the documentation, Fluent Bit’s mem input plugin exposes a few metrics regarding memory usage which should be self-explaining: Mem.total, Mem.used, Mem.free, Swap.total, Swap.used and Swap.free. +The problem is that Mem.used and Mem.free do not accurately reflect the machine’s actual memory usage. +This is because these metrics include caches and buffers, which can be reclaimed by other processes if needed. +Most tools reporting memory usage therefore include an additional metric that specifices the memory available on the system. +For example, the command free -m reports the following data on my laptop:

+
               total        used        free      shared  buff/cache   available
+Mem:           15864        3728        7334         518        5647       12136
+Swap:           2383         663        1720
+
+

Notice that the available memory is more than free memory.

+

While the issue is known (see this and this link), it is unfortunately not yet fixed.

+

A Temporary Solution

+

The issues I linked previously provide stand-alone plugins that fix the problem, which will hopefully be merged in the official project at some point. +However, I didn’t want to install another plugin so I used Fluent Bit’s exec input plugin and the free Linux command to query memory usage like so:

+
[INPUT]
+    Name exec
+    Tag memory
+    Command free -m | tail -2 | tr '\n' ' '
+    Interval_Sec 1
+
+

To interpret the command’s output, I created the following filter:

+
[FILTER]
+    Name parser
+    Match memory
+    Key_Name exec
+    Parser free
+
+

Lastly, I created the following parser (warning: regex shitcode incoming):

+
[PARSER]
+    Name free
+    Format regex
+    Regex ^Mem:\s+(?<mem_total>\d+)\s+(?<mem_used>\d+)\s+(?<mem_free>\d+)\s+(?<mem_shared>\d+)\s+(?<mem_buff_cache>\d+)\s+(?<mem_available>\d+) Swap:\s+(?<swap_total>\d+)\s+(?<swap_used>\d+)\s+(?<swap_free>\d+)
+    Types mem_total:integer mem_used:integer mem_free:integer mem_shared:integer mem_buff_cache:integer mem_available:integer swap_total:integer swap_used:integer
+
+

With this configuration, you can use the mem_available metric to get accurate memory usage in Fluent Bit.

+

Conclusion

+

Let’s hope Fluent Bit’s mem input plugin is improved upon soon so this hacky solution is not needed. +I also intend to document my new monitoring pipeline, which at the moment consists of:

+
    +
  • Fluent Bit
  • +
  • Fluentd
  • +
  • Elasticsearch
  • +
  • Grafana
  • +
]]>
Pim Kunis
Error Handling in Borgmatic2023-08-08T11:51:00+02:002023-08-08T11:51:00+02:00http://localhost:4000/backup-failureBorgBackup and Borgmatic have been my go-to tools to create backups for my home lab since I started creating backups. +Using Systemd Timers, I regularly create a backup every night. +I also monitor successful execution of the backup process, in case some error occurs. +However, the way I set this up resulted in not receiving notifications. +Even though it boils down to RTFM, I’d like to explain my error and how to handle errors correctly.

+

I was using the on_error option to handle errors, like so:

+
on_error:
+  - 'apprise --body="Error while performing backup" <URL> || true'
+
+

However, on_error does not handle errors from the execution of before_everything and after_everything hooks. +My solution to this was moving the error handling up to the Systemd service that calls Borgmatic. +This results in the following Systemd service:

+
[Unit]
+Description=Backup data using Borgmatic
+# Added
+OnFailure=backup-failure.service
+
+[Service]
+ExecStart=/usr/bin/borgmatic --config /root/backup.yml
+Type=oneshot
+
+

This handles any error, be it from Borgmatic’s hooks or itself. +The backup-failure service is very simple, and just calls Apprise to send a notification:

+
[Unit]
+Description=Send backup failure notification
+
+[Service]
+Type=oneshot
+ExecStart=apprise --body="Failed to create backup!" <URL>
+
+[Install]
+WantedBy=multi-user.target
+
+

The Aftermath (or what I learned)

+

Because the error handling and alerting weren’t working propertly, my backups didn’t succeed for two weeks straight. +And, of course, you only notice your backups aren’t working when you actually need them. +This is exactly what happened: my disk was full and a MariaDB database crashed as a result of that. +Actually, the whole database seemed to be corrupt and I find it worrying MariaDB does not seem to be very resilient to failures (in comparison a PostgreSQL database was able to recover automatically). +I then tried to recover the data using last night’s backup, only to find out there was no such backup. +Fortunately, I had other means to recover the data so I incurred no data loss.

+

I already knew it is important to test backups, but I learned it is also important to test failures during backups!

]]>
Pim Kunis
Using Ansible to alter Kernel Parameters2023-06-19T09:31:00+02:002023-06-19T09:31:00+02:00http://localhost:4000/ansible-edit-grubFor months, I’ve had a peculiar problem with my laptop: once in a while, seemingly without reason, my laptop screen would freeze. +This only happened on my laptop screen, and not on an external monitor. +I had kind of learned to live with it as I couldn’t find a solution online. +The only remedy I had was reloading my window manager, which would often unfreeze the screen.

+

Yesterday I tried Googling once more and I actually found a thread about it on the Arch Linux forums! +They talk about the same laptop model, the Lenovo ThinkPad x260, having the problem. +Fortunately, they also propose a temporary fix.

+

Trying the Fix

+

Apparently, a problem with the Panel Self Refresh (PSR) feature of Intel iGPUs is the culprit. +According to the Linux source code, PSR enables the display to go into a lower standby mode when the sytem is idle but the screen is in use. +These lower standby modes can reduce power usage of your device when idling.

+

This all seems useful, except when it makes your screen freeze! +The proposed fix disables the PSR feature entirely. +To do this, we need to change a parameter to the Intel Graphics Linux Kernel Module (LKM). +The LKM for Intel Graphics is called i915. +There are multiple ways to change kernel parameters, but I chose to edit my Grub configuration.

+

First, I wanted to test whether it actually works. +When booting into my Linux partition via Grub, you can press e to edit the Grub definition. +Somewhere there, you can find the linux command which specifies to boot Linux and how to do that. +I simply appended the option i915.enable_psr=0 to this line. +After rebooting, I noticed my screen no longer freezes! +Success!

+

Persisting the Fix

+

To make the change permanent, we need to permanently change Grub’s configuration. +One way to do this, is by changing Grub’s defaults in /etc/default/grub. +Namely, the GRUB_CMDLINE_LINUX_DEFAULT option specifies what options Grub should pass to the Linux kernel by default. +For me, this is a nice solution as the problem exists for both Linux OSes I have installed. +I changed this option to:

+
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i915.enable_psr=0"
+
+

Next, I wanted to automate this solution using Ansible. +This turned out to be quite easy, as the Grub configuration looks a bit like an ini file (maybe it is?):

+
- name: Edit grub to disable Panel Self Refresh
+  become: true
+  ini_file:
+    path: /etc/default/grub
+    section: null
+    option: "GRUB_CMDLINE_LINUX_DEFAULT"
+    value: '"quiet splash i915.enable_psr=0"'
+    no_extra_spaces: true
+  notify: update grub
+
+

Lastly, I created the notify hook to update the Grub configuration:

+
- name: update grub
+  become: true
+  command:
+    cmd: update-grub
+
+

Update: Just use Nix

+

Lately, I have been learning a bit of NixOS with the intention of replacing my current setup. +Compared to Ansible, applying this fix is a breeze on NixOS:

+
{
+  boot.kernelParams = [ "i915.enable_psr=0" ];
+}
+
+

That’s it, yep.

+

Conclusion

+

It turned out to be quite easy to change Linux kernel parameters using Ansible. +Maybe some kernel gurus have better ways to change parameters, but this works for me for now.

+

As a sidenote, I started reading a bit more about NixOS and realised that it can solve issues like these much more nicely than Ansible does. +I might replace my OS with NixOS some day, if I manage to rewrite my Ansible for it.

]]>
Pim Kunis
Sending Apprise Notifications from Concourse CI2023-06-14T23:39:00+02:002023-06-14T23:39:00+02:00http://localhost:4000/concourse-apprise-notifierRecently, I deployed Concourse CI because I wanted to get my feet wet with a CI/CD pipeline. +However, I had a practical use case lying around for a long time: automatically compiling my static website and deploying it to my docker Swarm. +This took some time getting right, but the result works like a charm (source code).

+

It’s comforting to know I don’t have move a finger and my website is automatically deployed. +However, I would still like to receive some indication of what’s happening. +And what’s a better way to do that, than using my Apprise service to keep me up to date. +There’s a little snag though: I could not find any Concourse resource that does this. +That’s when I decided to just create it myself.

+

The Plagiarism Hunt

+

As any good computer person, I am lazy. +I’d rather just copy someone’s work, so that’s what I did. +I found this GitHub repository that does the same thing but for Slack notifications. +For some reason it’s archived, but it seemed like it should work. +I actually noticed lots of repositories for Concourse resource types are archived, so not sure what’s going on there.

+

Getting to know Concourse

+

Let’s first understand what we need to do reach our end goal of sending Apprise notifications from Concourse.

+

A Concourse pipeline takes some inputs, performs some operations on them which result in some outputs. +These inputs and outputs are called resources in Concourse. +For example, a Git repository could be a resource. +Each resource is an instance of a resource type. +A resource type therefore is simply a blueprint that can create multiple resources. +To continue the example, a resource type could be “Git repository”.

+

We therefore need to create our own resource type that can send Apprise notifications. +A resource type is simply a container that includes three scripts:

+
    +
  • check: check for a new version of a resource
  • +
  • in: retrieve a version of the resource
  • +
  • out: create a version of the resource
  • +
+

As Apprise notifications are basically fire-and-forget, we will only implement the out script.

+

Writing the out script

+

The whole script can be found here, but I will explain the most important bits of it. +Note that I only use Apprise’s persistent storage solution, and not its stateless solution.

+

Concourse provides us with the working directory, which we cd to:

+
cd "${1}"
+
+

We create a timestamp, formatted in JSON, which we will use for the resource’s new version later. +Concourse requires us to set a version for the resource, but since Apprise notifications don’t have that, we use the timestamp:

+
timestamp="$(jq -n "{version:{timestamp:\"$(date +%s)\"}}")"
+
+

First some black magic Bash to redirect file descriptors. +Not sure why this is needed, but I copied it anyways. +After that, we create a temporary file holding resource’s parameters.

+
exec 3>&1
+exec 1>&2
+
+payload=$(mktemp /tmp/resource-in.XXXXXX)
+cat > "${payload}" <&0
+
+

We then extract the individual parameters. +The source key contains values how the resource type was specified, while the params key specifies parameters for this specific resource.

+
apprise_host="$(jq -r '.source.host' < "${payload}")"
+apprise_key="$(jq -r '.source.key' < "${payload}")"
+
+alert_body="$(jq -r '.params.body' < "${payload}")"
+alert_title="$(jq -r '.params.title // null' < "${payload}")"
+alert_type="$(jq -r '.params.type // null' < "${payload}")"
+alert_tag="$(jq -r '.params.tag // null' < "${payload}")"
+alert_format="$(jq -r '.params.format // null' < "${payload}")"
+
+

We then format the different parameters using JSON:

+
alert_body="$(eval "printf \"${alert_body}\"" | jq -R -s .)"
+[ "${alert_title}" != "null" ] && alert_title="$(eval "printf \"${alert_title}\"" | jq -R -s .)"
+[ "${alert_type}" != "null" ] && alert_type="$(eval "printf \"${alert_type}\"" | jq -R -s .)"
+[ "${alert_tag}" != "null" ] && alert_tag="$(eval "printf \"${alert_tag}\"" | jq -R -s .)"
+[ "${alert_format}" != "null" ] && alert_format="$(eval "printf \"${alert_format}\"" | jq -R -s .)"
+
+

Next, from the individual parameters we construct the final JSON message body we send to the Apprise endpoint.

+
body="$(cat <<EOF
+{
+  "body": ${alert_body},
+  "title": ${alert_title},
+  "type": ${alert_type},
+  "tag": ${alert_tag},
+  "format": ${alert_format}
+}
+EOF
+)"
+
+

Before sending it just yet, we compact the JSON and remove any values that are null:

+
compact_body="$(echo "${body}" | jq -c '.')"
+echo "$compact_body" | jq 'del(..|nulls)' > /tmp/compact_body.json
+
+

Here is the most important line, where we send the payload to the Apprise endpoint. +It’s quite straight-forward.

+
curl -v -X POST -T /tmp/compact_body.json -H "Content-Type: application/json" "${apprise_host}/notify/${apprise_key}"
+
+

Finally, we print the timestamp (fake version) in order to appease the Concourse gods.

+
echo "${timestamp}" >&3
+
+

Building the Container

+

As said earlier, to actually use this script, we need to add it to a image. +I won’t be explaining this whole process, but the source can be found here. +The most important take-aways are these:

+
    +
  • Use concourse/oci-build-task to build a image from a Dockerfile.
  • +
  • Use registry-image to push the image to an image registry.
  • +
+

Using the Resource Type

+

Using our newly created resource type is surprisingly simple. +I use it for the blog you are reading right now and the pipeline definition can be found here. +Here we specify the resource type in a Concourse pipeline:

+
resource_types:
+- name: apprise
+  type: registry-image
+  source:
+    repository: git.kun.is/pim/concourse-apprise-notifier
+    tag: "1.1.1"
+
+

We simply have to tell Concourse where to find the image, and which tag we want. +Next, we instantiate the resource type to create a resource:

+
resources:
+- name: apprise-notification
+  type: apprise
+  source:
+    host: https://apprise.kun.is:444
+    key: concourse
+  icon: bell
+
+

We simply specify the host to send Apprise notifications to. +Yeah, I even gave it a little bell because it’s cute.

+

All that’s left to do, is actually send the notification. +Let’s see how that is done:

+
- name: deploy-static-website
+  plan:
+    - task: deploy-site
+      config: ...
+
+      on_success:
+	put: apprise-notification
+	params:
+	  title: "Static website deployed!"
+	  body: "New version: $(cat version/version)"
+	no_get: true
+
+

As can be seen, the Apprise notification can be triggered when a task is executed successfully. +We do this using the put command, which execute the out script underwater. +We set the notification’s title and body, and send it! +The result is seen below in my Ntfy app, which Apprise forwards the message to: +picture showing my Ntfy app with the Apprise notification

+

And to finish this off, here is what it looks like in the Concourse web UI: +the concourse web gui showing the pipeline of my static website including the the apprise notification resources

+

Conclusion

+

Concourse’s way of representing everything as an image/container is really interesting in my opinion. +A resource type is quite easily implemented as well, although Bash might not be the optimal way to do this. +I’ve seen some people implement it in Rust, which might be a good excuse to finally learn that language :)

+

Apart from Apprise notifications, I’m planning on creating a resource type to deploy to a Docker swarm eventually. +This seems like a lot harder than simply sending notifications though.

]]>
Pim Kunis
My Experiences with virtio-9p2023-05-31T14:18:00+02:002023-05-31T14:18:00+02:00http://localhost:4000/virtio-9p-experiencesWhen I was scaling up my home lab, I started thinking more about data management. +I hadn’t (and still haven’t) set up any form of network storage. +I have, however, set up a backup mechanism using Borg. +Still, I want to operate lots of virtual machines, and backing up each one of them separately seemed excessive. +So I started thinking, what if I just let the host machines back up the data? +After all, the amount of physical hosts I have in my home lab is unlikely to increase drastically.

+

The Use Case for Sharing Directories

+

I started working out this idea further. +Without network storage, I needed a way for guest VMs to access the host’s disks. +Here there are two possibilities, either expose some block device or a file system. +Creating a whole virtual disk for just the data of some VMs seemed wasteful, and from my experiences also increases backup times dramatically. +I therefore searched for a way to mount a directory from the host OS on the guest VM. +This is when I stumbled upon this blog post talking about sharing directories with virtual machines.

+

Sharing Directories with virtio-9p

+

virtio-9p is a way to map a directory on the host OS to a special device on the virtual machine. +In virt-manager, it looks like the following: +picture showing virt-manager configuration to map a directory to a VM +Under the hood, virtio-9p uses the 9pnet protocol. +Originally developed at Bell Labs, support for this is available in all modern Linux kernels. +If you share a directory with a VM, you can then mount it. +Below is an extract of my /etc/fstab to automatically mount the directory:

+
data	/mnt/data	9p	trans=virtio,rw	0	0
+
+

The first argument (data) refers to the name you gave this share from the host +With the trans option we specify that this is a virtio share.

+

Problems with virtio-9p

+

At first I had no problems with my setup, but I am now contemplating just moving to a network storage based setup because of two problems.

+

The first problem is that some files have suddenly changed ownership from libvirt-qemu to root. +If the file is owned by root, the guest OS can still see it, but cannot access it. +I am not entirely sure the problem lies with virtio, but I suspect it is. +For anyone experiencing this problem, I wrote a small shell script to revert ownership to the libvirt-qemu user:

+
find -printf "%h/%f %u\n"  | grep root | cut -d ' ' -f1 | xargs chown libvirt-qemu:libvirt-qemu
+
+

Another problem that I have experienced, is guests being unable to mount the directory at all. +I have only experienced this problem once, but it was highly annoying. +To fix it, I had to reboot the whole physical machine.

+

Alternatives

+

virtio-9p seemed like a good idea, but as discussed, I had some problems with it. +It seems virtioFS might be a an interesting alternative as it is designed specifically for sharing directories with VMs.

+

As for me, I will probably finally look into deploying network storage either with NFS or SSHFS.

]]>
Pim Kunis
Homebrew SSH Certificate Authority for the Terraform Libvirt Provider2023-05-23T11:14:00+02:002023-05-23T11:14:00+02:00http://localhost:4000/homebrew-ssh-caEver SSH’ed into a freshly installed server and gotten the following annoying message?

+
The authenticity of host 'host.tld (1.2.3.4)' can't be established.
+ED25519 key fingerprint is SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8.
+Are you sure you want to continue connecting (yes/no)?
+
+

Or even more annoying:

+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
+Someone could be eavesdropping on you right now (man-in-the-middle attack)!
+It is also possible that a host key has just been changed.
+The fingerprint for the ED25519 key sent by the remote host is
+SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8.
+Please contact your system administrator.
+Add correct host key in /home/user/.ssh/known_hosts to get rid of this message.
+Offending ED25519 key in /home/user/.ssh/known_hosts:3
+  remove with:
+  ssh-keygen -f "/etc/ssh/ssh_known_hosts" -R "1.2.3.4"
+ED25519 host key for 1.2.3.4 has changed and you have requested strict checking.
+Host key verification failed.
+
+

Could it be that the programmers at OpenSSH simply like to annoy us with these confusing messages? +Maybe, but these warnings also serve as a way to notify users of a potential Man-in-the-Middle (MITM) attack. +I won’t go into the details of this problem, but I refer you to this excellent blog post. +Instead, I would like to talk about ways to solve these annoying warnings.

+

One obvious solution is simply to add each host to your known_hosts file. +This works okay when managing a handful of servers, but becomes unbearable when managing many servers. +In my case, I wanted to quickly spin up virtual machines using Duncan Mac-Vicar’s Terraform Libvirt provider, without having to accept their host key before connecting. +The solution? Issuing SSH host certificates using an SSH certificate authority.

+

SSH Certificate Authorities vs. the Web

+

The idea of an SSH certificate authority (CA) is quite easy to grasp, if you understand the web’s Public Key Infrastructure (PKI). +Just like with the web, a trusted party can issue certificates that are offered when establishing a connection. +The idea is, just by trusting the trusted party, you trust every certificate they issue. +In the case of the web’s PKI, this trusted party is bundled and trusted by your browser or operating system. +However, in the case of SSH, the trusted party is you! (Okay you can also trust your own web certificate authority) +With this great power, comes great responsibility which we will abuse heavily in this article.

+

SSH Certificate Authority for Terraform

+

So, let’s start with a plan. +I want to spawn virtual machines with Terraform which which are automatically provisioned with a SSH host certificate issued by my CA. +This CA will be another host on my private network, issuing certificates over SSH.

+

Fetching the SSH Host Certificate

+

First we generate an SSH key pair in Terraform. +Below is the code for that:

+
resource "tls_private_key" "debian" {
+  algorithm = "ED25519"
+}
+
+data "tls_public_key" "debian" {
+  private_key_pem = tls_private_key.debian.private_key_pem
+}
+
+

Now that we have an SSH key pair, we need to somehow make Terraform communicate this with the CA. +Lucky for us, there is a way for Terraform to execute an arbitrary command with the external data feature. +We call this script below:

+
data "external" "cert" {
+  program = ["bash", "${path.module}/get_cert.sh"]
+
+  query = {
+    pubkey   = trimspace(data.tls_public_key.debian.public_key_openssh)
+    host     = var.name
+    cahost   = var.ca_host
+    cascript = var.ca_script
+    cakey    = var.ca_key
+  }
+}
+
+

These query parameters will end up in the script’s stdin in JSON format. +We can then read these parameters, and send them to the CA over SSH. +The result must as well be in JSON format.

+
#!/bin/bash
+set -euo pipefail
+IFS=$'\n\t'
+
+# Read the query parameters
+eval "$(jq -r '@sh "PUBKEY=\(.pubkey) HOST=\(.host) CAHOST=\(.cahost) CASCRIPT=\(.cascript) CAKEY=\(.cakey)"')"
+
+# Fetch certificate from the CA
+# Warning: extremely ugly code that I am to lazy to fix
+CERT=$(ssh -o ConnectTimeout=3 -o ConnectionAttempts=1 root@$CAHOST '"'"$CASCRIPT"'" host "'"$CAKEY"'" "'"$PUBKEY"'" "'"$HOST"'".dmz')
+
+jq -n --arg cert "$CERT" '{"cert":$cert}'
+
+

We see that a script is called on the remote host that issues the certificate. +This is just a simple wrapper around ssh-keygen, which you can see below.

+
#!/bin/bash
+set -euo pipefail
+IFS=$'\n\t'
+
+host() {
+	CAKEY="$2"
+	PUBKEY="$3"
+	HOST="$4"
+
+	echo "$PUBKEY" > /root/ca/"$HOST".pub
+	ssh-keygen -h -s /root/ca/keys/"$CAKEY" -I "$HOST" -n "$HOST" /root/ca/"$HOST".pub
+	cat /root/ca/"$HOST"-cert.pub
+	rm /root/ca/"$HOST"*.pub
+}
+
+"$1" "$@"
+
+

Appeasing the Terraform Gods

+

So nice, we can fetch the SSH host certificate from the CA. +We should just be able to use it right? +We can, but it brings a big annoyance with it: Terraform will fetch a new certificate every time it is run. +This is because the external feature of Terraform is a data source. +If we were to use this data source for a Terraform resource, it would need to be updated every time we run Terraform. +I have not been able to find a way to avoid fetching the certificate every time, except for writing my own resource provider which I’d rather not. +I have, however, found a way to hack around the issue.

+

The idea is as follows: we can use Terraform’s ignore_changes to, well, ignore any changes of a resource. +Unfortunately, we cannot use this for a data source, so we must create a glue null_resource that supports ignore_changes. +This is shown in the code snipppet below. +We use the triggers property simply to copy the certificate in; we don’t use it for it’s original purpose.

+
resource "null_resource" "cert" {
+  triggers = {
+    cert = data.external.cert.result["cert"]
+  }
+
+  lifecycle {
+    ignore_changes = [
+      triggers
+    ]
+  }
+}
+
+

And voilà, we can now use null_resource.cert.triggers["cert"] as our certificate, that won’t trigger replacements in Terraform.

+

Setting the Host Certificate with Cloud-Init

+

Terraform’s Libvirt provider has native support for Cloud-Init, which is very handy. +We can give the host certificate directly to Cloud-Init and place it on the virtual machine. +Inside the Cloud-Init configuration, we can set the ssh_keys property to do this:

+
ssh_keys:
+  ed25519_private: |
+    ${indent(4, private_key)}
+  ed25519_certificate: "${host_cert}"
+
+

I hardcoded this to ED25519 keys, because this is all I use.

+

This works perfectly, and I never have to accept host certificates from virtual machines again.

+

Caveats

+

A sharp eye might have noticed the lifecycle of these host certificates is severely lacking. +Namely, the deployed host certificates have no expiration date nore is there revocation function. +There are ways to implement these, but for my home lab I did not deem this necessary at this point. +In a more professional environment, I would suggest using Hashicorp’s Vault.

+

This project did teach me about the limits and flexibility of Terraform, so all in all a success! +All code can be found on the git repository here.

]]>
Pim Kunis
\ No newline at end of file diff --git a/src/_site/fluent-bit-memory/index.html b/src/_site/fluent-bit-memory/index.html new file mode 100644 index 0000000..b1eecfd --- /dev/null +++ b/src/_site/fluent-bit-memory/index.html @@ -0,0 +1,19 @@ + Monitoring Correct Memory Usage in Fluent Bit - Pim Kunis

Monitoring Correct Memory Usage in Fluent Bit

Previously, I have used Prometheus’ node_exporter to monitor the memory usage of my servers. However, I am currently in the process of moving away from Prometheus to a new Monioring stack. While I understand the advantages, I felt like Prometheus’ pull architecture does not scale nicely. Everytime I spin up a new machine, I would have to centrally change Prometheus’ configuration in order for it to query the new server.

In order to collect metrics from my servers, I am now using Fluent Bit. I love Fluent Bit’s way of configuration which I can easily express as code and automate, its focus on effiency and being vendor agnostic. However, I have stumbled upon one, in my opinion, big issue with Fluent Bit: its mem plugin to monitor memory usage is completely useless. In this post I will go over the problem and my temporary solution.

The Problem with Fluent Bit’s mem Plugin

As can be seen in the documentation, Fluent Bit’s mem input plugin exposes a few metrics regarding memory usage which should be self-explaining: Mem.total, Mem.used, Mem.free, Swap.total, Swap.used and Swap.free. The problem is that Mem.used and Mem.free do not accurately reflect the machine’s actual memory usage. This is because these metrics include caches and buffers, which can be reclaimed by other processes if needed. Most tools reporting memory usage therefore include an additional metric that specifices the memory available on the system. For example, the command free -m reports the following data on my laptop:

               total        used        free      shared  buff/cache   available
+Mem:           15864        3728        7334         518        5647       12136
+Swap:           2383         663        1720
+

Notice that the available memory is more than free memory.

While the issue is known (see this and this link), it is unfortunately not yet fixed.

A Temporary Solution

The issues I linked previously provide stand-alone plugins that fix the problem, which will hopefully be merged in the official project at some point. However, I didn’t want to install another plugin so I used Fluent Bit’s exec input plugin and the free Linux command to query memory usage like so:

[INPUT]
+    Name exec
+    Tag memory
+    Command free -m | tail -2 | tr '\n' ' '
+    Interval_Sec 1
+

To interpret the command’s output, I created the following filter:

[FILTER]
+    Name parser
+    Match memory
+    Key_Name exec
+    Parser free
+

Lastly, I created the following parser (warning: regex shitcode incoming):

[PARSER]
+    Name free
+    Format regex
+    Regex ^Mem:\s+(?<mem_total>\d+)\s+(?<mem_used>\d+)\s+(?<mem_free>\d+)\s+(?<mem_shared>\d+)\s+(?<mem_buff_cache>\d+)\s+(?<mem_available>\d+) Swap:\s+(?<swap_total>\d+)\s+(?<swap_used>\d+)\s+(?<swap_free>\d+)
+    Types mem_total:integer mem_used:integer mem_free:integer mem_shared:integer mem_buff_cache:integer mem_available:integer swap_total:integer swap_used:integer
+

With this configuration, you can use the mem_available metric to get accurate memory usage in Fluent Bit.

Conclusion

Let’s hope Fluent Bit’s mem input plugin is improved upon soon so this hacky solution is not needed. I also intend to document my new monitoring pipeline, which at the moment consists of:

  • Fluent Bit
  • Fluentd
  • Elasticsearch
  • Grafana
diff --git a/src/_site/hashicorp-license-change/index.html b/src/_site/hashicorp-license-change/index.html new file mode 100644 index 0000000..f0452f0 --- /dev/null +++ b/src/_site/hashicorp-license-change/index.html @@ -0,0 +1 @@ + Hashicorp's License Change and my Home Lab - Update - Pim Kunis

Hashicorp's License Change and my Home Lab - Update

See the Update at the end of the article.

Already a week ago, Hashicorp announced it would change the license on almost all its projects. Unlike their previous license, which was the Mozilla Public License 2.0, their new license is no longer truly open source. It is called the Business Source License™ and restricts use of their software for competitors. In their own words:

Vendors who provide competitive services built on our community products will no longer be able to incorporate future releases, bug fixes, or security patches contributed to our products.

I found a great article by MeshedInsights that names this behaviour the “rights ratchet model”. They define a script start-ups use to garner the interest of open source enthusiasts but eventually turn their back on them for profit. The reason why Hashicorp can do this, is because contributors signed a copyright license agreement (CLA). This agreement transfers the copyright of contributors’ code to Hashicorp, allowing them to change the license if they want to.

I find this action really regrettable because I like their products. This sort of action was also why I wanted to avoid using an Elastic stack, which also had their license changed.1 These companies do not respect their contributors and the software stack beneath they built their product on, which is actually open source (Golang, Linux, etc.).

Impact on my Home Lab

I am using Terraform in my home lab to manage several important things:

  • Libvirt virtual machines
  • PowerDNS records
  • Elasticsearch configuration

With Hashicorp’s anti open source move, I intend to move away from Terraform in the future. While I will not use Hashicorp’s products for new personal projects, I will leave my current setup as-is for some time because there is no real need to quickly migrate.

I might also investigate some of Terraform’s competitors, like Pulumi. Hopefully there is a project that respects open source which I can use in the future.

Update

A promising fork of Terraform has been announced called OpenTF. They intend to take part of the Cloud Native Computing Foundation, which I think is a good effort because Terraform is so important for modern cloud infrastructures.

Footnotes

  1. While I am still using Elasticsearch, I don’t use the rest of the Elastic stack in order to prevent a vendor lock-in. ↩

diff --git a/src/_site/homebrew-ssh-ca/index.html b/src/_site/homebrew-ssh-ca/index.html new file mode 100644 index 0000000..5ef63bd --- /dev/null +++ b/src/_site/homebrew-ssh-ca/index.html @@ -0,0 +1,83 @@ + Homebrew SSH Certificate Authority for the Terraform Libvirt Provider - Pim Kunis

Homebrew SSH Certificate Authority for the Terraform Libvirt Provider

Ever SSH’ed into a freshly installed server and gotten the following annoying message?

The authenticity of host 'host.tld (1.2.3.4)' can't be established.
+ED25519 key fingerprint is SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8.
+Are you sure you want to continue connecting (yes/no)?
+

Or even more annoying:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
+Someone could be eavesdropping on you right now (man-in-the-middle attack)!
+It is also possible that a host key has just been changed.
+The fingerprint for the ED25519 key sent by the remote host is
+SHA256:eUXGdm1YdsMAS7vkdx6dOJdOGHdem5gQp4tadCfdLB8.
+Please contact your system administrator.
+Add correct host key in /home/user/.ssh/known_hosts to get rid of this message.
+Offending ED25519 key in /home/user/.ssh/known_hosts:3
+  remove with:
+  ssh-keygen -f "/etc/ssh/ssh_known_hosts" -R "1.2.3.4"
+ED25519 host key for 1.2.3.4 has changed and you have requested strict checking.
+Host key verification failed.
+

Could it be that the programmers at OpenSSH simply like to annoy us with these confusing messages? Maybe, but these warnings also serve as a way to notify users of a potential Man-in-the-Middle (MITM) attack. I won’t go into the details of this problem, but I refer you to this excellent blog post. Instead, I would like to talk about ways to solve these annoying warnings.

One obvious solution is simply to add each host to your known_hosts file. This works okay when managing a handful of servers, but becomes unbearable when managing many servers. In my case, I wanted to quickly spin up virtual machines using Duncan Mac-Vicar’s Terraform Libvirt provider, without having to accept their host key before connecting. The solution? Issuing SSH host certificates using an SSH certificate authority.

SSH Certificate Authorities vs. the Web

The idea of an SSH certificate authority (CA) is quite easy to grasp, if you understand the web’s Public Key Infrastructure (PKI). Just like with the web, a trusted party can issue certificates that are offered when establishing a connection. The idea is, just by trusting the trusted party, you trust every certificate they issue. In the case of the web’s PKI, this trusted party is bundled and trusted by your browser or operating system. However, in the case of SSH, the trusted party is you! (Okay you can also trust your own web certificate authority) With this great power, comes great responsibility which we will abuse heavily in this article.

SSH Certificate Authority for Terraform

So, let’s start with a plan. I want to spawn virtual machines with Terraform which which are automatically provisioned with a SSH host certificate issued by my CA. This CA will be another host on my private network, issuing certificates over SSH.

Fetching the SSH Host Certificate

First we generate an SSH key pair in Terraform. Below is the code for that:

resource "tls_private_key" "debian" {
+  algorithm = "ED25519"
+}
+
+data "tls_public_key" "debian" {
+  private_key_pem = tls_private_key.debian.private_key_pem
+}
+

Now that we have an SSH key pair, we need to somehow make Terraform communicate this with the CA. Lucky for us, there is a way for Terraform to execute an arbitrary command with the external data feature. We call this script below:

data "external" "cert" {
+  program = ["bash", "${path.module}/get_cert.sh"]
+
+  query = {
+    pubkey   = trimspace(data.tls_public_key.debian.public_key_openssh)
+    host     = var.name
+    cahost   = var.ca_host
+    cascript = var.ca_script
+    cakey    = var.ca_key
+  }
+}
+

These query parameters will end up in the script’s stdin in JSON format. We can then read these parameters, and send them to the CA over SSH. The result must as well be in JSON format.

#!/bin/bash
+set -euo pipefail
+IFS=$'\n\t'
+
+# Read the query parameters
+eval "$(jq -r '@sh "PUBKEY=\(.pubkey) HOST=\(.host) CAHOST=\(.cahost) CASCRIPT=\(.cascript) CAKEY=\(.cakey)"')"
+
+# Fetch certificate from the CA
+# Warning: extremely ugly code that I am to lazy to fix
+CERT=$(ssh -o ConnectTimeout=3 -o ConnectionAttempts=1 root@$CAHOST '"'"$CASCRIPT"'" host "'"$CAKEY"'" "'"$PUBKEY"'" "'"$HOST"'".dmz')
+
+jq -n --arg cert "$CERT" '{"cert":$cert}'
+

We see that a script is called on the remote host that issues the certificate. This is just a simple wrapper around ssh-keygen, which you can see below.

#!/bin/bash
+set -euo pipefail
+IFS=$'\n\t'
+
+host() {
+	CAKEY="$2"
+	PUBKEY="$3"
+	HOST="$4"
+
+	echo "$PUBKEY" > /root/ca/"$HOST".pub
+	ssh-keygen -h -s /root/ca/keys/"$CAKEY" -I "$HOST" -n "$HOST" /root/ca/"$HOST".pub
+	cat /root/ca/"$HOST"-cert.pub
+	rm /root/ca/"$HOST"*.pub
+}
+
+"$1" "$@"
+

Appeasing the Terraform Gods

So nice, we can fetch the SSH host certificate from the CA. We should just be able to use it right? We can, but it brings a big annoyance with it: Terraform will fetch a new certificate every time it is run. This is because the external feature of Terraform is a data source. If we were to use this data source for a Terraform resource, it would need to be updated every time we run Terraform. I have not been able to find a way to avoid fetching the certificate every time, except for writing my own resource provider which I’d rather not. I have, however, found a way to hack around the issue.

The idea is as follows: we can use Terraform’s ignore_changes to, well, ignore any changes of a resource. Unfortunately, we cannot use this for a data source, so we must create a glue null_resource that supports ignore_changes. This is shown in the code snipppet below. We use the triggers property simply to copy the certificate in; we don’t use it for it’s original purpose.

resource "null_resource" "cert" {
+  triggers = {
+    cert = data.external.cert.result["cert"]
+  }
+
+  lifecycle {
+    ignore_changes = [
+      triggers
+    ]
+  }
+}
+

And voilà, we can now use null_resource.cert.triggers["cert"] as our certificate, that won’t trigger replacements in Terraform.

Setting the Host Certificate with Cloud-Init

Terraform’s Libvirt provider has native support for Cloud-Init, which is very handy. We can give the host certificate directly to Cloud-Init and place it on the virtual machine. Inside the Cloud-Init configuration, we can set the ssh_keys property to do this:

ssh_keys:
+  ed25519_private: |
+    ${indent(4, private_key)}
+  ed25519_certificate: "${host_cert}"
+

I hardcoded this to ED25519 keys, because this is all I use.

This works perfectly, and I never have to accept host certificates from virtual machines again.

Caveats

A sharp eye might have noticed the lifecycle of these host certificates is severely lacking. Namely, the deployed host certificates have no expiration date nore is there revocation function. There are ways to implement these, but for my home lab I did not deem this necessary at this point. In a more professional environment, I would suggest using Hashicorp’s Vault.

This project did teach me about the limits and flexibility of Terraform, so all in all a success! All code can be found on the git repository here.

diff --git a/src/_site/index.html b/src/_site/index.html new file mode 100644 index 0000000..6b45c7f --- /dev/null +++ b/src/_site/index.html @@ -0,0 +1 @@ + Pim Kunis
pim

Pim Kunis

A pig's gotta fly

Recent Posts

diff --git a/src/_site/infrastructure-snapshot/index.html b/src/_site/infrastructure-snapshot/index.html new file mode 100644 index 0000000..9187eca --- /dev/null +++ b/src/_site/infrastructure-snapshot/index.html @@ -0,0 +1,41 @@ + Home Lab Infrastructure Snapshot August 2023 - Pim Kunis

Home Lab Infrastructure Snapshot August 2023

I have been meaning to write about the current state of my home lab infrastructure for a while now. Now that the most important parts are quite stable, I think the opportunity is ripe. I expect this post to get quite long, so I might have to leave out some details along the way.

This post will be a starting point for future infrastructure snapshots which I can hopefully put out periodically. That is, if there is enough worth talking about.

Keep an eye out for the icon, which links to the source code and configuration of anything mentioned. Oh yeah, did I mention everything I do is open source?

Networking and Infrastructure Overview

Hardware and Operating Systems

Let’s start with the basics: what kind of hardware do I use for my home lab? The most important servers are my three Gigabyte Brix GB-BLCE-4105. Two of them have 16 GB of memory, and one 8 GB. I named these servers as follows:

  • Atlas: because this server was going to “lift” a lot of virtual machines.
  • Lewis: we started out with a “Max” server named after the Formula 1 driver Max Verstappen, but it kind of became an unmanagable behemoth without infrastructure-as-code. Our second server we subsequently named Lewis after his colleague Lewis Hamilton. Note: people around me vetoed these names and I am no F1 fan!
  • Jefke: it’s a funny Belgian name. That’s all.

Here is a picture of them sitting in their cosy closet:

A picture of my servers.

If you look look to the left, you will also see a Raspberry pi 4B. I use this Pi to do some rudimentary monitoring whether servers and services are running. More on this in the relevant section below. The Pi is called Iris because it’s a messenger for the other servers.

I used to run Ubuntu on these systems, but I have since migrated away to Debian. The main reasons were Canonical putting advertisements in my terminal and pushing Snap which has a proprietry backend. Two of my servers run the newly released Debian Bookworm, while one still runs Debian Bullseye.

Networking

For networking, I wanted hypervisors and virtual machines separated by VLANs for security reasons. The following picture shows a simplified view of the VLANs present in my home lab:

Picture showing the VLANS in my home lab.

All virtual machines are connected to a virtual bridge which tags network traffic with the DMZ VLAN. The hypervisors VLAN is used for traffic to and from the hypervisors. Devices from the hypervisors VLAN are allowed to connect to devices in the DMZ, but not vice versa. The hypervisors are connected to a switch using a trunk link, allows both DMZ and hypervisors traffic.

I realised the above design using ifupdown. Below is the configuration for each hypervisor, which creates a new enp3s0.30 interface with all DMZ traffic from the enp3s0 interface .

auto enp3s0.30
+iface enp3s0.30 inet manual
+iface enp3s0.30 inet6 auto
+	accept_ra 0
+	dhcp 0
+	request_prefix 0
+	privext 0
+	pre-up sysctl -w net/ipv6/conf/enp3s0.30/disable_ipv6=1
+

This configuration seems more complex than it actually is. Most of it is to make sure the interface is not assigned an IPv4/6 address on the hypervisor host. The magic .30 at the end of the interface name makes this interface tagged with VLAN ID 30 (DMZ for me).

Now that we have an interface tagged for the DMZ VLAN, we can create a bridge where future virtual machines can connect to:

auto dmzbr
+iface dmzbr inet manual
+	bridge_ports enp3s0.30
+	bridge_stp off
+iface dmzbr inet6 auto
+	accept_ra 0
+	dhcp 0
+	request_prefix 0
+	privext 0
+	pre-up sysctl -w net/ipv6/conf/dmzbr/disable_ipv6=1
+

Just like the previous config, this is quite bloated because I don’t want the interface to be assigned an IP address on the host. Most importantly, the bridge_ports enp3s0.30 line here makes this interface a virtual bridge for the enp3s0.30 interface.

And voilĂ , we now have a virtual bridge on each machine, where only DMZ traffic will flow. Here I verify whether this configuration works:

Show

We can see that the two virtual interfaces are created, and are only assigned a MAC address and not a IP address:

root@atlas:~# ip a show enp3s0.30
+4: enp3s0.30@enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master dmzbr state UP group default qlen 1000
+    link/ether d8:5e:d3:4c:70:38 brd ff:ff:ff:ff:ff:ff
+5: dmzbr: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
+    link/ether 4e:f7:1f:0f:ad:17 brd ff:ff:ff:ff:ff:ff
+

Pinging a VM from a hypervisor works:

root@atlas:~# ping -c1 maestro.dmz
+PING maestro.dmz (192.168.30.8) 56(84) bytes of data.
+64 bytes from 192.168.30.8 (192.168.30.8): icmp_seq=1 ttl=63 time=0.457 ms
+

Pinging a hypervisor from a VM does not work:

root@maestro:~# ping  -c1 atlas.hyp
+PING atlas.hyp (192.168.40.2) 56(84) bytes of data.
+
+--- atlas.hyp ping statistics ---
+1 packets transmitted, 0 received, 100% packet loss, time 0ms
+

DNS and DHCP

Now that we have a working DMZ network, let’s build on it to get DNS and DHCP working. This will enable new virtual machines to obtain a static or dynamic IP address and register their host in DNS. This has actually been incredibly annoying due to our friend Network address translation (NAT).

NAT recap

Network address translation (NAT) is a function of a router which allows multiple hosts to share a single IP address. This is needed for IPv4, because IPv4 addresses are scarce and usually one household is only assigned a single IPv4 address. This is one of the problems IPv6 attempts to solve (mainly by having so many IP addresses that they should never run out). To solve the problem for IPv4, each host in a network is assigned a private IPv4 address, which can be reused for every network.

Then, the router must perform address translation. It does this by keeping track of ports opened by hosts in its private network. If a packet from the internet arrives at the router for such a port, it forwards this packet to the correct host.

I would like to host my own DNS on a virtual machine (called hermes, more on VMs later) in the DMZ network. This basically gives two problems:

  1. The upstream DNS server will refer to the public internet-accessible IP address of our DNS server. This IP-address has no meaning inside the private network due to NAT and the router will reject the packet.
  2. Our DNS resolves hosts to their public internet-accessible IP address. This is similar to the previous problem as the public IP address has no meaning.

The first problem can be remediated by overriding the location of the DNS server for hosts inside the DMZ network. This can be achieved on my router, which uses Unbound as its recursive DNS server:

Unbound overides for kun.is and dmz domains.

Any DNS requests to Unbound to domains in either dmz or kun.is will now be forwarded 192.168.30.7 (port 5353). This is the virtual machine hosting my DNS.

The second problem can be solved at the DNS server. We need to do some magic overriding, which dnsmasq is perfect for :

alias=84.245.14.149,192.168.30.8
+server=/kun.is/192.168.30.7
+

This always overrides the public IPv4 address to the private one. It also overrides the DNS server for kun.is to 192.168.30.7.

Finally, behind the dnsmasq server, I run Powerdns as authoritative DNS server . I like this DNS server because I can manage it with Terraform .

Here is a small diagram showing my setup (my networking teacher would probably kill me for this): Shitty diagram showing my DNS setup.

Virtualization

https://github.com/containrrr/shepherd Now that we have laid out the basic networking, let’s talk virtualization. Each of my servers are configured to run KVM virtual machines, orchestrated using Libvirt. Configuration of the physical hypervisor servers, including KVM/Libvirt is done using Ansible. The VMs are spun up using Terraform and the dmacvicar/libvirt Terraform provider.

This all isn’t too exciting, except that I created a Terraform module that abstracts the Terraform Libvirt provider for my specific scenario :

module "maestro" {
+  source          = "git::https://git.kun.is/home/tf-modules.git//debian"
+  name            = "maestro"
+  domain_name     = "tf-maestro"
+  memory          = 10240
+  mac             = "CA:FE:C0:FF:EE:08"
+}
+

This automatically creates a Debian virtual machines with the properties specified. It also sets up certificate-based SSH authentication which I talked about before.

Clustering

With virtualization explained, let’s move up one level further. Each of my three physical servers hosts a virtual machine running Docker, which together form a Docker Swarm. I use Traefik as a reverse proxy which routes requests to the correct container.

All data is hosted on a single machine and made available to containers using NFS. This might not be very secure (as NFS is not encrypted and no proper authentication), it is quite fast.

As of today, I host the following services on my Docker Swarm :

CI / CD

For CI / CD, I run Concourse CI in a separate VM. This is needed, because Concourse heavily uses containers to create reproducible builds.

Although I should probably use it for more, I currently use my Concourse for three pipelines:

  • A pipeline to build this static website and create a container image of it. The image is then uploaded to the image registry of my Forgejo instance. I love it when I can use stuff I previously built :) The pipeline finally deploys this new image to the Docker Swarm .
  • A pipeline to create a Concourse resource that sends Apprise alerts (Concourse-ception?)
  • A pipeline to build a custom Fluentd image with plugins installed

Backups

To create backups, I use Borg. As I keep all data on one machine, this backup process is quite simple. In fact, all this data is stored in a single Libvirt volume. To configure Borg with a simple declarative script, I use Borgmatic.

In order to back up the data inside the Libvirt volume, I create a snapshot to a file. Then I can mount this snapshot in my file system. The files can then be backed up while the system is still running. It is also possible to simply back up the Libvirt image, but this takes more time and storage .

Monitoring and Alerting

The last topic I would like to talk about is monitoring and alerting. This is something I’m still actively improving and only just set up properly.

Alerting

For alerting, I wanted something that runs entirely on my own infrastructure. I settled for Apprise + Ntfy.

Apprise is a server that is able to send notifications to dozens of services. For application developers, it is thus only necessary to implement the Apprise API to gain access to all these services. The Apprise API itself is also very simple. By using Apprise, I can also easily switch to another notification service later. Ntfy is free software made for mobile push notifications.

I use this alerting system in quite a lot of places in my infrastructure, for example when creating backups.

Uptime Monitoring

The first monitoring setup I created, was using Uptime Kuma. Uptime Kuma periodically pings a service to see whether it is still running. You can do a literal ping, test HTTP response codes, check database connectivity and much more. I use it to check whether my services and VMs are online. And the best part is, Uptime Kuma supports Apprise so I get push notifications on my phone whenever something goes down!

Metrics and Log Monitoring

A new monitoring system I am still in the process of deploying is focused on metrics and logs. I plan on creating a separate blog post about this, so keep an eye out on that (for example using RSS :)). Safe to say, it is no basic ELK stack!

Conclusion

That’s it for now! Hopefully I inspired someone to build something… or how not to :)

diff --git a/src/_site/infrastructure-snapshot/nat.png b/src/_site/infrastructure-snapshot/nat.png new file mode 100644 index 0000000..0d5f72c Binary files /dev/null and b/src/_site/infrastructure-snapshot/nat.png differ diff --git a/src/_site/infrastructure-snapshot/servers.jpeg b/src/_site/infrastructure-snapshot/servers.jpeg new file mode 100644 index 0000000..b269484 Binary files /dev/null and b/src/_site/infrastructure-snapshot/servers.jpeg differ diff --git a/src/_site/infrastructure-snapshot/unbound_overrides.png b/src/_site/infrastructure-snapshot/unbound_overrides.png new file mode 100644 index 0000000..f94394f Binary files /dev/null and b/src/_site/infrastructure-snapshot/unbound_overrides.png differ diff --git a/src/_site/infrastructure-snapshot/vlans.png b/src/_site/infrastructure-snapshot/vlans.png new file mode 100644 index 0000000..7bf4add Binary files /dev/null and b/src/_site/infrastructure-snapshot/vlans.png differ diff --git a/src/_site/its-alive/index.html b/src/_site/its-alive/index.html new file mode 100644 index 0000000..ed3f035 --- /dev/null +++ b/src/_site/its-alive/index.html @@ -0,0 +1 @@ + It's alive! - Pim Kunis

It's alive!

Finally, after several months this website is up and running again!

My homelab has completely changed, but the reason why it initially went offline is because of my failing CI installation. I was using Concourse CI which I was initially interested in due to the reproducible nature of its builds using containers. However, for some reason pipelines were sporadically getting stuck when I reboot the virtual machine it was running on. The fix was very annoying: I had to re-create the pipelines manually (which feels very backwards for a CI/CD system!) Additionally, my virtual machine setup back then was also quite fragile and I decided to get rid of that as well.

I have learned that having an escape hatch to deploy something is probably a good idea đź… Expect a new overview of my homelab soon, in the same vein as this post from last year!

diff --git a/src/_site/now.json b/src/_site/now.json new file mode 100644 index 0000000..b6dcfd5 --- /dev/null +++ b/src/_site/now.json @@ -0,0 +1,7 @@ +{ + "version": 2, + "routes": [ + { "handle": "filesystem" }, + { "src": "/.*", "status": 404, "dest": "404.html" } + ] +} diff --git a/src/_site/robots.txt b/src/_site/robots.txt new file mode 100644 index 0000000..d297064 --- /dev/null +++ b/src/_site/robots.txt @@ -0,0 +1 @@ +Sitemap: http://localhost:4000/sitemap.xml diff --git a/src/_site/sitemap.xml b/src/_site/sitemap.xml new file mode 100644 index 0000000..cf9eeba --- /dev/null +++ b/src/_site/sitemap.xml @@ -0,0 +1,51 @@ + + + +http://localhost:4000/homebrew-ssh-ca/ +2023-05-23T11:14:00+02:00 + + +http://localhost:4000/virtio-9p-experiences/ +2023-05-31T14:18:00+02:00 + + +http://localhost:4000/concourse-apprise-notifier/ +2023-06-14T23:39:00+02:00 + + +http://localhost:4000/ansible-edit-grub/ +2023-06-19T09:31:00+02:00 + + +http://localhost:4000/backup-failure/ +2023-08-08T11:51:00+02:00 + + +http://localhost:4000/fluent-bit-memory/ +2023-08-09T16:19:00+02:00 + + +http://localhost:4000/hashicorp-license-change/ +2023-08-17T18:15:00+02:00 + + +http://localhost:4000/infrastructure-snapshot/ +2023-08-27T22:23:00+02:00 + + +http://localhost:4000/its-alive/ +2024-04-21T10:02:00+02:00 + + +http://localhost:4000/about/ + + +http://localhost:4000/archive/ + + +http://localhost:4000/ + + +http://localhost:4000/tags/ + + diff --git a/src/_site/tags/index.html b/src/_site/tags/index.html new file mode 100644 index 0000000..b321f96 --- /dev/null +++ b/src/_site/tags/index.html @@ -0,0 +1 @@ + Tags - Pim Kunis

Tags.

diff --git a/src/_site/virtio-9p-experiences/index.html b/src/_site/virtio-9p-experiences/index.html new file mode 100644 index 0000000..d37388a --- /dev/null +++ b/src/_site/virtio-9p-experiences/index.html @@ -0,0 +1,3 @@ + My Experiences with virtio-9p - Pim Kunis

My Experiences with virtio-9p

When I was scaling up my home lab, I started thinking more about data management. I hadn’t (and still haven’t) set up any form of network storage. I have, however, set up a backup mechanism using Borg. Still, I want to operate lots of virtual machines, and backing up each one of them separately seemed excessive. So I started thinking, what if I just let the host machines back up the data? After all, the amount of physical hosts I have in my home lab is unlikely to increase drastically.

The Use Case for Sharing Directories

I started working out this idea further. Without network storage, I needed a way for guest VMs to access the host’s disks. Here there are two possibilities, either expose some block device or a file system. Creating a whole virtual disk for just the data of some VMs seemed wasteful, and from my experiences also increases backup times dramatically. I therefore searched for a way to mount a directory from the host OS on the guest VM. This is when I stumbled upon this blog post talking about sharing directories with virtual machines.

Sharing Directories with virtio-9p

virtio-9p is a way to map a directory on the host OS to a special device on the virtual machine. In virt-manager, it looks like the following: picture showing virt-manager configuration to map a directory to a VM Under the hood, virtio-9p uses the 9pnet protocol. Originally developed at Bell Labs, support for this is available in all modern Linux kernels. If you share a directory with a VM, you can then mount it. Below is an extract of my /etc/fstab to automatically mount the directory:

data	/mnt/data	9p	trans=virtio,rw	0	0
+

The first argument (data) refers to the name you gave this share from the host With the trans option we specify that this is a virtio share.

Problems with virtio-9p

At first I had no problems with my setup, but I am now contemplating just moving to a network storage based setup because of two problems.

The first problem is that some files have suddenly changed ownership from libvirt-qemu to root. If the file is owned by root, the guest OS can still see it, but cannot access it. I am not entirely sure the problem lies with virtio, but I suspect it is. For anyone experiencing this problem, I wrote a small shell script to revert ownership to the libvirt-qemu user:

find -printf "%h/%f %u\n"  | grep root | cut -d ' ' -f1 | xargs chown libvirt-qemu:libvirt-qemu
+

Another problem that I have experienced, is guests being unable to mount the directory at all. I have only experienced this problem once, but it was highly annoying. To fix it, I had to reboot the whole physical machine.

Alternatives

virtio-9p seemed like a good idea, but as discussed, I had some problems with it. It seems virtioFS might be a an interesting alternative as it is designed specifically for sharing directories with VMs.

As for me, I will probably finally look into deploying network storage either with NFS or SSHFS.

diff --git a/src/_site/virtio-9p-experiences/virt-manager.png b/src/_site/virtio-9p-experiences/virt-manager.png new file mode 100644 index 0000000..8567efb Binary files /dev/null and b/src/_site/virtio-9p-experiences/virt-manager.png differ diff --git a/src/about.md b/src/about.md new file mode 100644 index 0000000..292ca93 --- /dev/null +++ b/src/about.md @@ -0,0 +1,9 @@ +--- +title: Me +permalink: /about/ +layout: page +excerpt: Free PIIs +comments: false +--- + +Here I might post some personally identifiable information. diff --git a/src/archive.html b/src/archive.html new file mode 100644 index 0000000..f030c57 --- /dev/null +++ b/src/archive.html @@ -0,0 +1,29 @@ +--- +title: Archive +permalink: /archive/ +layout: page +excerpt: All posts. +comments: false +--- + +
+ + +
+ +
    + +{%- for post in site.posts -%} + {%- capture current_year -%}{{ post.date | date: "%Y" }}{%- endcapture -%} + {%- unless current_year == previous_year -%} +

    {{ current_year }}

    + {%- assign previous_year = current_year -%} + {%- endunless -%} + +{%- endfor -%} diff --git a/src/assets/css/fontawesome.all.min.css b/src/assets/css/fontawesome.all.min.css new file mode 100644 index 0000000..84dbeb8 --- /dev/null +++ b/src/assets/css/fontawesome.all.min.css @@ -0,0 +1,6 @@ +/*! + * Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2022 Fonticons, Inc. + */ +.fa{font-family:var(--fa-style-family,"Font Awesome 6 Free");font-weight:var(--fa-style,900)}.fa,.fa-brands,.fa-classic,.fa-regular,.fa-sharp,.fa-solid,.fab,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:var(--fa-display,inline-block);font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-classic,.fa-regular,.fa-solid,.far,.fas{font-family:"Font Awesome 6 Free"}.fa-brands,.fab{font-family:"Font Awesome 6 Brands"}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.08em) var(--fa-border-style,solid) var(--fa-border-color,#eee);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{-webkit-animation-name:fa-beat;animation-name:fa-beat;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{-webkit-animation-name:fa-bounce;animation-name:fa-bounce;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{-webkit-animation-name:fa-fade;animation-name:fa-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{-webkit-animation-name:fa-beat-fade;animation-name:fa-beat-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{-webkit-animation-name:fa-flip;animation-name:fa-flip;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{-webkit-animation-name:fa-shake;animation-name:fa-shake;-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-duration:var(--fa-animation-duration,2s);animation-duration:var(--fa-animation-duration,2s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,steps(8));animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{-webkit-animation-delay:-1ms;animation-delay:-1ms;-webkit-animation-duration:1ms;animation-duration:1ms;-webkit-animation-iteration-count:1;animation-iteration-count:1;transition-delay:0s;transition-duration:0s}}@-webkit-keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@-webkit-keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@-webkit-keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@-webkit-keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@-webkit-keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@-webkit-keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}.fa-rotate-by{-webkit-transform:rotate(var(--fa-rotate-angle,none));transform:rotate(var(--fa-rotate-angle,none))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)}.fa-0:before{content:"\30"}.fa-1:before{content:"\31"}.fa-2:before{content:"\32"}.fa-3:before{content:"\33"}.fa-4:before{content:"\34"}.fa-5:before{content:"\35"}.fa-6:before{content:"\36"}.fa-7:before{content:"\37"}.fa-8:before{content:"\38"}.fa-9:before{content:"\39"}.fa-fill-drip:before{content:"\f576"}.fa-arrows-to-circle:before{content:"\e4bd"}.fa-chevron-circle-right:before,.fa-circle-chevron-right:before{content:"\f138"}.fa-at:before{content:"\40"}.fa-trash-alt:before,.fa-trash-can:before{content:"\f2ed"}.fa-text-height:before{content:"\f034"}.fa-user-times:before,.fa-user-xmark:before{content:"\f235"}.fa-stethoscope:before{content:"\f0f1"}.fa-comment-alt:before,.fa-message:before{content:"\f27a"}.fa-info:before{content:"\f129"}.fa-compress-alt:before,.fa-down-left-and-up-right-to-center:before{content:"\f422"}.fa-explosion:before{content:"\e4e9"}.fa-file-alt:before,.fa-file-lines:before,.fa-file-text:before{content:"\f15c"}.fa-wave-square:before{content:"\f83e"}.fa-ring:before{content:"\f70b"}.fa-building-un:before{content:"\e4d9"}.fa-dice-three:before{content:"\f527"}.fa-calendar-alt:before,.fa-calendar-days:before{content:"\f073"}.fa-anchor-circle-check:before{content:"\e4aa"}.fa-building-circle-arrow-right:before{content:"\e4d1"}.fa-volleyball-ball:before,.fa-volleyball:before{content:"\f45f"}.fa-arrows-up-to-line:before{content:"\e4c2"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-circle-minus:before,.fa-minus-circle:before{content:"\f056"}.fa-door-open:before{content:"\f52b"}.fa-right-from-bracket:before,.fa-sign-out-alt:before{content:"\f2f5"}.fa-atom:before{content:"\f5d2"}.fa-soap:before{content:"\e06e"}.fa-heart-music-camera-bolt:before,.fa-icons:before{content:"\f86d"}.fa-microphone-alt-slash:before,.fa-microphone-lines-slash:before{content:"\f539"}.fa-bridge-circle-check:before{content:"\e4c9"}.fa-pump-medical:before{content:"\e06a"}.fa-fingerprint:before{content:"\f577"}.fa-hand-point-right:before{content:"\f0a4"}.fa-magnifying-glass-location:before,.fa-search-location:before{content:"\f689"}.fa-forward-step:before,.fa-step-forward:before{content:"\f051"}.fa-face-smile-beam:before,.fa-smile-beam:before{content:"\f5b8"}.fa-flag-checkered:before{content:"\f11e"}.fa-football-ball:before,.fa-football:before{content:"\f44e"}.fa-school-circle-exclamation:before{content:"\e56c"}.fa-crop:before{content:"\f125"}.fa-angle-double-down:before,.fa-angles-down:before{content:"\f103"}.fa-users-rectangle:before{content:"\e594"}.fa-people-roof:before{content:"\e537"}.fa-people-line:before{content:"\e534"}.fa-beer-mug-empty:before,.fa-beer:before{content:"\f0fc"}.fa-diagram-predecessor:before{content:"\e477"}.fa-arrow-up-long:before,.fa-long-arrow-up:before{content:"\f176"}.fa-burn:before,.fa-fire-flame-simple:before{content:"\f46a"}.fa-male:before,.fa-person:before{content:"\f183"}.fa-laptop:before{content:"\f109"}.fa-file-csv:before{content:"\f6dd"}.fa-menorah:before{content:"\f676"}.fa-truck-plane:before{content:"\e58f"}.fa-record-vinyl:before{content:"\f8d9"}.fa-face-grin-stars:before,.fa-grin-stars:before{content:"\f587"}.fa-bong:before{content:"\f55c"}.fa-pastafarianism:before,.fa-spaghetti-monster-flying:before{content:"\f67b"}.fa-arrow-down-up-across-line:before{content:"\e4af"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f2e5"}.fa-jar-wheat:before{content:"\e517"}.fa-envelopes-bulk:before,.fa-mail-bulk:before{content:"\f674"}.fa-file-circle-exclamation:before{content:"\e4eb"}.fa-circle-h:before,.fa-hospital-symbol:before{content:"\f47e"}.fa-pager:before{content:"\f815"}.fa-address-book:before,.fa-contact-book:before{content:"\f2b9"}.fa-strikethrough:before{content:"\f0cc"}.fa-k:before{content:"\4b"}.fa-landmark-flag:before{content:"\e51c"}.fa-pencil-alt:before,.fa-pencil:before{content:"\f303"}.fa-backward:before{content:"\f04a"}.fa-caret-right:before{content:"\f0da"}.fa-comments:before{content:"\f086"}.fa-file-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-code-pull-request:before{content:"\e13c"}.fa-clipboard-list:before{content:"\f46d"}.fa-truck-loading:before,.fa-truck-ramp-box:before{content:"\f4de"}.fa-user-check:before{content:"\f4fc"}.fa-vial-virus:before{content:"\e597"}.fa-sheet-plastic:before{content:"\e571"}.fa-blog:before{content:"\f781"}.fa-user-ninja:before{content:"\f504"}.fa-person-arrow-up-from-line:before{content:"\e539"}.fa-scroll-torah:before,.fa-torah:before{content:"\f6a0"}.fa-broom-ball:before,.fa-quidditch-broom-ball:before,.fa-quidditch:before{content:"\f458"}.fa-toggle-off:before{content:"\f204"}.fa-archive:before,.fa-box-archive:before{content:"\f187"}.fa-person-drowning:before{content:"\e545"}.fa-arrow-down-9-1:before,.fa-sort-numeric-desc:before,.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-face-grin-tongue-squint:before,.fa-grin-tongue-squint:before{content:"\f58a"}.fa-spray-can:before{content:"\f5bd"}.fa-truck-monster:before{content:"\f63b"}.fa-w:before{content:"\57"}.fa-earth-africa:before,.fa-globe-africa:before{content:"\f57c"}.fa-rainbow:before{content:"\f75b"}.fa-circle-notch:before{content:"\f1ce"}.fa-tablet-alt:before,.fa-tablet-screen-button:before{content:"\f3fa"}.fa-paw:before{content:"\f1b0"}.fa-cloud:before{content:"\f0c2"}.fa-trowel-bricks:before{content:"\e58a"}.fa-face-flushed:before,.fa-flushed:before{content:"\f579"}.fa-hospital-user:before{content:"\f80d"}.fa-tent-arrow-left-right:before{content:"\e57f"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-binoculars:before{content:"\f1e5"}.fa-microphone-slash:before{content:"\f131"}.fa-box-tissue:before{content:"\e05b"}.fa-motorcycle:before{content:"\f21c"}.fa-bell-concierge:before,.fa-concierge-bell:before{content:"\f562"}.fa-pen-ruler:before,.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-arrows-left-right:before,.fa-people-arrows:before{content:"\e068"}.fa-mars-and-venus-burst:before{content:"\e523"}.fa-caret-square-right:before,.fa-square-caret-right:before{content:"\f152"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-sun-plant-wilt:before{content:"\e57a"}.fa-toilets-portable:before{content:"\e584"}.fa-hockey-puck:before{content:"\f453"}.fa-table:before{content:"\f0ce"}.fa-magnifying-glass-arrow-right:before{content:"\e521"}.fa-digital-tachograph:before,.fa-tachograph-digital:before{content:"\f566"}.fa-users-slash:before{content:"\e073"}.fa-clover:before{content:"\e139"}.fa-mail-reply:before,.fa-reply:before{content:"\f3e5"}.fa-star-and-crescent:before{content:"\f699"}.fa-house-fire:before{content:"\e50c"}.fa-minus-square:before,.fa-square-minus:before{content:"\f146"}.fa-helicopter:before{content:"\f533"}.fa-compass:before{content:"\f14e"}.fa-caret-square-down:before,.fa-square-caret-down:before{content:"\f150"}.fa-file-circle-question:before{content:"\e4ef"}.fa-laptop-code:before{content:"\f5fc"}.fa-swatchbook:before{content:"\f5c3"}.fa-prescription-bottle:before{content:"\f485"}.fa-bars:before,.fa-navicon:before{content:"\f0c9"}.fa-people-group:before{content:"\e533"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-heart-broken:before,.fa-heart-crack:before{content:"\f7a9"}.fa-external-link-square-alt:before,.fa-square-up-right:before{content:"\f360"}.fa-face-kiss-beam:before,.fa-kiss-beam:before{content:"\f597"}.fa-film:before{content:"\f008"}.fa-ruler-horizontal:before{content:"\f547"}.fa-people-robbery:before{content:"\e536"}.fa-lightbulb:before{content:"\f0eb"}.fa-caret-left:before{content:"\f0d9"}.fa-circle-exclamation:before,.fa-exclamation-circle:before{content:"\f06a"}.fa-school-circle-xmark:before{content:"\e56d"}.fa-arrow-right-from-bracket:before,.fa-sign-out:before{content:"\f08b"}.fa-chevron-circle-down:before,.fa-circle-chevron-down:before{content:"\f13a"}.fa-unlock-alt:before,.fa-unlock-keyhole:before{content:"\f13e"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-headphones-alt:before,.fa-headphones-simple:before{content:"\f58f"}.fa-sitemap:before{content:"\f0e8"}.fa-circle-dollar-to-slot:before,.fa-donate:before{content:"\f4b9"}.fa-memory:before{content:"\f538"}.fa-road-spikes:before{content:"\e568"}.fa-fire-burner:before{content:"\e4f1"}.fa-flag:before{content:"\f024"}.fa-hanukiah:before{content:"\f6e6"}.fa-feather:before{content:"\f52d"}.fa-volume-down:before,.fa-volume-low:before{content:"\f027"}.fa-comment-slash:before{content:"\f4b3"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-compress:before{content:"\f066"}.fa-wheat-alt:before,.fa-wheat-awn:before{content:"\e2cd"}.fa-ankh:before{content:"\f644"}.fa-hands-holding-child:before{content:"\e4fa"}.fa-asterisk:before{content:"\2a"}.fa-check-square:before,.fa-square-check:before{content:"\f14a"}.fa-peseta-sign:before{content:"\e221"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-ghost:before{content:"\f6e2"}.fa-list-squares:before,.fa-list:before{content:"\f03a"}.fa-phone-square-alt:before,.fa-square-phone-flip:before{content:"\f87b"}.fa-cart-plus:before{content:"\f217"}.fa-gamepad:before{content:"\f11b"}.fa-circle-dot:before,.fa-dot-circle:before{content:"\f192"}.fa-dizzy:before,.fa-face-dizzy:before{content:"\f567"}.fa-egg:before{content:"\f7fb"}.fa-house-medical-circle-xmark:before{content:"\e513"}.fa-campground:before{content:"\f6bb"}.fa-folder-plus:before{content:"\f65e"}.fa-futbol-ball:before,.fa-futbol:before,.fa-soccer-ball:before{content:"\f1e3"}.fa-paint-brush:before,.fa-paintbrush:before{content:"\f1fc"}.fa-lock:before{content:"\f023"}.fa-gas-pump:before{content:"\f52f"}.fa-hot-tub-person:before,.fa-hot-tub:before{content:"\f593"}.fa-map-location:before,.fa-map-marked:before{content:"\f59f"}.fa-house-flood-water:before{content:"\e50e"}.fa-tree:before{content:"\f1bb"}.fa-bridge-lock:before{content:"\e4cc"}.fa-sack-dollar:before{content:"\f81d"}.fa-edit:before,.fa-pen-to-square:before{content:"\f044"}.fa-car-side:before{content:"\f5e4"}.fa-share-alt:before,.fa-share-nodes:before{content:"\f1e0"}.fa-heart-circle-minus:before{content:"\e4ff"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-microscope:before{content:"\f610"}.fa-sink:before{content:"\e06d"}.fa-bag-shopping:before,.fa-shopping-bag:before{content:"\f290"}.fa-arrow-down-z-a:before,.fa-sort-alpha-desc:before,.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-mitten:before{content:"\f7b5"}.fa-person-rays:before{content:"\e54d"}.fa-users:before{content:"\f0c0"}.fa-eye-slash:before{content:"\f070"}.fa-flask-vial:before{content:"\e4f3"}.fa-hand-paper:before,.fa-hand:before{content:"\f256"}.fa-om:before{content:"\f679"}.fa-worm:before{content:"\e599"}.fa-house-circle-xmark:before{content:"\e50b"}.fa-plug:before{content:"\f1e6"}.fa-chevron-up:before{content:"\f077"}.fa-hand-spock:before{content:"\f259"}.fa-stopwatch:before{content:"\f2f2"}.fa-face-kiss:before,.fa-kiss:before{content:"\f596"}.fa-bridge-circle-xmark:before{content:"\e4cb"}.fa-face-grin-tongue:before,.fa-grin-tongue:before{content:"\f589"}.fa-chess-bishop:before{content:"\f43a"}.fa-face-grin-wink:before,.fa-grin-wink:before{content:"\f58c"}.fa-deaf:before,.fa-deafness:before,.fa-ear-deaf:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-road-circle-check:before{content:"\e564"}.fa-dice-five:before{content:"\f523"}.fa-rss-square:before,.fa-square-rss:before{content:"\f143"}.fa-land-mine-on:before{content:"\e51b"}.fa-i-cursor:before{content:"\f246"}.fa-stamp:before{content:"\f5bf"}.fa-stairs:before{content:"\e289"}.fa-i:before{content:"\49"}.fa-hryvnia-sign:before,.fa-hryvnia:before{content:"\f6f2"}.fa-pills:before{content:"\f484"}.fa-face-grin-wide:before,.fa-grin-alt:before{content:"\f581"}.fa-tooth:before{content:"\f5c9"}.fa-v:before{content:"\56"}.fa-bangladeshi-taka-sign:before{content:"\e2e6"}.fa-bicycle:before{content:"\f206"}.fa-rod-asclepius:before,.fa-rod-snake:before,.fa-staff-aesculapius:before,.fa-staff-snake:before{content:"\e579"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-ambulance:before,.fa-truck-medical:before{content:"\f0f9"}.fa-wheat-awn-circle-exclamation:before{content:"\e598"}.fa-snowman:before{content:"\f7d0"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-road-barrier:before{content:"\e562"}.fa-school:before{content:"\f549"}.fa-igloo:before{content:"\f7ae"}.fa-joint:before{content:"\f595"}.fa-angle-right:before{content:"\f105"}.fa-horse:before{content:"\f6f0"}.fa-q:before{content:"\51"}.fa-g:before{content:"\47"}.fa-notes-medical:before{content:"\f481"}.fa-temperature-2:before,.fa-temperature-half:before,.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-dong-sign:before{content:"\e169"}.fa-capsules:before{content:"\f46b"}.fa-poo-bolt:before,.fa-poo-storm:before{content:"\f75a"}.fa-face-frown-open:before,.fa-frown-open:before{content:"\f57a"}.fa-hand-point-up:before{content:"\f0a6"}.fa-money-bill:before{content:"\f0d6"}.fa-bookmark:before{content:"\f02e"}.fa-align-justify:before{content:"\f039"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-helmet-un:before{content:"\e503"}.fa-bullseye:before{content:"\f140"}.fa-bacon:before{content:"\f7e5"}.fa-hand-point-down:before{content:"\f0a7"}.fa-arrow-up-from-bracket:before{content:"\e09a"}.fa-folder-blank:before,.fa-folder:before{content:"\f07b"}.fa-file-medical-alt:before,.fa-file-waveform:before{content:"\f478"}.fa-radiation:before{content:"\f7b9"}.fa-chart-simple:before{content:"\e473"}.fa-mars-stroke:before{content:"\f229"}.fa-vial:before{content:"\f492"}.fa-dashboard:before,.fa-gauge-med:before,.fa-gauge:before,.fa-tachometer-alt-average:before{content:"\f624"}.fa-magic-wand-sparkles:before,.fa-wand-magic-sparkles:before{content:"\e2ca"}.fa-e:before{content:"\45"}.fa-pen-alt:before,.fa-pen-clip:before{content:"\f305"}.fa-bridge-circle-exclamation:before{content:"\e4ca"}.fa-user:before{content:"\f007"}.fa-school-circle-check:before{content:"\e56b"}.fa-dumpster:before{content:"\f793"}.fa-shuttle-van:before,.fa-van-shuttle:before{content:"\f5b6"}.fa-building-user:before{content:"\e4da"}.fa-caret-square-left:before,.fa-square-caret-left:before{content:"\f191"}.fa-highlighter:before{content:"\f591"}.fa-key:before{content:"\f084"}.fa-bullhorn:before{content:"\f0a1"}.fa-globe:before{content:"\f0ac"}.fa-synagogue:before{content:"\f69b"}.fa-person-half-dress:before{content:"\e548"}.fa-road-bridge:before{content:"\e563"}.fa-location-arrow:before{content:"\f124"}.fa-c:before{content:"\43"}.fa-tablet-button:before{content:"\f10a"}.fa-building-lock:before{content:"\e4d6"}.fa-pizza-slice:before{content:"\f818"}.fa-money-bill-wave:before{content:"\f53a"}.fa-area-chart:before,.fa-chart-area:before{content:"\f1fe"}.fa-house-flag:before{content:"\e50d"}.fa-person-circle-minus:before{content:"\e540"}.fa-ban:before,.fa-cancel:before{content:"\f05e"}.fa-camera-rotate:before{content:"\e0d8"}.fa-air-freshener:before,.fa-spray-can-sparkles:before{content:"\f5d0"}.fa-star:before{content:"\f005"}.fa-repeat:before{content:"\f363"}.fa-cross:before{content:"\f654"}.fa-box:before{content:"\f466"}.fa-venus-mars:before{content:"\f228"}.fa-arrow-pointer:before,.fa-mouse-pointer:before{content:"\f245"}.fa-expand-arrows-alt:before,.fa-maximize:before{content:"\f31e"}.fa-charging-station:before{content:"\f5e7"}.fa-shapes:before,.fa-triangle-circle-square:before{content:"\f61f"}.fa-random:before,.fa-shuffle:before{content:"\f074"}.fa-person-running:before,.fa-running:before{content:"\f70c"}.fa-mobile-retro:before{content:"\e527"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-spider:before{content:"\f717"}.fa-hands-bound:before{content:"\e4f9"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-plane-circle-exclamation:before{content:"\e556"}.fa-x-ray:before{content:"\f497"}.fa-spell-check:before{content:"\f891"}.fa-slash:before{content:"\f715"}.fa-computer-mouse:before,.fa-mouse:before{content:"\f8cc"}.fa-arrow-right-to-bracket:before,.fa-sign-in:before{content:"\f090"}.fa-shop-slash:before,.fa-store-alt-slash:before{content:"\e070"}.fa-server:before{content:"\f233"}.fa-virus-covid-slash:before{content:"\e4a9"}.fa-shop-lock:before{content:"\e4a5"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-blender-phone:before{content:"\f6b6"}.fa-building-wheat:before{content:"\e4db"}.fa-person-breastfeeding:before{content:"\e53a"}.fa-right-to-bracket:before,.fa-sign-in-alt:before{content:"\f2f6"}.fa-venus:before{content:"\f221"}.fa-passport:before{content:"\f5ab"}.fa-heart-pulse:before,.fa-heartbeat:before{content:"\f21e"}.fa-people-carry-box:before,.fa-people-carry:before{content:"\f4ce"}.fa-temperature-high:before{content:"\f769"}.fa-microchip:before{content:"\f2db"}.fa-crown:before{content:"\f521"}.fa-weight-hanging:before{content:"\f5cd"}.fa-xmarks-lines:before{content:"\e59a"}.fa-file-prescription:before{content:"\f572"}.fa-weight-scale:before,.fa-weight:before{content:"\f496"}.fa-user-friends:before,.fa-user-group:before{content:"\f500"}.fa-arrow-up-a-z:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-chess-knight:before{content:"\f441"}.fa-face-laugh-squint:before,.fa-laugh-squint:before{content:"\f59b"}.fa-wheelchair:before{content:"\f193"}.fa-arrow-circle-up:before,.fa-circle-arrow-up:before{content:"\f0aa"}.fa-toggle-on:before{content:"\f205"}.fa-person-walking:before,.fa-walking:before{content:"\f554"}.fa-l:before{content:"\4c"}.fa-fire:before{content:"\f06d"}.fa-bed-pulse:before,.fa-procedures:before{content:"\f487"}.fa-shuttle-space:before,.fa-space-shuttle:before{content:"\f197"}.fa-face-laugh:before,.fa-laugh:before{content:"\f599"}.fa-folder-open:before{content:"\f07c"}.fa-heart-circle-plus:before{content:"\e500"}.fa-code-fork:before{content:"\e13b"}.fa-city:before{content:"\f64f"}.fa-microphone-alt:before,.fa-microphone-lines:before{content:"\f3c9"}.fa-pepper-hot:before{content:"\f816"}.fa-unlock:before{content:"\f09c"}.fa-colon-sign:before{content:"\e140"}.fa-headset:before{content:"\f590"}.fa-store-slash:before{content:"\e071"}.fa-road-circle-xmark:before{content:"\e566"}.fa-user-minus:before{content:"\f503"}.fa-mars-stroke-up:before,.fa-mars-stroke-v:before{content:"\f22a"}.fa-champagne-glasses:before,.fa-glass-cheers:before{content:"\f79f"}.fa-clipboard:before{content:"\f328"}.fa-house-circle-exclamation:before{content:"\e50a"}.fa-file-arrow-up:before,.fa-file-upload:before{content:"\f574"}.fa-wifi-3:before,.fa-wifi-strong:before,.fa-wifi:before{content:"\f1eb"}.fa-bath:before,.fa-bathtub:before{content:"\f2cd"}.fa-underline:before{content:"\f0cd"}.fa-user-edit:before,.fa-user-pen:before{content:"\f4ff"}.fa-signature:before{content:"\f5b7"}.fa-stroopwafel:before{content:"\f551"}.fa-bold:before{content:"\f032"}.fa-anchor-lock:before{content:"\e4ad"}.fa-building-ngo:before{content:"\e4d7"}.fa-manat-sign:before{content:"\e1d5"}.fa-not-equal:before{content:"\f53e"}.fa-border-style:before,.fa-border-top-left:before{content:"\f853"}.fa-map-location-dot:before,.fa-map-marked-alt:before{content:"\f5a0"}.fa-jedi:before{content:"\f669"}.fa-poll:before,.fa-square-poll-vertical:before{content:"\f681"}.fa-mug-hot:before{content:"\f7b6"}.fa-battery-car:before,.fa-car-battery:before{content:"\f5df"}.fa-gift:before{content:"\f06b"}.fa-dice-two:before{content:"\f528"}.fa-chess-queen:before{content:"\f445"}.fa-glasses:before{content:"\f530"}.fa-chess-board:before{content:"\f43c"}.fa-building-circle-check:before{content:"\e4d2"}.fa-person-chalkboard:before{content:"\e53d"}.fa-mars-stroke-h:before,.fa-mars-stroke-right:before{content:"\f22b"}.fa-hand-back-fist:before,.fa-hand-rock:before{content:"\f255"}.fa-caret-square-up:before,.fa-square-caret-up:before{content:"\f151"}.fa-cloud-showers-water:before{content:"\e4e4"}.fa-bar-chart:before,.fa-chart-bar:before{content:"\f080"}.fa-hands-bubbles:before,.fa-hands-wash:before{content:"\e05e"}.fa-less-than-equal:before{content:"\f537"}.fa-train:before{content:"\f238"}.fa-eye-low-vision:before,.fa-low-vision:before{content:"\f2a8"}.fa-crow:before{content:"\f520"}.fa-sailboat:before{content:"\e445"}.fa-window-restore:before{content:"\f2d2"}.fa-plus-square:before,.fa-square-plus:before{content:"\f0fe"}.fa-torii-gate:before{content:"\f6a1"}.fa-frog:before{content:"\f52e"}.fa-bucket:before{content:"\e4cf"}.fa-image:before{content:"\f03e"}.fa-microphone:before{content:"\f130"}.fa-cow:before{content:"\f6c8"}.fa-caret-up:before{content:"\f0d8"}.fa-screwdriver:before{content:"\f54a"}.fa-folder-closed:before{content:"\e185"}.fa-house-tsunami:before{content:"\e515"}.fa-square-nfi:before{content:"\e576"}.fa-arrow-up-from-ground-water:before{content:"\e4b5"}.fa-glass-martini-alt:before,.fa-martini-glass:before{content:"\f57b"}.fa-rotate-back:before,.fa-rotate-backward:before,.fa-rotate-left:before,.fa-undo-alt:before{content:"\f2ea"}.fa-columns:before,.fa-table-columns:before{content:"\f0db"}.fa-lemon:before{content:"\f094"}.fa-head-side-mask:before{content:"\e063"}.fa-handshake:before{content:"\f2b5"}.fa-gem:before{content:"\f3a5"}.fa-dolly-box:before,.fa-dolly:before{content:"\f472"}.fa-smoking:before{content:"\f48d"}.fa-compress-arrows-alt:before,.fa-minimize:before{content:"\f78c"}.fa-monument:before{content:"\f5a6"}.fa-snowplow:before{content:"\f7d2"}.fa-angle-double-right:before,.fa-angles-right:before{content:"\f101"}.fa-cannabis:before{content:"\f55f"}.fa-circle-play:before,.fa-play-circle:before{content:"\f144"}.fa-tablets:before{content:"\f490"}.fa-ethernet:before{content:"\f796"}.fa-eur:before,.fa-euro-sign:before,.fa-euro:before{content:"\f153"}.fa-chair:before{content:"\f6c0"}.fa-check-circle:before,.fa-circle-check:before{content:"\f058"}.fa-circle-stop:before,.fa-stop-circle:before{content:"\f28d"}.fa-compass-drafting:before,.fa-drafting-compass:before{content:"\f568"}.fa-plate-wheat:before{content:"\e55a"}.fa-icicles:before{content:"\f7ad"}.fa-person-shelter:before{content:"\e54f"}.fa-neuter:before{content:"\f22c"}.fa-id-badge:before{content:"\f2c1"}.fa-marker:before{content:"\f5a1"}.fa-face-laugh-beam:before,.fa-laugh-beam:before{content:"\f59a"}.fa-helicopter-symbol:before{content:"\e502"}.fa-universal-access:before{content:"\f29a"}.fa-chevron-circle-up:before,.fa-circle-chevron-up:before{content:"\f139"}.fa-lari-sign:before{content:"\e1c8"}.fa-volcano:before{content:"\f770"}.fa-person-walking-dashed-line-arrow-right:before{content:"\e553"}.fa-gbp:before,.fa-pound-sign:before,.fa-sterling-sign:before{content:"\f154"}.fa-viruses:before{content:"\e076"}.fa-square-person-confined:before{content:"\e577"}.fa-user-tie:before{content:"\f508"}.fa-arrow-down-long:before,.fa-long-arrow-down:before{content:"\f175"}.fa-tent-arrow-down-to-line:before{content:"\e57e"}.fa-certificate:before{content:"\f0a3"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-suitcase:before{content:"\f0f2"}.fa-person-skating:before,.fa-skating:before{content:"\f7c5"}.fa-filter-circle-dollar:before,.fa-funnel-dollar:before{content:"\f662"}.fa-camera-retro:before{content:"\f083"}.fa-arrow-circle-down:before,.fa-circle-arrow-down:before{content:"\f0ab"}.fa-arrow-right-to-file:before,.fa-file-import:before{content:"\f56f"}.fa-external-link-square:before,.fa-square-arrow-up-right:before{content:"\f14c"}.fa-box-open:before{content:"\f49e"}.fa-scroll:before{content:"\f70e"}.fa-spa:before{content:"\f5bb"}.fa-location-pin-lock:before{content:"\e51f"}.fa-pause:before{content:"\f04c"}.fa-hill-avalanche:before{content:"\e507"}.fa-temperature-0:before,.fa-temperature-empty:before,.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-bomb:before{content:"\f1e2"}.fa-registered:before{content:"\f25d"}.fa-address-card:before,.fa-contact-card:before,.fa-vcard:before{content:"\f2bb"}.fa-balance-scale-right:before,.fa-scale-unbalanced-flip:before{content:"\f516"}.fa-subscript:before{content:"\f12c"}.fa-diamond-turn-right:before,.fa-directions:before{content:"\f5eb"}.fa-burst:before{content:"\e4dc"}.fa-house-laptop:before,.fa-laptop-house:before{content:"\e066"}.fa-face-tired:before,.fa-tired:before{content:"\f5c8"}.fa-money-bills:before{content:"\e1f3"}.fa-smog:before{content:"\f75f"}.fa-crutch:before{content:"\f7f7"}.fa-cloud-arrow-up:before,.fa-cloud-upload-alt:before,.fa-cloud-upload:before{content:"\f0ee"}.fa-palette:before{content:"\f53f"}.fa-arrows-turn-right:before{content:"\e4c0"}.fa-vest:before{content:"\e085"}.fa-ferry:before{content:"\e4ea"}.fa-arrows-down-to-people:before{content:"\e4b9"}.fa-seedling:before,.fa-sprout:before{content:"\f4d8"}.fa-arrows-alt-h:before,.fa-left-right:before{content:"\f337"}.fa-boxes-packing:before{content:"\e4c7"}.fa-arrow-circle-left:before,.fa-circle-arrow-left:before{content:"\f0a8"}.fa-group-arrows-rotate:before{content:"\e4f6"}.fa-bowl-food:before{content:"\e4c6"}.fa-candy-cane:before{content:"\f786"}.fa-arrow-down-wide-short:before,.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-cloud-bolt:before,.fa-thunderstorm:before{content:"\f76c"}.fa-remove-format:before,.fa-text-slash:before{content:"\f87d"}.fa-face-smile-wink:before,.fa-smile-wink:before{content:"\f4da"}.fa-file-word:before{content:"\f1c2"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-arrows-h:before,.fa-arrows-left-right:before{content:"\f07e"}.fa-house-lock:before{content:"\e510"}.fa-cloud-arrow-down:before,.fa-cloud-download-alt:before,.fa-cloud-download:before{content:"\f0ed"}.fa-children:before{content:"\e4e1"}.fa-blackboard:before,.fa-chalkboard:before{content:"\f51b"}.fa-user-alt-slash:before,.fa-user-large-slash:before{content:"\f4fa"}.fa-envelope-open:before{content:"\f2b6"}.fa-handshake-alt-slash:before,.fa-handshake-simple-slash:before{content:"\e05f"}.fa-mattress-pillow:before{content:"\e525"}.fa-guarani-sign:before{content:"\e19a"}.fa-arrows-rotate:before,.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-fire-extinguisher:before{content:"\f134"}.fa-cruzeiro-sign:before{content:"\e152"}.fa-greater-than-equal:before{content:"\f532"}.fa-shield-alt:before,.fa-shield-halved:before{content:"\f3ed"}.fa-atlas:before,.fa-book-atlas:before{content:"\f558"}.fa-virus:before{content:"\e074"}.fa-envelope-circle-check:before{content:"\e4e8"}.fa-layer-group:before{content:"\f5fd"}.fa-arrows-to-dot:before{content:"\e4be"}.fa-archway:before{content:"\f557"}.fa-heart-circle-check:before{content:"\e4fd"}.fa-house-chimney-crack:before,.fa-house-damage:before{content:"\f6f1"}.fa-file-archive:before,.fa-file-zipper:before{content:"\f1c6"}.fa-square:before{content:"\f0c8"}.fa-glass-martini:before,.fa-martini-glass-empty:before{content:"\f000"}.fa-couch:before{content:"\f4b8"}.fa-cedi-sign:before{content:"\e0df"}.fa-italic:before{content:"\f033"}.fa-church:before{content:"\f51d"}.fa-comments-dollar:before{content:"\f653"}.fa-democrat:before{content:"\f747"}.fa-z:before{content:"\5a"}.fa-person-skiing:before,.fa-skiing:before{content:"\f7c9"}.fa-road-lock:before{content:"\e567"}.fa-a:before{content:"\41"}.fa-temperature-arrow-down:before,.fa-temperature-down:before{content:"\e03f"}.fa-feather-alt:before,.fa-feather-pointed:before{content:"\f56b"}.fa-p:before{content:"\50"}.fa-snowflake:before{content:"\f2dc"}.fa-newspaper:before{content:"\f1ea"}.fa-ad:before,.fa-rectangle-ad:before{content:"\f641"}.fa-arrow-circle-right:before,.fa-circle-arrow-right:before{content:"\f0a9"}.fa-filter-circle-xmark:before{content:"\e17b"}.fa-locust:before{content:"\e520"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-list-1-2:before,.fa-list-numeric:before,.fa-list-ol:before{content:"\f0cb"}.fa-person-dress-burst:before{content:"\e544"}.fa-money-check-alt:before,.fa-money-check-dollar:before{content:"\f53d"}.fa-vector-square:before{content:"\f5cb"}.fa-bread-slice:before{content:"\f7ec"}.fa-language:before{content:"\f1ab"}.fa-face-kiss-wink-heart:before,.fa-kiss-wink-heart:before{content:"\f598"}.fa-filter:before{content:"\f0b0"}.fa-question:before{content:"\3f"}.fa-file-signature:before{content:"\f573"}.fa-arrows-alt:before,.fa-up-down-left-right:before{content:"\f0b2"}.fa-house-chimney-user:before{content:"\e065"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-puzzle-piece:before{content:"\f12e"}.fa-money-check:before{content:"\f53c"}.fa-star-half-alt:before,.fa-star-half-stroke:before{content:"\f5c0"}.fa-code:before{content:"\f121"}.fa-glass-whiskey:before,.fa-whiskey-glass:before{content:"\f7a0"}.fa-building-circle-exclamation:before{content:"\e4d3"}.fa-magnifying-glass-chart:before{content:"\e522"}.fa-arrow-up-right-from-square:before,.fa-external-link:before{content:"\f08e"}.fa-cubes-stacked:before{content:"\e4e6"}.fa-krw:before,.fa-won-sign:before,.fa-won:before{content:"\f159"}.fa-virus-covid:before{content:"\e4a8"}.fa-austral-sign:before{content:"\e0a9"}.fa-f:before{content:"\46"}.fa-leaf:before{content:"\f06c"}.fa-road:before{content:"\f018"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-person-circle-plus:before{content:"\e541"}.fa-chart-pie:before,.fa-pie-chart:before{content:"\f200"}.fa-bolt-lightning:before{content:"\e0b7"}.fa-sack-xmark:before{content:"\e56a"}.fa-file-excel:before{content:"\f1c3"}.fa-file-contract:before{content:"\f56c"}.fa-fish-fins:before{content:"\e4f2"}.fa-building-flag:before{content:"\e4d5"}.fa-face-grin-beam:before,.fa-grin-beam:before{content:"\f582"}.fa-object-ungroup:before{content:"\f248"}.fa-poop:before{content:"\f619"}.fa-location-pin:before,.fa-map-marker:before{content:"\f041"}.fa-kaaba:before{content:"\f66b"}.fa-toilet-paper:before{content:"\f71e"}.fa-hard-hat:before,.fa-hat-hard:before,.fa-helmet-safety:before{content:"\f807"}.fa-eject:before{content:"\f052"}.fa-arrow-alt-circle-right:before,.fa-circle-right:before{content:"\f35a"}.fa-plane-circle-check:before{content:"\e555"}.fa-face-rolling-eyes:before,.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-object-group:before{content:"\f247"}.fa-chart-line:before,.fa-line-chart:before{content:"\f201"}.fa-mask-ventilator:before{content:"\e524"}.fa-arrow-right:before{content:"\f061"}.fa-map-signs:before,.fa-signs-post:before{content:"\f277"}.fa-cash-register:before{content:"\f788"}.fa-person-circle-question:before{content:"\e542"}.fa-h:before{content:"\48"}.fa-tarp:before{content:"\e57b"}.fa-screwdriver-wrench:before,.fa-tools:before{content:"\f7d9"}.fa-arrows-to-eye:before{content:"\e4bf"}.fa-plug-circle-bolt:before{content:"\e55b"}.fa-heart:before{content:"\f004"}.fa-mars-and-venus:before{content:"\f224"}.fa-home-user:before,.fa-house-user:before{content:"\e1b0"}.fa-dumpster-fire:before{content:"\f794"}.fa-house-crack:before{content:"\e3b1"}.fa-cocktail:before,.fa-martini-glass-citrus:before{content:"\f561"}.fa-face-surprise:before,.fa-surprise:before{content:"\f5c2"}.fa-bottle-water:before{content:"\e4c5"}.fa-circle-pause:before,.fa-pause-circle:before{content:"\f28b"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-apple-alt:before,.fa-apple-whole:before{content:"\f5d1"}.fa-kitchen-set:before{content:"\e51a"}.fa-r:before{content:"\52"}.fa-temperature-1:before,.fa-temperature-quarter:before,.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-cube:before{content:"\f1b2"}.fa-bitcoin-sign:before{content:"\e0b4"}.fa-shield-dog:before{content:"\e573"}.fa-solar-panel:before{content:"\f5ba"}.fa-lock-open:before{content:"\f3c1"}.fa-elevator:before{content:"\e16d"}.fa-money-bill-transfer:before{content:"\e528"}.fa-money-bill-trend-up:before{content:"\e529"}.fa-house-flood-water-circle-arrow-right:before{content:"\e50f"}.fa-poll-h:before,.fa-square-poll-horizontal:before{content:"\f682"}.fa-circle:before{content:"\f111"}.fa-backward-fast:before,.fa-fast-backward:before{content:"\f049"}.fa-recycle:before{content:"\f1b8"}.fa-user-astronaut:before{content:"\f4fb"}.fa-plane-slash:before{content:"\e069"}.fa-trademark:before{content:"\f25c"}.fa-basketball-ball:before,.fa-basketball:before{content:"\f434"}.fa-satellite-dish:before{content:"\f7c0"}.fa-arrow-alt-circle-up:before,.fa-circle-up:before{content:"\f35b"}.fa-mobile-alt:before,.fa-mobile-screen-button:before{content:"\f3cd"}.fa-volume-high:before,.fa-volume-up:before{content:"\f028"}.fa-users-rays:before{content:"\e593"}.fa-wallet:before{content:"\f555"}.fa-clipboard-check:before{content:"\f46c"}.fa-file-audio:before{content:"\f1c7"}.fa-burger:before,.fa-hamburger:before{content:"\f805"}.fa-wrench:before{content:"\f0ad"}.fa-bugs:before{content:"\e4d0"}.fa-rupee-sign:before,.fa-rupee:before{content:"\f156"}.fa-file-image:before{content:"\f1c5"}.fa-circle-question:before,.fa-question-circle:before{content:"\f059"}.fa-plane-departure:before{content:"\f5b0"}.fa-handshake-slash:before{content:"\e060"}.fa-book-bookmark:before{content:"\e0bb"}.fa-code-branch:before{content:"\f126"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-bridge:before{content:"\e4c8"}.fa-phone-alt:before,.fa-phone-flip:before{content:"\f879"}.fa-truck-front:before{content:"\e2b7"}.fa-cat:before{content:"\f6be"}.fa-anchor-circle-exclamation:before{content:"\e4ab"}.fa-truck-field:before{content:"\e58d"}.fa-route:before{content:"\f4d7"}.fa-clipboard-question:before{content:"\e4e3"}.fa-panorama:before{content:"\e209"}.fa-comment-medical:before{content:"\f7f5"}.fa-teeth-open:before{content:"\f62f"}.fa-file-circle-minus:before{content:"\e4ed"}.fa-tags:before{content:"\f02c"}.fa-wine-glass:before{content:"\f4e3"}.fa-fast-forward:before,.fa-forward-fast:before{content:"\f050"}.fa-face-meh-blank:before,.fa-meh-blank:before{content:"\f5a4"}.fa-parking:before,.fa-square-parking:before{content:"\f540"}.fa-house-signal:before{content:"\e012"}.fa-bars-progress:before,.fa-tasks-alt:before{content:"\f828"}.fa-faucet-drip:before{content:"\e006"}.fa-cart-flatbed:before,.fa-dolly-flatbed:before{content:"\f474"}.fa-ban-smoking:before,.fa-smoking-ban:before{content:"\f54d"}.fa-terminal:before{content:"\f120"}.fa-mobile-button:before{content:"\f10b"}.fa-house-medical-flag:before{content:"\e514"}.fa-basket-shopping:before,.fa-shopping-basket:before{content:"\f291"}.fa-tape:before{content:"\f4db"}.fa-bus-alt:before,.fa-bus-simple:before{content:"\f55e"}.fa-eye:before{content:"\f06e"}.fa-face-sad-cry:before,.fa-sad-cry:before{content:"\f5b3"}.fa-audio-description:before{content:"\f29e"}.fa-person-military-to-person:before{content:"\e54c"}.fa-file-shield:before{content:"\e4f0"}.fa-user-slash:before{content:"\f506"}.fa-pen:before{content:"\f304"}.fa-tower-observation:before{content:"\e586"}.fa-file-code:before{content:"\f1c9"}.fa-signal-5:before,.fa-signal-perfect:before,.fa-signal:before{content:"\f012"}.fa-bus:before{content:"\f207"}.fa-heart-circle-xmark:before{content:"\e501"}.fa-home-lg:before,.fa-house-chimney:before{content:"\e3af"}.fa-window-maximize:before{content:"\f2d0"}.fa-face-frown:before,.fa-frown:before{content:"\f119"}.fa-prescription:before{content:"\f5b1"}.fa-shop:before,.fa-store-alt:before{content:"\f54f"}.fa-floppy-disk:before,.fa-save:before{content:"\f0c7"}.fa-vihara:before{content:"\f6a7"}.fa-balance-scale-left:before,.fa-scale-unbalanced:before{content:"\f515"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-comment-dots:before,.fa-commenting:before{content:"\f4ad"}.fa-plant-wilt:before{content:"\e5aa"}.fa-diamond:before{content:"\f219"}.fa-face-grin-squint:before,.fa-grin-squint:before{content:"\f585"}.fa-hand-holding-dollar:before,.fa-hand-holding-usd:before{content:"\f4c0"}.fa-bacterium:before{content:"\e05a"}.fa-hand-pointer:before{content:"\f25a"}.fa-drum-steelpan:before{content:"\f56a"}.fa-hand-scissors:before{content:"\f257"}.fa-hands-praying:before,.fa-praying-hands:before{content:"\f684"}.fa-arrow-right-rotate:before,.fa-arrow-rotate-forward:before,.fa-arrow-rotate-right:before,.fa-redo:before{content:"\f01e"}.fa-biohazard:before{content:"\f780"}.fa-location-crosshairs:before,.fa-location:before{content:"\f601"}.fa-mars-double:before{content:"\f227"}.fa-child-dress:before{content:"\e59c"}.fa-users-between-lines:before{content:"\e591"}.fa-lungs-virus:before{content:"\e067"}.fa-face-grin-tears:before,.fa-grin-tears:before{content:"\f588"}.fa-phone:before{content:"\f095"}.fa-calendar-times:before,.fa-calendar-xmark:before{content:"\f273"}.fa-child-reaching:before{content:"\e59d"}.fa-head-side-virus:before{content:"\e064"}.fa-user-cog:before,.fa-user-gear:before{content:"\f4fe"}.fa-arrow-up-1-9:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-door-closed:before{content:"\f52a"}.fa-shield-virus:before{content:"\e06c"}.fa-dice-six:before{content:"\f526"}.fa-mosquito-net:before{content:"\e52c"}.fa-bridge-water:before{content:"\e4ce"}.fa-person-booth:before{content:"\f756"}.fa-text-width:before{content:"\f035"}.fa-hat-wizard:before{content:"\f6e8"}.fa-pen-fancy:before{content:"\f5ac"}.fa-digging:before,.fa-person-digging:before{content:"\f85e"}.fa-trash:before{content:"\f1f8"}.fa-gauge-simple-med:before,.fa-gauge-simple:before,.fa-tachometer-average:before{content:"\f629"}.fa-book-medical:before{content:"\f7e6"}.fa-poo:before{content:"\f2fe"}.fa-quote-right-alt:before,.fa-quote-right:before{content:"\f10e"}.fa-shirt:before,.fa-t-shirt:before,.fa-tshirt:before{content:"\f553"}.fa-cubes:before{content:"\f1b3"}.fa-divide:before{content:"\f529"}.fa-tenge-sign:before,.fa-tenge:before{content:"\f7d7"}.fa-headphones:before{content:"\f025"}.fa-hands-holding:before{content:"\f4c2"}.fa-hands-clapping:before{content:"\e1a8"}.fa-republican:before{content:"\f75e"}.fa-arrow-left:before{content:"\f060"}.fa-person-circle-xmark:before{content:"\e543"}.fa-ruler:before{content:"\f545"}.fa-align-left:before{content:"\f036"}.fa-dice-d6:before{content:"\f6d1"}.fa-restroom:before{content:"\f7bd"}.fa-j:before{content:"\4a"}.fa-users-viewfinder:before{content:"\e595"}.fa-file-video:before{content:"\f1c8"}.fa-external-link-alt:before,.fa-up-right-from-square:before{content:"\f35d"}.fa-table-cells:before,.fa-th:before{content:"\f00a"}.fa-file-pdf:before{content:"\f1c1"}.fa-bible:before,.fa-book-bible:before{content:"\f647"}.fa-o:before{content:"\4f"}.fa-medkit:before,.fa-suitcase-medical:before{content:"\f0fa"}.fa-user-secret:before{content:"\f21b"}.fa-otter:before{content:"\f700"}.fa-female:before,.fa-person-dress:before{content:"\f182"}.fa-comment-dollar:before{content:"\f651"}.fa-briefcase-clock:before,.fa-business-time:before{content:"\f64a"}.fa-table-cells-large:before,.fa-th-large:before{content:"\f009"}.fa-book-tanakh:before,.fa-tanakh:before{content:"\f827"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-clipboard-user:before{content:"\f7f3"}.fa-child:before{content:"\f1ae"}.fa-lira-sign:before{content:"\f195"}.fa-satellite:before{content:"\f7bf"}.fa-plane-lock:before{content:"\e558"}.fa-tag:before{content:"\f02b"}.fa-comment:before{content:"\f075"}.fa-birthday-cake:before,.fa-cake-candles:before,.fa-cake:before{content:"\f1fd"}.fa-envelope:before{content:"\f0e0"}.fa-angle-double-up:before,.fa-angles-up:before{content:"\f102"}.fa-paperclip:before{content:"\f0c6"}.fa-arrow-right-to-city:before{content:"\e4b3"}.fa-ribbon:before{content:"\f4d6"}.fa-lungs:before{content:"\f604"}.fa-arrow-up-9-1:before,.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-litecoin-sign:before{content:"\e1d3"}.fa-border-none:before{content:"\f850"}.fa-circle-nodes:before{content:"\e4e2"}.fa-parachute-box:before{content:"\f4cd"}.fa-indent:before{content:"\f03c"}.fa-truck-field-un:before{content:"\e58e"}.fa-hourglass-empty:before,.fa-hourglass:before{content:"\f254"}.fa-mountain:before{content:"\f6fc"}.fa-user-doctor:before,.fa-user-md:before{content:"\f0f0"}.fa-circle-info:before,.fa-info-circle:before{content:"\f05a"}.fa-cloud-meatball:before{content:"\f73b"}.fa-camera-alt:before,.fa-camera:before{content:"\f030"}.fa-square-virus:before{content:"\e578"}.fa-meteor:before{content:"\f753"}.fa-car-on:before{content:"\e4dd"}.fa-sleigh:before{content:"\f7cc"}.fa-arrow-down-1-9:before,.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-hand-holding-droplet:before,.fa-hand-holding-water:before{content:"\f4c1"}.fa-water:before{content:"\f773"}.fa-calendar-check:before{content:"\f274"}.fa-braille:before{content:"\f2a1"}.fa-prescription-bottle-alt:before,.fa-prescription-bottle-medical:before{content:"\f486"}.fa-landmark:before{content:"\f66f"}.fa-truck:before{content:"\f0d1"}.fa-crosshairs:before{content:"\f05b"}.fa-person-cane:before{content:"\e53c"}.fa-tent:before{content:"\e57d"}.fa-vest-patches:before{content:"\e086"}.fa-check-double:before{content:"\f560"}.fa-arrow-down-a-z:before,.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-money-bill-wheat:before{content:"\e52a"}.fa-cookie:before{content:"\f563"}.fa-arrow-left-rotate:before,.fa-arrow-rotate-back:before,.fa-arrow-rotate-backward:before,.fa-arrow-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-hard-drive:before,.fa-hdd:before{content:"\f0a0"}.fa-face-grin-squint-tears:before,.fa-grin-squint-tears:before{content:"\f586"}.fa-dumbbell:before{content:"\f44b"}.fa-list-alt:before,.fa-rectangle-list:before{content:"\f022"}.fa-tarp-droplet:before{content:"\e57c"}.fa-house-medical-circle-check:before{content:"\e511"}.fa-person-skiing-nordic:before,.fa-skiing-nordic:before{content:"\f7ca"}.fa-calendar-plus:before{content:"\f271"}.fa-plane-arrival:before{content:"\f5af"}.fa-arrow-alt-circle-left:before,.fa-circle-left:before{content:"\f359"}.fa-subway:before,.fa-train-subway:before{content:"\f239"}.fa-chart-gantt:before{content:"\e0e4"}.fa-indian-rupee-sign:before,.fa-indian-rupee:before,.fa-inr:before{content:"\e1bc"}.fa-crop-alt:before,.fa-crop-simple:before{content:"\f565"}.fa-money-bill-1:before,.fa-money-bill-alt:before{content:"\f3d1"}.fa-left-long:before,.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-dna:before{content:"\f471"}.fa-virus-slash:before{content:"\e075"}.fa-minus:before,.fa-subtract:before{content:"\f068"}.fa-chess:before{content:"\f439"}.fa-arrow-left-long:before,.fa-long-arrow-left:before{content:"\f177"}.fa-plug-circle-check:before{content:"\e55c"}.fa-street-view:before{content:"\f21d"}.fa-franc-sign:before{content:"\e18f"}.fa-volume-off:before{content:"\f026"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before,.fa-hands-american-sign-language-interpreting:before,.fa-hands-asl-interpreting:before{content:"\f2a3"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-droplet-slash:before,.fa-tint-slash:before{content:"\f5c7"}.fa-mosque:before{content:"\f678"}.fa-mosquito:before{content:"\e52b"}.fa-star-of-david:before{content:"\f69a"}.fa-person-military-rifle:before{content:"\e54b"}.fa-cart-shopping:before,.fa-shopping-cart:before{content:"\f07a"}.fa-vials:before{content:"\f493"}.fa-plug-circle-plus:before{content:"\e55f"}.fa-place-of-worship:before{content:"\f67f"}.fa-grip-vertical:before{content:"\f58e"}.fa-arrow-turn-up:before,.fa-level-up:before{content:"\f148"}.fa-u:before{content:"\55"}.fa-square-root-alt:before,.fa-square-root-variable:before{content:"\f698"}.fa-clock-four:before,.fa-clock:before{content:"\f017"}.fa-backward-step:before,.fa-step-backward:before{content:"\f048"}.fa-pallet:before{content:"\f482"}.fa-faucet:before{content:"\e005"}.fa-baseball-bat-ball:before{content:"\f432"}.fa-s:before{content:"\53"}.fa-timeline:before{content:"\e29c"}.fa-keyboard:before{content:"\f11c"}.fa-caret-down:before{content:"\f0d7"}.fa-clinic-medical:before,.fa-house-chimney-medical:before{content:"\f7f2"}.fa-temperature-3:before,.fa-temperature-three-quarters:before,.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-mobile-android-alt:before,.fa-mobile-screen:before{content:"\f3cf"}.fa-plane-up:before{content:"\e22d"}.fa-piggy-bank:before{content:"\f4d3"}.fa-battery-3:before,.fa-battery-half:before{content:"\f242"}.fa-mountain-city:before{content:"\e52e"}.fa-coins:before{content:"\f51e"}.fa-khanda:before{content:"\f66d"}.fa-sliders-h:before,.fa-sliders:before{content:"\f1de"}.fa-folder-tree:before{content:"\f802"}.fa-network-wired:before{content:"\f6ff"}.fa-map-pin:before{content:"\f276"}.fa-hamsa:before{content:"\f665"}.fa-cent-sign:before{content:"\e3f5"}.fa-flask:before{content:"\f0c3"}.fa-person-pregnant:before{content:"\e31e"}.fa-wand-sparkles:before{content:"\f72b"}.fa-ellipsis-v:before,.fa-ellipsis-vertical:before{content:"\f142"}.fa-ticket:before{content:"\f145"}.fa-power-off:before{content:"\f011"}.fa-long-arrow-alt-right:before,.fa-right-long:before{content:"\f30b"}.fa-flag-usa:before{content:"\f74d"}.fa-laptop-file:before{content:"\e51d"}.fa-teletype:before,.fa-tty:before{content:"\f1e4"}.fa-diagram-next:before{content:"\e476"}.fa-person-rifle:before{content:"\e54e"}.fa-house-medical-circle-exclamation:before{content:"\e512"}.fa-closed-captioning:before{content:"\f20a"}.fa-hiking:before,.fa-person-hiking:before{content:"\f6ec"}.fa-venus-double:before{content:"\f226"}.fa-images:before{content:"\f302"}.fa-calculator:before{content:"\f1ec"}.fa-people-pulling:before{content:"\e535"}.fa-n:before{content:"\4e"}.fa-cable-car:before,.fa-tram:before{content:"\f7da"}.fa-cloud-rain:before{content:"\f73d"}.fa-building-circle-xmark:before{content:"\e4d4"}.fa-ship:before{content:"\f21a"}.fa-arrows-down-to-line:before{content:"\e4b8"}.fa-download:before{content:"\f019"}.fa-face-grin:before,.fa-grin:before{content:"\f580"}.fa-backspace:before,.fa-delete-left:before{content:"\f55a"}.fa-eye-dropper-empty:before,.fa-eye-dropper:before,.fa-eyedropper:before{content:"\f1fb"}.fa-file-circle-check:before{content:"\e5a0"}.fa-forward:before{content:"\f04e"}.fa-mobile-android:before,.fa-mobile-phone:before,.fa-mobile:before{content:"\f3ce"}.fa-face-meh:before,.fa-meh:before{content:"\f11a"}.fa-align-center:before{content:"\f037"}.fa-book-dead:before,.fa-book-skull:before{content:"\f6b7"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-heart-circle-exclamation:before{content:"\e4fe"}.fa-home-alt:before,.fa-home-lg-alt:before,.fa-home:before,.fa-house:before{content:"\f015"}.fa-calendar-week:before{content:"\f784"}.fa-laptop-medical:before{content:"\f812"}.fa-b:before{content:"\42"}.fa-file-medical:before{content:"\f477"}.fa-dice-one:before{content:"\f525"}.fa-kiwi-bird:before{content:"\f535"}.fa-arrow-right-arrow-left:before,.fa-exchange:before{content:"\f0ec"}.fa-redo-alt:before,.fa-rotate-forward:before,.fa-rotate-right:before{content:"\f2f9"}.fa-cutlery:before,.fa-utensils:before{content:"\f2e7"}.fa-arrow-up-wide-short:before,.fa-sort-amount-up:before{content:"\f161"}.fa-mill-sign:before{content:"\e1ed"}.fa-bowl-rice:before{content:"\e2eb"}.fa-skull:before{content:"\f54c"}.fa-broadcast-tower:before,.fa-tower-broadcast:before{content:"\f519"}.fa-truck-pickup:before{content:"\f63c"}.fa-long-arrow-alt-up:before,.fa-up-long:before{content:"\f30c"}.fa-stop:before{content:"\f04d"}.fa-code-merge:before{content:"\f387"}.fa-upload:before{content:"\f093"}.fa-hurricane:before{content:"\f751"}.fa-mound:before{content:"\e52d"}.fa-toilet-portable:before{content:"\e583"}.fa-compact-disc:before{content:"\f51f"}.fa-file-arrow-down:before,.fa-file-download:before{content:"\f56d"}.fa-caravan:before{content:"\f8ff"}.fa-shield-cat:before{content:"\e572"}.fa-bolt:before,.fa-zap:before{content:"\f0e7"}.fa-glass-water:before{content:"\e4f4"}.fa-oil-well:before{content:"\e532"}.fa-vault:before{content:"\e2c5"}.fa-mars:before{content:"\f222"}.fa-toilet:before{content:"\f7d8"}.fa-plane-circle-xmark:before{content:"\e557"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen-sign:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble-sign:before,.fa-ruble:before{content:"\f158"}.fa-sun:before{content:"\f185"}.fa-guitar:before{content:"\f7a6"}.fa-face-laugh-wink:before,.fa-laugh-wink:before{content:"\f59c"}.fa-horse-head:before{content:"\f7ab"}.fa-bore-hole:before{content:"\e4c3"}.fa-industry:before{content:"\f275"}.fa-arrow-alt-circle-down:before,.fa-circle-down:before{content:"\f358"}.fa-arrows-turn-to-dots:before{content:"\e4c1"}.fa-florin-sign:before{content:"\e184"}.fa-arrow-down-short-wide:before,.fa-sort-amount-desc:before,.fa-sort-amount-down-alt:before{content:"\f884"}.fa-less-than:before{content:"\3c"}.fa-angle-down:before{content:"\f107"}.fa-car-tunnel:before{content:"\e4de"}.fa-head-side-cough:before{content:"\e061"}.fa-grip-lines:before{content:"\f7a4"}.fa-thumbs-down:before{content:"\f165"}.fa-user-lock:before{content:"\f502"}.fa-arrow-right-long:before,.fa-long-arrow-right:before{content:"\f178"}.fa-anchor-circle-xmark:before{content:"\e4ac"}.fa-ellipsis-h:before,.fa-ellipsis:before{content:"\f141"}.fa-chess-pawn:before{content:"\f443"}.fa-first-aid:before,.fa-kit-medical:before{content:"\f479"}.fa-person-through-window:before{content:"\e5a9"}.fa-toolbox:before{content:"\f552"}.fa-hands-holding-circle:before{content:"\e4fb"}.fa-bug:before{content:"\f188"}.fa-credit-card-alt:before,.fa-credit-card:before{content:"\f09d"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-hand-holding-hand:before{content:"\e4f7"}.fa-book-open-reader:before,.fa-book-reader:before{content:"\f5da"}.fa-mountain-sun:before{content:"\e52f"}.fa-arrows-left-right-to-line:before{content:"\e4ba"}.fa-dice-d20:before{content:"\f6cf"}.fa-truck-droplet:before{content:"\e58c"}.fa-file-circle-xmark:before{content:"\e5a1"}.fa-temperature-arrow-up:before,.fa-temperature-up:before{content:"\e040"}.fa-medal:before{content:"\f5a2"}.fa-bed:before{content:"\f236"}.fa-h-square:before,.fa-square-h:before{content:"\f0fd"}.fa-podcast:before{content:"\f2ce"}.fa-temperature-4:before,.fa-temperature-full:before,.fa-thermometer-4:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-bell:before{content:"\f0f3"}.fa-superscript:before{content:"\f12b"}.fa-plug-circle-xmark:before{content:"\e560"}.fa-star-of-life:before{content:"\f621"}.fa-phone-slash:before{content:"\f3dd"}.fa-paint-roller:before{content:"\f5aa"}.fa-hands-helping:before,.fa-handshake-angle:before{content:"\f4c4"}.fa-location-dot:before,.fa-map-marker-alt:before{content:"\f3c5"}.fa-file:before{content:"\f15b"}.fa-greater-than:before{content:"\3e"}.fa-person-swimming:before,.fa-swimmer:before{content:"\f5c4"}.fa-arrow-down:before{content:"\f063"}.fa-droplet:before,.fa-tint:before{content:"\f043"}.fa-eraser:before{content:"\f12d"}.fa-earth-america:before,.fa-earth-americas:before,.fa-earth:before,.fa-globe-americas:before{content:"\f57d"}.fa-person-burst:before{content:"\e53b"}.fa-dove:before{content:"\f4ba"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-socks:before{content:"\f696"}.fa-inbox:before{content:"\f01c"}.fa-section:before{content:"\e447"}.fa-gauge-high:before,.fa-tachometer-alt-fast:before,.fa-tachometer-alt:before{content:"\f625"}.fa-envelope-open-text:before{content:"\f658"}.fa-hospital-alt:before,.fa-hospital-wide:before,.fa-hospital:before{content:"\f0f8"}.fa-wine-bottle:before{content:"\f72f"}.fa-chess-rook:before{content:"\f447"}.fa-bars-staggered:before,.fa-reorder:before,.fa-stream:before{content:"\f550"}.fa-dharmachakra:before{content:"\f655"}.fa-hotdog:before{content:"\f80f"}.fa-blind:before,.fa-person-walking-with-cane:before{content:"\f29d"}.fa-drum:before{content:"\f569"}.fa-ice-cream:before{content:"\f810"}.fa-heart-circle-bolt:before{content:"\e4fc"}.fa-fax:before{content:"\f1ac"}.fa-paragraph:before{content:"\f1dd"}.fa-check-to-slot:before,.fa-vote-yea:before{content:"\f772"}.fa-star-half:before{content:"\f089"}.fa-boxes-alt:before,.fa-boxes-stacked:before,.fa-boxes:before{content:"\f468"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-assistive-listening-systems:before,.fa-ear-listen:before{content:"\f2a2"}.fa-tree-city:before{content:"\e587"}.fa-play:before{content:"\f04b"}.fa-font:before{content:"\f031"}.fa-rupiah-sign:before{content:"\e23d"}.fa-magnifying-glass:before,.fa-search:before{content:"\f002"}.fa-ping-pong-paddle-ball:before,.fa-table-tennis-paddle-ball:before,.fa-table-tennis:before{content:"\f45d"}.fa-diagnoses:before,.fa-person-dots-from-line:before{content:"\f470"}.fa-trash-can-arrow-up:before,.fa-trash-restore-alt:before{content:"\f82a"}.fa-naira-sign:before{content:"\e1f6"}.fa-cart-arrow-down:before{content:"\f218"}.fa-walkie-talkie:before{content:"\f8ef"}.fa-file-edit:before,.fa-file-pen:before{content:"\f31c"}.fa-receipt:before{content:"\f543"}.fa-pen-square:before,.fa-pencil-square:before,.fa-square-pen:before{content:"\f14b"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-person-circle-exclamation:before{content:"\e53f"}.fa-chevron-down:before{content:"\f078"}.fa-battery-5:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-skull-crossbones:before{content:"\f714"}.fa-code-compare:before{content:"\e13a"}.fa-list-dots:before,.fa-list-ul:before{content:"\f0ca"}.fa-school-lock:before{content:"\e56f"}.fa-tower-cell:before{content:"\e585"}.fa-down-long:before,.fa-long-arrow-alt-down:before{content:"\f309"}.fa-ranking-star:before{content:"\e561"}.fa-chess-king:before{content:"\f43f"}.fa-person-harassing:before{content:"\e549"}.fa-brazilian-real-sign:before{content:"\e46c"}.fa-landmark-alt:before,.fa-landmark-dome:before{content:"\f752"}.fa-arrow-up:before{content:"\f062"}.fa-television:before,.fa-tv-alt:before,.fa-tv:before{content:"\f26c"}.fa-shrimp:before{content:"\e448"}.fa-list-check:before,.fa-tasks:before{content:"\f0ae"}.fa-jug-detergent:before{content:"\e519"}.fa-circle-user:before,.fa-user-circle:before{content:"\f2bd"}.fa-user-shield:before{content:"\f505"}.fa-wind:before{content:"\f72e"}.fa-car-burst:before,.fa-car-crash:before{content:"\f5e1"}.fa-y:before{content:"\59"}.fa-person-snowboarding:before,.fa-snowboarding:before{content:"\f7ce"}.fa-shipping-fast:before,.fa-truck-fast:before{content:"\f48b"}.fa-fish:before{content:"\f578"}.fa-user-graduate:before{content:"\f501"}.fa-adjust:before,.fa-circle-half-stroke:before{content:"\f042"}.fa-clapperboard:before{content:"\e131"}.fa-circle-radiation:before,.fa-radiation-alt:before{content:"\f7ba"}.fa-baseball-ball:before,.fa-baseball:before{content:"\f433"}.fa-jet-fighter-up:before{content:"\e518"}.fa-diagram-project:before,.fa-project-diagram:before{content:"\f542"}.fa-copy:before{content:"\f0c5"}.fa-volume-mute:before,.fa-volume-times:before,.fa-volume-xmark:before{content:"\f6a9"}.fa-hand-sparkles:before{content:"\e05d"}.fa-grip-horizontal:before,.fa-grip:before{content:"\f58d"}.fa-share-from-square:before,.fa-share-square:before{content:"\f14d"}.fa-child-combatant:before,.fa-child-rifle:before{content:"\e4e0"}.fa-gun:before{content:"\e19b"}.fa-phone-square:before,.fa-square-phone:before{content:"\f098"}.fa-add:before,.fa-plus:before{content:"\2b"}.fa-expand:before{content:"\f065"}.fa-computer:before{content:"\e4e5"}.fa-close:before,.fa-multiply:before,.fa-remove:before,.fa-times:before,.fa-xmark:before{content:"\f00d"}.fa-arrows-up-down-left-right:before,.fa-arrows:before{content:"\f047"}.fa-chalkboard-teacher:before,.fa-chalkboard-user:before{content:"\f51c"}.fa-peso-sign:before{content:"\e222"}.fa-building-shield:before{content:"\e4d8"}.fa-baby:before{content:"\f77c"}.fa-users-line:before{content:"\e592"}.fa-quote-left-alt:before,.fa-quote-left:before{content:"\f10d"}.fa-tractor:before{content:"\f722"}.fa-trash-arrow-up:before,.fa-trash-restore:before{content:"\f829"}.fa-arrow-down-up-lock:before{content:"\e4b0"}.fa-lines-leaning:before{content:"\e51e"}.fa-ruler-combined:before{content:"\f546"}.fa-copyright:before{content:"\f1f9"}.fa-equals:before{content:"\3d"}.fa-blender:before{content:"\f517"}.fa-teeth:before{content:"\f62e"}.fa-ils:before,.fa-shekel-sign:before,.fa-shekel:before,.fa-sheqel-sign:before,.fa-sheqel:before{content:"\f20b"}.fa-map:before{content:"\f279"}.fa-rocket:before{content:"\f135"}.fa-photo-film:before,.fa-photo-video:before{content:"\f87c"}.fa-folder-minus:before{content:"\f65d"}.fa-store:before{content:"\f54e"}.fa-arrow-trend-up:before{content:"\e098"}.fa-plug-circle-minus:before{content:"\e55e"}.fa-sign-hanging:before,.fa-sign:before{content:"\f4d9"}.fa-bezier-curve:before{content:"\f55b"}.fa-bell-slash:before{content:"\f1f6"}.fa-tablet-android:before,.fa-tablet:before{content:"\f3fb"}.fa-school-flag:before{content:"\e56e"}.fa-fill:before{content:"\f575"}.fa-angle-up:before{content:"\f106"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-holly-berry:before{content:"\f7aa"}.fa-chevron-left:before{content:"\f053"}.fa-bacteria:before{content:"\e059"}.fa-hand-lizard:before{content:"\f258"}.fa-notdef:before{content:"\e1fe"}.fa-disease:before{content:"\f7fa"}.fa-briefcase-medical:before{content:"\f469"}.fa-genderless:before{content:"\f22d"}.fa-chevron-right:before{content:"\f054"}.fa-retweet:before{content:"\f079"}.fa-car-alt:before,.fa-car-rear:before{content:"\f5de"}.fa-pump-soap:before{content:"\e06b"}.fa-video-slash:before{content:"\f4e2"}.fa-battery-2:before,.fa-battery-quarter:before{content:"\f243"}.fa-radio:before{content:"\f8d7"}.fa-baby-carriage:before,.fa-carriage-baby:before{content:"\f77d"}.fa-traffic-light:before{content:"\f637"}.fa-thermometer:before{content:"\f491"}.fa-vr-cardboard:before{content:"\f729"}.fa-hand-middle-finger:before{content:"\f806"}.fa-percent:before,.fa-percentage:before{content:"\25"}.fa-truck-moving:before{content:"\f4df"}.fa-glass-water-droplet:before{content:"\e4f5"}.fa-display:before{content:"\e163"}.fa-face-smile:before,.fa-smile:before{content:"\f118"}.fa-thumb-tack:before,.fa-thumbtack:before{content:"\f08d"}.fa-trophy:before{content:"\f091"}.fa-person-praying:before,.fa-pray:before{content:"\f683"}.fa-hammer:before{content:"\f6e3"}.fa-hand-peace:before{content:"\f25b"}.fa-rotate:before,.fa-sync-alt:before{content:"\f2f1"}.fa-spinner:before{content:"\f110"}.fa-robot:before{content:"\f544"}.fa-peace:before{content:"\f67c"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-warehouse:before{content:"\f494"}.fa-arrow-up-right-dots:before{content:"\e4b7"}.fa-splotch:before{content:"\f5bc"}.fa-face-grin-hearts:before,.fa-grin-hearts:before{content:"\f584"}.fa-dice-four:before{content:"\f524"}.fa-sim-card:before{content:"\f7c4"}.fa-transgender-alt:before,.fa-transgender:before{content:"\f225"}.fa-mercury:before{content:"\f223"}.fa-arrow-turn-down:before,.fa-level-down:before{content:"\f149"}.fa-person-falling-burst:before{content:"\e547"}.fa-award:before{content:"\f559"}.fa-ticket-alt:before,.fa-ticket-simple:before{content:"\f3ff"}.fa-building:before{content:"\f1ad"}.fa-angle-double-left:before,.fa-angles-left:before{content:"\f100"}.fa-qrcode:before{content:"\f029"}.fa-clock-rotate-left:before,.fa-history:before{content:"\f1da"}.fa-face-grin-beam-sweat:before,.fa-grin-beam-sweat:before{content:"\f583"}.fa-arrow-right-from-file:before,.fa-file-export:before{content:"\f56e"}.fa-shield-blank:before,.fa-shield:before{content:"\f132"}.fa-arrow-up-short-wide:before,.fa-sort-amount-up-alt:before{content:"\f885"}.fa-house-medical:before{content:"\e3b2"}.fa-golf-ball-tee:before,.fa-golf-ball:before{content:"\f450"}.fa-chevron-circle-left:before,.fa-circle-chevron-left:before{content:"\f137"}.fa-house-chimney-window:before{content:"\e00d"}.fa-pen-nib:before{content:"\f5ad"}.fa-tent-arrow-turn-left:before{content:"\e580"}.fa-tents:before{content:"\e582"}.fa-magic:before,.fa-wand-magic:before{content:"\f0d0"}.fa-dog:before{content:"\f6d3"}.fa-carrot:before{content:"\f787"}.fa-moon:before{content:"\f186"}.fa-wine-glass-alt:before,.fa-wine-glass-empty:before{content:"\f5ce"}.fa-cheese:before{content:"\f7ef"}.fa-yin-yang:before{content:"\f6ad"}.fa-music:before{content:"\f001"}.fa-code-commit:before{content:"\f386"}.fa-temperature-low:before{content:"\f76b"}.fa-biking:before,.fa-person-biking:before{content:"\f84a"}.fa-broom:before{content:"\f51a"}.fa-shield-heart:before{content:"\e574"}.fa-gopuram:before{content:"\f664"}.fa-earth-oceania:before,.fa-globe-oceania:before{content:"\e47b"}.fa-square-xmark:before,.fa-times-square:before,.fa-xmark-square:before{content:"\f2d3"}.fa-hashtag:before{content:"\23"}.fa-expand-alt:before,.fa-up-right-and-down-left-from-center:before{content:"\f424"}.fa-oil-can:before{content:"\f613"}.fa-t:before{content:"\54"}.fa-hippo:before{content:"\f6ed"}.fa-chart-column:before{content:"\e0e3"}.fa-infinity:before{content:"\f534"}.fa-vial-circle-check:before{content:"\e596"}.fa-person-arrow-down-to-line:before{content:"\e538"}.fa-voicemail:before{content:"\f897"}.fa-fan:before{content:"\f863"}.fa-person-walking-luggage:before{content:"\e554"}.fa-arrows-alt-v:before,.fa-up-down:before{content:"\f338"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-calendar:before{content:"\f133"}.fa-trailer:before{content:"\e041"}.fa-bahai:before,.fa-haykal:before{content:"\f666"}.fa-sd-card:before{content:"\f7c2"}.fa-dragon:before{content:"\f6d5"}.fa-shoe-prints:before{content:"\f54b"}.fa-circle-plus:before,.fa-plus-circle:before{content:"\f055"}.fa-face-grin-tongue-wink:before,.fa-grin-tongue-wink:before{content:"\f58b"}.fa-hand-holding:before{content:"\f4bd"}.fa-plug-circle-exclamation:before{content:"\e55d"}.fa-chain-broken:before,.fa-chain-slash:before,.fa-link-slash:before,.fa-unlink:before{content:"\f127"}.fa-clone:before{content:"\f24d"}.fa-person-walking-arrow-loop-left:before{content:"\e551"}.fa-arrow-up-z-a:before,.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-fire-alt:before,.fa-fire-flame-curved:before{content:"\f7e4"}.fa-tornado:before{content:"\f76f"}.fa-file-circle-plus:before{content:"\e494"}.fa-book-quran:before,.fa-quran:before{content:"\f687"}.fa-anchor:before{content:"\f13d"}.fa-border-all:before{content:"\f84c"}.fa-angry:before,.fa-face-angry:before{content:"\f556"}.fa-cookie-bite:before{content:"\f564"}.fa-arrow-trend-down:before{content:"\e097"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-draw-polygon:before{content:"\f5ee"}.fa-balance-scale:before,.fa-scale-balanced:before{content:"\f24e"}.fa-gauge-simple-high:before,.fa-tachometer-fast:before,.fa-tachometer:before{content:"\f62a"}.fa-shower:before{content:"\f2cc"}.fa-desktop-alt:before,.fa-desktop:before{content:"\f390"}.fa-m:before{content:"\4d"}.fa-table-list:before,.fa-th-list:before{content:"\f00b"}.fa-comment-sms:before,.fa-sms:before{content:"\f7cd"}.fa-book:before{content:"\f02d"}.fa-user-plus:before{content:"\f234"}.fa-check:before{content:"\f00c"}.fa-battery-4:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-house-circle-check:before{content:"\e509"}.fa-angle-left:before{content:"\f104"}.fa-diagram-successor:before{content:"\e47a"}.fa-truck-arrow-right:before{content:"\e58b"}.fa-arrows-split-up-and-left:before{content:"\e4bc"}.fa-fist-raised:before,.fa-hand-fist:before{content:"\f6de"}.fa-cloud-moon:before{content:"\f6c3"}.fa-briefcase:before{content:"\f0b1"}.fa-person-falling:before{content:"\e546"}.fa-image-portrait:before,.fa-portrait:before{content:"\f3e0"}.fa-user-tag:before{content:"\f507"}.fa-rug:before{content:"\e569"}.fa-earth-europe:before,.fa-globe-europe:before{content:"\f7a2"}.fa-cart-flatbed-suitcase:before,.fa-luggage-cart:before{content:"\f59d"}.fa-rectangle-times:before,.fa-rectangle-xmark:before,.fa-times-rectangle:before,.fa-window-close:before{content:"\f410"}.fa-baht-sign:before{content:"\e0ac"}.fa-book-open:before{content:"\f518"}.fa-book-journal-whills:before,.fa-journal-whills:before{content:"\f66a"}.fa-handcuffs:before{content:"\e4f8"}.fa-exclamation-triangle:before,.fa-triangle-exclamation:before,.fa-warning:before{content:"\f071"}.fa-database:before{content:"\f1c0"}.fa-arrow-turn-right:before,.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-bottle-droplet:before{content:"\e4c4"}.fa-mask-face:before{content:"\e1d7"}.fa-hill-rockslide:before{content:"\e508"}.fa-exchange-alt:before,.fa-right-left:before{content:"\f362"}.fa-paper-plane:before{content:"\f1d8"}.fa-road-circle-exclamation:before{content:"\e565"}.fa-dungeon:before{content:"\f6d9"}.fa-align-right:before{content:"\f038"}.fa-money-bill-1-wave:before,.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-life-ring:before{content:"\f1cd"}.fa-hands:before,.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-calendar-day:before{content:"\f783"}.fa-ladder-water:before,.fa-swimming-pool:before,.fa-water-ladder:before{content:"\f5c5"}.fa-arrows-up-down:before,.fa-arrows-v:before{content:"\f07d"}.fa-face-grimace:before,.fa-grimace:before{content:"\f57f"}.fa-wheelchair-alt:before,.fa-wheelchair-move:before{content:"\e2ce"}.fa-level-down-alt:before,.fa-turn-down:before{content:"\f3be"}.fa-person-walking-arrow-right:before{content:"\e552"}.fa-envelope-square:before,.fa-square-envelope:before{content:"\f199"}.fa-dice:before{content:"\f522"}.fa-bowling-ball:before{content:"\f436"}.fa-brain:before{content:"\f5dc"}.fa-band-aid:before,.fa-bandage:before{content:"\f462"}.fa-calendar-minus:before{content:"\f272"}.fa-circle-xmark:before,.fa-times-circle:before,.fa-xmark-circle:before{content:"\f057"}.fa-gifts:before{content:"\f79c"}.fa-hotel:before{content:"\f594"}.fa-earth-asia:before,.fa-globe-asia:before{content:"\f57e"}.fa-id-card-alt:before,.fa-id-card-clip:before{content:"\f47f"}.fa-magnifying-glass-plus:before,.fa-search-plus:before{content:"\f00e"}.fa-thumbs-up:before{content:"\f164"}.fa-user-clock:before{content:"\f4fd"}.fa-allergies:before,.fa-hand-dots:before{content:"\f461"}.fa-file-invoice:before{content:"\f570"}.fa-window-minimize:before{content:"\f2d1"}.fa-coffee:before,.fa-mug-saucer:before{content:"\f0f4"}.fa-brush:before{content:"\f55d"}.fa-mask:before{content:"\f6fa"}.fa-magnifying-glass-minus:before,.fa-search-minus:before{content:"\f010"}.fa-ruler-vertical:before{content:"\f548"}.fa-user-alt:before,.fa-user-large:before{content:"\f406"}.fa-train-tram:before{content:"\e5b4"}.fa-user-nurse:before{content:"\f82f"}.fa-syringe:before{content:"\f48e"}.fa-cloud-sun:before{content:"\f6c4"}.fa-stopwatch-20:before{content:"\e06f"}.fa-square-full:before{content:"\f45c"}.fa-magnet:before{content:"\f076"}.fa-jar:before{content:"\e516"}.fa-note-sticky:before,.fa-sticky-note:before{content:"\f249"}.fa-bug-slash:before{content:"\e490"}.fa-arrow-up-from-water-pump:before{content:"\e4b6"}.fa-bone:before{content:"\f5d7"}.fa-user-injured:before{content:"\f728"}.fa-face-sad-tear:before,.fa-sad-tear:before{content:"\f5b4"}.fa-plane:before{content:"\f072"}.fa-tent-arrows-down:before{content:"\e581"}.fa-exclamation:before{content:"\21"}.fa-arrows-spin:before{content:"\e4bb"}.fa-print:before{content:"\f02f"}.fa-try:before,.fa-turkish-lira-sign:before,.fa-turkish-lira:before{content:"\e2bb"}.fa-dollar-sign:before,.fa-dollar:before,.fa-usd:before{content:"\24"}.fa-x:before{content:"\58"}.fa-magnifying-glass-dollar:before,.fa-search-dollar:before{content:"\f688"}.fa-users-cog:before,.fa-users-gear:before{content:"\f509"}.fa-person-military-pointing:before{content:"\e54a"}.fa-bank:before,.fa-building-columns:before,.fa-institution:before,.fa-museum:before,.fa-university:before{content:"\f19c"}.fa-umbrella:before{content:"\f0e9"}.fa-trowel:before{content:"\e589"}.fa-d:before{content:"\44"}.fa-stapler:before{content:"\e5af"}.fa-masks-theater:before,.fa-theater-masks:before{content:"\f630"}.fa-kip-sign:before{content:"\e1c4"}.fa-hand-point-left:before{content:"\f0a5"}.fa-handshake-alt:before,.fa-handshake-simple:before{content:"\f4c6"}.fa-fighter-jet:before,.fa-jet-fighter:before{content:"\f0fb"}.fa-share-alt-square:before,.fa-square-share-nodes:before{content:"\f1e1"}.fa-barcode:before{content:"\f02a"}.fa-plus-minus:before{content:"\e43c"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-person-circle-check:before{content:"\e53e"}.fa-level-up-alt:before,.fa-turn-up:before{content:"\f3bf"}.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host,:root{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-weight:400}.fa-monero:before{content:"\f3d0"}.fa-hooli:before{content:"\f427"}.fa-yelp:before{content:"\f1e9"}.fa-cc-visa:before{content:"\f1f0"}.fa-lastfm:before{content:"\f202"}.fa-shopware:before{content:"\f5b5"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-aws:before{content:"\f375"}.fa-redhat:before{content:"\f7bc"}.fa-yoast:before{content:"\f2b1"}.fa-cloudflare:before{content:"\e07d"}.fa-ups:before{content:"\f7e0"}.fa-wpexplorer:before{content:"\f2de"}.fa-dyalog:before{content:"\f399"}.fa-bity:before{content:"\f37a"}.fa-stackpath:before{content:"\f842"}.fa-buysellads:before{content:"\f20d"}.fa-first-order:before{content:"\f2b0"}.fa-modx:before{content:"\f285"}.fa-guilded:before{content:"\e07e"}.fa-vnv:before{content:"\f40b"}.fa-js-square:before,.fa-square-js:before{content:"\f3b9"}.fa-microsoft:before{content:"\f3ca"}.fa-qq:before{content:"\f1d6"}.fa-orcid:before{content:"\f8d2"}.fa-java:before{content:"\f4e4"}.fa-invision:before{content:"\f7b0"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-centercode:before{content:"\f380"}.fa-glide-g:before{content:"\f2a6"}.fa-drupal:before{content:"\f1a9"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-unity:before{content:"\e049"}.fa-whmcs:before{content:"\f40d"}.fa-rocketchat:before{content:"\f3e8"}.fa-vk:before{content:"\f189"}.fa-untappd:before{content:"\f405"}.fa-mailchimp:before{content:"\f59e"}.fa-css3-alt:before{content:"\f38b"}.fa-reddit-square:before,.fa-square-reddit:before{content:"\f1a2"}.fa-vimeo-v:before{content:"\f27d"}.fa-contao:before{content:"\f26d"}.fa-square-font-awesome:before{content:"\e5ad"}.fa-deskpro:before{content:"\f38f"}.fa-sistrix:before{content:"\f3ee"}.fa-instagram-square:before,.fa-square-instagram:before{content:"\e055"}.fa-battle-net:before{content:"\f835"}.fa-the-red-yeti:before{content:"\f69d"}.fa-hacker-news-square:before,.fa-square-hacker-news:before{content:"\f3af"}.fa-edge:before{content:"\f282"}.fa-napster:before{content:"\f3d2"}.fa-snapchat-square:before,.fa-square-snapchat:before{content:"\f2ad"}.fa-google-plus-g:before{content:"\f0d5"}.fa-artstation:before{content:"\f77a"}.fa-markdown:before{content:"\f60f"}.fa-sourcetree:before{content:"\f7d3"}.fa-google-plus:before{content:"\f2b3"}.fa-diaspora:before{content:"\f791"}.fa-foursquare:before{content:"\f180"}.fa-stack-overflow:before{content:"\f16c"}.fa-github-alt:before{content:"\f113"}.fa-phoenix-squadron:before{content:"\f511"}.fa-pagelines:before{content:"\f18c"}.fa-algolia:before{content:"\f36c"}.fa-red-river:before{content:"\f3e3"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-safari:before{content:"\f267"}.fa-google:before{content:"\f1a0"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-atlassian:before{content:"\f77b"}.fa-linkedin-in:before{content:"\f0e1"}.fa-digital-ocean:before{content:"\f391"}.fa-nimblr:before{content:"\f5a8"}.fa-chromecast:before{content:"\f838"}.fa-evernote:before{content:"\f839"}.fa-hacker-news:before{content:"\f1d4"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-adversal:before{content:"\f36a"}.fa-creative-commons:before{content:"\f25e"}.fa-watchman-monitoring:before{content:"\e087"}.fa-fonticons:before{content:"\f280"}.fa-weixin:before{content:"\f1d7"}.fa-shirtsinbulk:before{content:"\f214"}.fa-codepen:before{content:"\f1cb"}.fa-git-alt:before{content:"\f841"}.fa-lyft:before{content:"\f3c3"}.fa-rev:before{content:"\f5b2"}.fa-windows:before{content:"\f17a"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-square-viadeo:before,.fa-viadeo-square:before{content:"\f2aa"}.fa-meetup:before{content:"\f2e0"}.fa-centos:before{content:"\f789"}.fa-adn:before{content:"\f170"}.fa-cloudsmith:before{content:"\f384"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-dribbble-square:before,.fa-square-dribbble:before{content:"\f397"}.fa-codiepie:before{content:"\f284"}.fa-node:before{content:"\f419"}.fa-mix:before{content:"\f3cb"}.fa-steam:before{content:"\f1b6"}.fa-cc-apple-pay:before{content:"\f416"}.fa-scribd:before{content:"\f28a"}.fa-openid:before{content:"\f19b"}.fa-instalod:before{content:"\e081"}.fa-expeditedssl:before{content:"\f23e"}.fa-sellcast:before{content:"\f2da"}.fa-square-twitter:before,.fa-twitter-square:before{content:"\f081"}.fa-r-project:before{content:"\f4f7"}.fa-delicious:before{content:"\f1a5"}.fa-freebsd:before{content:"\f3a4"}.fa-vuejs:before{content:"\f41f"}.fa-accusoft:before{content:"\f369"}.fa-ioxhost:before{content:"\f208"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-app-store:before{content:"\f36f"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-itunes-note:before{content:"\f3b5"}.fa-golang:before{content:"\e40f"}.fa-kickstarter:before{content:"\f3bb"}.fa-grav:before{content:"\f2d6"}.fa-weibo:before{content:"\f18a"}.fa-uncharted:before{content:"\e084"}.fa-firstdraft:before{content:"\f3a1"}.fa-square-youtube:before,.fa-youtube-square:before{content:"\f431"}.fa-wikipedia-w:before{content:"\f266"}.fa-rendact:before,.fa-wpressr:before{content:"\f3e4"}.fa-angellist:before{content:"\f209"}.fa-galactic-republic:before{content:"\f50c"}.fa-nfc-directional:before{content:"\e530"}.fa-skype:before{content:"\f17e"}.fa-joget:before{content:"\f3b7"}.fa-fedora:before{content:"\f798"}.fa-stripe-s:before{content:"\f42a"}.fa-meta:before{content:"\e49b"}.fa-laravel:before{content:"\f3bd"}.fa-hotjar:before{content:"\f3b1"}.fa-bluetooth-b:before{content:"\f294"}.fa-sticker-mule:before{content:"\f3f7"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-hips:before{content:"\f452"}.fa-behance:before{content:"\f1b4"}.fa-reddit:before{content:"\f1a1"}.fa-discord:before{content:"\f392"}.fa-chrome:before{content:"\f268"}.fa-app-store-ios:before{content:"\f370"}.fa-cc-discover:before{content:"\f1f2"}.fa-wpbeginner:before{content:"\f297"}.fa-confluence:before{content:"\f78d"}.fa-mdb:before{content:"\f8ca"}.fa-dochub:before{content:"\f394"}.fa-accessible-icon:before{content:"\f368"}.fa-ebay:before{content:"\f4f4"}.fa-amazon:before{content:"\f270"}.fa-unsplash:before{content:"\e07c"}.fa-yarn:before{content:"\f7e3"}.fa-square-steam:before,.fa-steam-square:before{content:"\f1b7"}.fa-500px:before{content:"\f26e"}.fa-square-vimeo:before,.fa-vimeo-square:before{content:"\f194"}.fa-asymmetrik:before{content:"\f372"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-gratipay:before{content:"\f184"}.fa-apple:before{content:"\f179"}.fa-hive:before{content:"\e07f"}.fa-gitkraken:before{content:"\f3a6"}.fa-keybase:before{content:"\f4f5"}.fa-apple-pay:before{content:"\f415"}.fa-padlet:before{content:"\e4a0"}.fa-amazon-pay:before{content:"\f42c"}.fa-github-square:before,.fa-square-github:before{content:"\f092"}.fa-stumbleupon:before{content:"\f1a4"}.fa-fedex:before{content:"\f797"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-shopify:before{content:"\e057"}.fa-neos:before{content:"\f612"}.fa-hackerrank:before{content:"\f5f7"}.fa-researchgate:before{content:"\f4f8"}.fa-swift:before{content:"\f8e1"}.fa-angular:before{content:"\f420"}.fa-speakap:before{content:"\f3f3"}.fa-angrycreative:before{content:"\f36e"}.fa-y-combinator:before{content:"\f23b"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-gitlab-square:before,.fa-square-gitlab:before{content:"\e5ae"}.fa-studiovinari:before{content:"\f3f8"}.fa-pied-piper:before{content:"\f2ae"}.fa-wordpress:before{content:"\f19a"}.fa-product-hunt:before{content:"\f288"}.fa-firefox:before{content:"\f269"}.fa-linode:before{content:"\f2b8"}.fa-goodreads:before{content:"\f3a8"}.fa-odnoklassniki-square:before,.fa-square-odnoklassniki:before{content:"\f264"}.fa-jsfiddle:before{content:"\f1cc"}.fa-sith:before{content:"\f512"}.fa-themeisle:before{content:"\f2b2"}.fa-page4:before{content:"\f3d7"}.fa-hashnode:before{content:"\e499"}.fa-react:before{content:"\f41b"}.fa-cc-paypal:before{content:"\f1f4"}.fa-squarespace:before{content:"\f5be"}.fa-cc-stripe:before{content:"\f1f5"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-bitcoin:before{content:"\f379"}.fa-keycdn:before{content:"\f3ba"}.fa-opera:before{content:"\f26a"}.fa-itch-io:before{content:"\f83a"}.fa-umbraco:before{content:"\f8e8"}.fa-galactic-senate:before{content:"\f50d"}.fa-ubuntu:before{content:"\f7df"}.fa-draft2digital:before{content:"\f396"}.fa-stripe:before{content:"\f429"}.fa-houzz:before{content:"\f27c"}.fa-gg:before{content:"\f260"}.fa-dhl:before{content:"\f790"}.fa-pinterest-square:before,.fa-square-pinterest:before{content:"\f0d3"}.fa-xing:before{content:"\f168"}.fa-blackberry:before{content:"\f37b"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-playstation:before{content:"\f3df"}.fa-quinscape:before{content:"\f459"}.fa-less:before{content:"\f41d"}.fa-blogger-b:before{content:"\f37d"}.fa-opencart:before{content:"\f23d"}.fa-vine:before{content:"\f1ca"}.fa-paypal:before{content:"\f1ed"}.fa-gitlab:before{content:"\f296"}.fa-typo3:before{content:"\f42b"}.fa-reddit-alien:before{content:"\f281"}.fa-yahoo:before{content:"\f19e"}.fa-dailymotion:before{content:"\e052"}.fa-affiliatetheme:before{content:"\f36b"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-bootstrap:before{content:"\f836"}.fa-odnoklassniki:before{content:"\f263"}.fa-nfc-symbol:before{content:"\e531"}.fa-ethereum:before{content:"\f42e"}.fa-speaker-deck:before{content:"\f83c"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-patreon:before{content:"\f3d9"}.fa-avianex:before{content:"\f374"}.fa-ello:before{content:"\f5f1"}.fa-gofore:before{content:"\f3a7"}.fa-bimobject:before{content:"\f378"}.fa-facebook-f:before{content:"\f39e"}.fa-google-plus-square:before,.fa-square-google-plus:before{content:"\f0d4"}.fa-mandalorian:before{content:"\f50f"}.fa-first-order-alt:before{content:"\f50a"}.fa-osi:before{content:"\f41a"}.fa-google-wallet:before{content:"\f1ee"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-periscope:before{content:"\f3da"}.fa-fulcrum:before{content:"\f50b"}.fa-cloudscale:before{content:"\f383"}.fa-forumbee:before{content:"\f211"}.fa-mizuni:before{content:"\f3cc"}.fa-schlix:before{content:"\f3ea"}.fa-square-xing:before,.fa-xing-square:before{content:"\f169"}.fa-bandcamp:before{content:"\f2d5"}.fa-wpforms:before{content:"\f298"}.fa-cloudversify:before{content:"\f385"}.fa-usps:before{content:"\f7e1"}.fa-megaport:before{content:"\f5a3"}.fa-magento:before{content:"\f3c4"}.fa-spotify:before{content:"\f1bc"}.fa-optin-monster:before{content:"\f23c"}.fa-fly:before{content:"\f417"}.fa-aviato:before{content:"\f421"}.fa-itunes:before{content:"\f3b4"}.fa-cuttlefish:before{content:"\f38c"}.fa-blogger:before{content:"\f37c"}.fa-flickr:before{content:"\f16e"}.fa-viber:before{content:"\f409"}.fa-soundcloud:before{content:"\f1be"}.fa-digg:before{content:"\f1a6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-symfony:before{content:"\f83d"}.fa-maxcdn:before{content:"\f136"}.fa-etsy:before{content:"\f2d7"}.fa-facebook-messenger:before{content:"\f39f"}.fa-audible:before{content:"\f373"}.fa-think-peaks:before{content:"\f731"}.fa-bilibili:before{content:"\e3d9"}.fa-erlang:before{content:"\f39d"}.fa-cotton-bureau:before{content:"\f89e"}.fa-dashcube:before{content:"\f210"}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-stack-exchange:before{content:"\f18d"}.fa-elementor:before{content:"\f430"}.fa-pied-piper-square:before,.fa-square-pied-piper:before{content:"\e01e"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-palfed:before{content:"\f3d8"}.fa-superpowers:before{content:"\f2dd"}.fa-resolving:before{content:"\f3e7"}.fa-xbox:before{content:"\f412"}.fa-searchengin:before{content:"\f3eb"}.fa-tiktok:before{content:"\e07b"}.fa-facebook-square:before,.fa-square-facebook:before{content:"\f082"}.fa-renren:before{content:"\f18b"}.fa-linux:before{content:"\f17c"}.fa-glide:before{content:"\f2a5"}.fa-linkedin:before{content:"\f08c"}.fa-hubspot:before{content:"\f3b2"}.fa-deploydog:before{content:"\f38e"}.fa-twitch:before{content:"\f1e8"}.fa-ravelry:before{content:"\f2d9"}.fa-mixer:before{content:"\e056"}.fa-lastfm-square:before,.fa-square-lastfm:before{content:"\f203"}.fa-vimeo:before{content:"\f40a"}.fa-mendeley:before{content:"\f7b3"}.fa-uniregistry:before{content:"\f404"}.fa-figma:before{content:"\f799"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-dropbox:before{content:"\f16b"}.fa-instagram:before{content:"\f16d"}.fa-cmplid:before{content:"\e360"}.fa-facebook:before{content:"\f09a"}.fa-gripfire:before{content:"\f3ac"}.fa-jedi-order:before{content:"\f50e"}.fa-uikit:before{content:"\f403"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-phabricator:before{content:"\f3db"}.fa-ussunnah:before{content:"\f407"}.fa-earlybirds:before{content:"\f39a"}.fa-trade-federation:before{content:"\f513"}.fa-autoprefixer:before{content:"\f41c"}.fa-whatsapp:before{content:"\f232"}.fa-slideshare:before{content:"\f1e7"}.fa-google-play:before{content:"\f3ab"}.fa-viadeo:before{content:"\f2a9"}.fa-line:before{content:"\f3c0"}.fa-google-drive:before{content:"\f3aa"}.fa-servicestack:before{content:"\f3ec"}.fa-simplybuilt:before{content:"\f215"}.fa-bitbucket:before{content:"\f171"}.fa-imdb:before{content:"\f2d8"}.fa-deezer:before{content:"\e077"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-jira:before{content:"\f7b1"}.fa-docker:before{content:"\f395"}.fa-screenpal:before{content:"\e570"}.fa-bluetooth:before{content:"\f293"}.fa-gitter:before{content:"\f426"}.fa-d-and-d:before{content:"\f38d"}.fa-microblog:before{content:"\e01a"}.fa-cc-diners-club:before{content:"\f24c"}.fa-gg-circle:before{content:"\f261"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-yandex:before{content:"\f413"}.fa-readme:before{content:"\f4d5"}.fa-html5:before{content:"\f13b"}.fa-sellsy:before{content:"\f213"}.fa-sass:before{content:"\f41e"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-buromobelexperte:before{content:"\f37f"}.fa-salesforce:before{content:"\f83b"}.fa-octopus-deploy:before{content:"\e082"}.fa-medapps:before{content:"\f3c6"}.fa-ns8:before{content:"\f3d5"}.fa-pinterest-p:before{content:"\f231"}.fa-apper:before{content:"\f371"}.fa-fort-awesome:before{content:"\f286"}.fa-waze:before{content:"\f83f"}.fa-cc-jcb:before{content:"\f24b"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-rust:before{content:"\e07a"}.fa-wix:before{content:"\f5cf"}.fa-behance-square:before,.fa-square-behance:before{content:"\f1b5"}.fa-supple:before{content:"\f3f9"}.fa-rebel:before{content:"\f1d0"}.fa-css3:before{content:"\f13c"}.fa-staylinked:before{content:"\f3f5"}.fa-kaggle:before{content:"\f5fa"}.fa-space-awesome:before{content:"\e5ac"}.fa-deviantart:before{content:"\f1bd"}.fa-cpanel:before{content:"\f388"}.fa-goodreads-g:before{content:"\f3a9"}.fa-git-square:before,.fa-square-git:before{content:"\f1d2"}.fa-square-tumblr:before,.fa-tumblr-square:before{content:"\f174"}.fa-trello:before{content:"\f181"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-get-pocket:before{content:"\f265"}.fa-perbyte:before{content:"\e083"}.fa-grunt:before{content:"\f3ad"}.fa-weebly:before{content:"\f5cc"}.fa-connectdevelop:before{content:"\f20e"}.fa-leanpub:before{content:"\f212"}.fa-black-tie:before{content:"\f27e"}.fa-themeco:before{content:"\f5c6"}.fa-python:before{content:"\f3e2"}.fa-android:before{content:"\f17b"}.fa-bots:before{content:"\e340"}.fa-free-code-camp:before{content:"\f2c5"}.fa-hornbill:before{content:"\f592"}.fa-js:before{content:"\f3b8"}.fa-ideal:before{content:"\e013"}.fa-git:before{content:"\f1d3"}.fa-dev:before{content:"\f6cc"}.fa-sketch:before{content:"\f7c6"}.fa-yandex-international:before{content:"\f414"}.fa-cc-amex:before{content:"\f1f3"}.fa-uber:before{content:"\f402"}.fa-github:before{content:"\f09b"}.fa-php:before{content:"\f457"}.fa-alipay:before{content:"\f642"}.fa-youtube:before{content:"\f167"}.fa-skyatlas:before{content:"\f216"}.fa-firefox-browser:before{content:"\e007"}.fa-replyd:before{content:"\f3e6"}.fa-suse:before{content:"\f7d6"}.fa-jenkins:before{content:"\f3b6"}.fa-twitter:before{content:"\f099"}.fa-rockrms:before{content:"\f3e9"}.fa-pinterest:before{content:"\f0d2"}.fa-buffer:before{content:"\f837"}.fa-npm:before{content:"\f3d4"}.fa-yammer:before{content:"\f840"}.fa-btc:before{content:"\f15a"}.fa-dribbble:before{content:"\f17d"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-internet-explorer:before{content:"\f26b"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-old-republic:before{content:"\f510"}.fa-square-whatsapp:before,.fa-whatsapp-square:before{content:"\f40c"}.fa-node-js:before{content:"\f3d3"}.fa-edge-legacy:before{content:"\e078"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-medrt:before{content:"\f3c8"}.fa-usb:before{content:"\f287"}.fa-tumblr:before{content:"\f173"}.fa-vaadin:before{content:"\f408"}.fa-quora:before{content:"\f2c4"}.fa-reacteurope:before{content:"\f75d"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-amilia:before{content:"\f36d"}.fa-mixcloud:before{content:"\f289"}.fa-flipboard:before{content:"\f44d"}.fa-viacoin:before{content:"\f237"}.fa-critical-role:before{content:"\f6c9"}.fa-sitrox:before{content:"\e44a"}.fa-discourse:before{content:"\f393"}.fa-joomla:before{content:"\f1aa"}.fa-mastodon:before{content:"\f4f6"}.fa-airbnb:before{content:"\f834"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-buy-n-large:before{content:"\f8a6"}.fa-gulp:before{content:"\f3ae"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-strava:before{content:"\f428"}.fa-ember:before{content:"\f423"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-teamspeak:before{content:"\f4f9"}.fa-pushed:before{content:"\f3e1"}.fa-wordpress-simple:before{content:"\f411"}.fa-nutritionix:before{content:"\f3d6"}.fa-wodu:before{content:"\e088"}.fa-google-pay:before{content:"\e079"}.fa-intercom:before{content:"\f7af"}.fa-zhihu:before{content:"\f63f"}.fa-korvue:before{content:"\f42f"}.fa-pix:before{content:"\e43a"}.fa-steam-symbol:before{content:"\f3f6"}:host,:root{--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a} \ No newline at end of file diff --git a/src/assets/css/style.scss b/src/assets/css/style.scss new file mode 100644 index 0000000..9f41894 --- /dev/null +++ b/src/assets/css/style.scss @@ -0,0 +1,4 @@ +--- +--- + +@import "main"; diff --git a/src/assets/favicons/android-chrome-192x192.png b/src/assets/favicons/android-chrome-192x192.png new file mode 100644 index 0000000..9100b0e Binary files /dev/null and b/src/assets/favicons/android-chrome-192x192.png differ diff --git a/src/assets/favicons/android-chrome-384x384.png b/src/assets/favicons/android-chrome-384x384.png new file mode 100644 index 0000000..babf68c Binary files /dev/null and b/src/assets/favicons/android-chrome-384x384.png differ diff --git a/src/assets/favicons/android-chrome-512x512.png b/src/assets/favicons/android-chrome-512x512.png new file mode 100644 index 0000000..e886216 Binary files /dev/null and b/src/assets/favicons/android-chrome-512x512.png differ diff --git a/src/assets/favicons/apple-touch-icon.png b/src/assets/favicons/apple-touch-icon.png new file mode 100644 index 0000000..6e70307 Binary files /dev/null and b/src/assets/favicons/apple-touch-icon.png differ diff --git a/src/assets/favicons/browserconfig.xml b/src/assets/favicons/browserconfig.xml new file mode 100644 index 0000000..5cd27e3 --- /dev/null +++ b/src/assets/favicons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #603cba + + + diff --git a/src/assets/favicons/favicon-16x16.png b/src/assets/favicons/favicon-16x16.png new file mode 100644 index 0000000..b81a8dc Binary files /dev/null and b/src/assets/favicons/favicon-16x16.png differ diff --git a/src/assets/favicons/favicon-32x32.png b/src/assets/favicons/favicon-32x32.png new file mode 100644 index 0000000..532571d Binary files /dev/null and b/src/assets/favicons/favicon-32x32.png differ diff --git a/src/assets/favicons/favicon.ico b/src/assets/favicons/favicon.ico new file mode 100644 index 0000000..fbc9922 Binary files /dev/null and b/src/assets/favicons/favicon.ico differ diff --git a/src/assets/favicons/mstile-150x150.png b/src/assets/favicons/mstile-150x150.png new file mode 100644 index 0000000..455d5ed Binary files /dev/null and b/src/assets/favicons/mstile-150x150.png differ diff --git a/src/assets/favicons/safari-pinned-tab.svg b/src/assets/favicons/safari-pinned-tab.svg new file mode 100644 index 0000000..ec6019b --- /dev/null +++ b/src/assets/favicons/safari-pinned-tab.svg @@ -0,0 +1,25 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/src/assets/favicons/site.webmanifest b/src/assets/favicons/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/src/assets/favicons/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/src/assets/img/avatar.jpg b/src/assets/img/avatar.jpg new file mode 100644 index 0000000..e13d044 Binary files /dev/null and b/src/assets/img/avatar.jpg differ diff --git a/src/assets/js/disqus.js b/src/assets/js/disqus.js new file mode 100644 index 0000000..f490611 --- /dev/null +++ b/src/assets/js/disqus.js @@ -0,0 +1 @@ +!function(t,e,n){"use strict";var o=function(t,e){var n,o;return function(){var i=this,r=arguments,d=+new Date;n&&dt.innerHeight*r||i-l-u.offsetHeight-t.innerHeight*r>0)return!0;var c,f,p,y=e.getElementById("disqus_thread");y&&y.removeAttribute("id"),u.setAttribute("id","disqus_thread"),u.disqusLoaderStatus="loaded","loaded"==a?DISQUS.reset({reload:!0,config:d}):(t.disqus_config=d,"unloaded"==a&&(a="loading",c=s,f=function(){a="loaded"},(p=e.createElement("script")).src=c,p.async=!0,p.setAttribute("data-timestamp",+new Date),p.addEventListener("load",function(){"function"==typeof f&&f()}),(e.head||e.body).appendChild(p)))};t.addEventListener("scroll",o(i,l)),t.addEventListener("resize",o(i,l)),t.disqusLoader=function(t,n){n=function(t,e){var n,o={};for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(o[n]=t[n]);for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(o[n]=e[n]);return o}({laziness:1,throttle:250,scriptUrl:!1,disqusConfig:!1},n),r=n.laziness+1,i=n.throttle,d=n.disqusConfig,s=!1===s?n.scriptUrl:s,(u="string"==typeof t?e.querySelector(t):"number"==typeof t.length?t[0]:t)&&(u.disqusLoaderStatus="unloaded"),l()}}(window,document); \ No newline at end of file diff --git a/src/assets/js/main.js b/src/assets/js/main.js new file mode 100644 index 0000000..b58aca9 --- /dev/null +++ b/src/assets/js/main.js @@ -0,0 +1,31 @@ +(() => { + // Theme switch + const body = document.body; + const lamp = document.getElementById("mode"); + + const toggleTheme = (state) => { + if (state === "dark") { + localStorage.setItem("theme", "light"); + body.removeAttribute("data-theme"); + } else if (state === "light") { + localStorage.setItem("theme", "dark"); + body.setAttribute("data-theme", "dark"); + } else { + initTheme(state); + } + }; + + lamp.addEventListener("click", () => + toggleTheme(localStorage.getItem("theme")) + ); + + // Blur the content when the menu is open + const cbox = document.getElementById("menu-trigger"); + + cbox.addEventListener("change", function () { + const area = document.querySelector(".wrapper"); + this.checked + ? area.classList.add("blurry") + : area.classList.remove("blurry"); + }); +})(); diff --git a/src/assets/js/search.min.js b/src/assets/js/search.min.js new file mode 100644 index 0000000..7b47ead --- /dev/null +++ b/src/assets/js/search.min.js @@ -0,0 +1,6 @@ +/*! + * Simple-Jekyll-Search + * Copyright 2015-2020, Christian Fei + * Licensed under the MIT License. + */ + !function(){"use strict";var i={compile:function(r){return o.template.replace(o.pattern,function(t,e){var n=o.middleware(e,r[e],o.template);return void 0!==n?n:r[e]||t})},setOptions:function(t){o.pattern=t.pattern||o.pattern,o.template=t.template||o.template,"function"==typeof t.middleware&&(o.middleware=t.middleware)}},o={};o.pattern=/\{(.*?)\}/g,o.template="",o.middleware=function(){};var n=function(t,e){var n=e.length,r=t.length;if(n{title}',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,exclude:[]},w=function j(t){if(!((e=t)&&"undefined"!=typeof e.required&&e.required instanceof Array))throw new Error("-- OptionsValidator: required options missing");var e;if(!(this instanceof j))return new j(t);var r=t.required;this.getRequiredOptions=function(){return r},this.validate=function(e){var n=[];return r.forEach(function(t){"undefined"==typeof e[t]&&n.push(t)}),n}}({required:v=["searchInput","resultsContainer","json"]}),t.SimpleJekyllSearch=function(t){var n;0 + + + + + #2c2c2c + + + diff --git a/src/gemset.nix b/src/gemset.nix new file mode 100644 index 0000000..7e8feb5 --- /dev/null +++ b/src/gemset.nix @@ -0,0 +1,383 @@ +{ + addressable = { + dependencies = ["public_suffix"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1ypdmpdn20hxp5vwxz3zc04r5xcwqc25qszdlg41h8ghdqbllwmw"; + type = "gem"; + }; + version = "2.8.1"; + }; + colorator = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0f7wvpam948cglrciyqd798gdc6z3cfijciavd0dfixgaypmvy72"; + type = "gem"; + }; + version = "1.1.0"; + }; + commonmarker = { + dependencies = ["ruby-enum"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1pmjm87p0hxnknp33cxyvkgbr1swfp9gcznssmalm9z8kwyancb9"; + type = "gem"; + }; + version = "0.17.13"; + }; + concurrent-ruby = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1qnsflsbjj38im8xq35g0vihlz96h09wjn2dad5g543l3vvrkrx5"; + type = "gem"; + }; + version = "1.2.0"; + }; + em-websocket = { + dependencies = ["eventmachine" "http_parser.rb"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1a66b0kjk6jx7pai9gc7i27zd0a128gy73nmas98gjz6wjyr4spm"; + type = "gem"; + }; + version = "0.5.3"; + }; + eventmachine = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0wh9aqb0skz80fhfn66lbpr4f86ya2z5rx6gm5xlfhd05bj1ch4r"; + type = "gem"; + }; + version = "1.2.7"; + }; + ffi = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1862ydmclzy1a0cjbvm8dz7847d9rch495ib0zb64y84d3xd4bkg"; + type = "gem"; + }; + version = "1.15.5"; + }; + forwardable-extended = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "15zcqfxfvsnprwm8agia85x64vjzr2w0xn9vxfnxzgcv8s699v0v"; + type = "gem"; + }; + version = "2.6.0"; + }; + "http_parser.rb" = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1gj4fmls0mf52dlr928gaq0c0cb0m3aqa9kaa6l0ikl2zbqk42as"; + type = "gem"; + }; + version = "0.8.0"; + }; + i18n = { + dependencies = ["concurrent-ruby"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1vdcchz7jli1p0gnc669a7bj3q1fv09y9ppf0y3k0vb1jwdwrqwi"; + type = "gem"; + }; + version = "1.12.0"; + }; + jekyll = { + dependencies = ["addressable" "colorator" "em-websocket" "i18n" "jekyll-sass-converter" "jekyll-watch" "kramdown" "kramdown-parser-gfm" "liquid" "mercenary" "pathutil" "rouge" "safe_yaml" "terminal-table"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "192k1ggw99slpqpxb4xamcvcm2pdahgnmygl746hmkrar0i3xa5r"; + type = "gem"; + }; + version = "4.1.1"; + }; + jekyll-commonmark = { + dependencies = ["commonmarker" "jekyll"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "15kr36k56l4fh8yp7qswn9m91v7sa5kr2vq9w40li16z4n4akk57"; + type = "gem"; + }; + version = "1.3.1"; + }; + jekyll-commonmark-ghpages = { + dependencies = ["commonmarker" "jekyll-commonmark" "rouge"]; + groups = ["jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1bhpk7iiz2p0hha650zxqq7rlbfj92m9qxxxasarrswszl4pcvp7"; + type = "gem"; + }; + version = "0.1.6"; + }; + jekyll-compose = { + dependencies = ["jekyll"]; + groups = ["jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1ny8xps0mrmx2w0xxc9rwa15ch1wkxvdrzxiwnqramqwja566y04"; + type = "gem"; + }; + version = "0.12.0"; + }; + jekyll-feed = { + dependencies = ["jekyll"]; + groups = ["jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1hzwmjrxi57x68i7jx5rxi8qlcbqcbg3di55wywrp53pr0bap6k8"; + type = "gem"; + }; + version = "0.17.0"; + }; + jekyll-postfiles = { + dependencies = ["jekyll"]; + groups = ["jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0l8n0qwn6cr5mqhjzi4v8z1gm6d4f0rjpc6745vkm5b9kv3vjpii"; + type = "gem"; + }; + version = "3.1.0"; + }; + jekyll-sass-converter = { + dependencies = ["sassc"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "077xkkkb592vg8kxdia9jwsaz1bc70lkpf4hdvazjqphn5hlz2bi"; + type = "gem"; + }; + version = "2.2.0"; + }; + jekyll-sitemap = { + dependencies = ["jekyll"]; + groups = ["jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0622rwsn5i0m5xcyzdn86l68wgydqwji03lqixdfm1f1xdfqrq0d"; + type = "gem"; + }; + version = "1.4.0"; + }; + jekyll-watch = { + dependencies = ["listen"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1qd7hy1kl87fl7l0frw5qbn22x7ayfzlv9a5ca1m59g0ym1ysi5w"; + type = "gem"; + }; + version = "2.2.1"; + }; + kramdown = { + dependencies = ["rexml"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1ic14hdcqxn821dvzki99zhmcy130yhv5fqfffkcf87asv5mnbmn"; + type = "gem"; + }; + version = "2.4.0"; + }; + kramdown-parser-gfm = { + dependencies = ["kramdown"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0a8pb3v951f4x7h968rqfsa19c8arz21zw1vaj42jza22rap8fgv"; + type = "gem"; + }; + version = "1.1.0"; + }; + liquid = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1czxv2i1gv3k7hxnrgfjb0z8khz74l4pmfwd70c7kr25l2qypksg"; + type = "gem"; + }; + version = "4.0.4"; + }; + listen = { + dependencies = ["rb-fsevent" "rb-inotify"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "13rgkfar8pp31z1aamxf5y7cfq88wv6rxxcwy7cmm177qq508ycn"; + type = "gem"; + }; + version = "3.8.0"; + }; + mercenary = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0f2i827w4lmsizrxixsrv2ssa3gk1b7lmqh8brk8ijmdb551wnmj"; + type = "gem"; + }; + version = "0.4.0"; + }; + pathutil = { + dependencies = ["forwardable-extended"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "12fm93ljw9fbxmv2krki5k5wkvr7560qy8p4spvb9jiiaqv78fz4"; + type = "gem"; + }; + version = "0.16.2"; + }; + public_suffix = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0hz0bx2qs2pwb0bwazzsah03ilpf3aai8b7lk7s35jsfzwbkjq35"; + type = "gem"; + }; + version = "5.0.1"; + }; + rb-fsevent = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1zmf31rnpm8553lqwibvv3kkx0v7majm1f341xbxc0bk5sbhp423"; + type = "gem"; + }; + version = "0.11.2"; + }; + rb-inotify = { + dependencies = ["ffi"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1jm76h8f8hji38z3ggf4bzi8vps6p7sagxn3ab57qc0xyga64005"; + type = "gem"; + }; + version = "0.10.1"; + }; + rexml = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "08ximcyfjy94pm1rhcx04ny1vx2sk0x4y185gzn86yfsbzwkng53"; + type = "gem"; + }; + version = "3.2.5"; + }; + rouge = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1dnfkrk8xx2m8r3r9m2p5xcq57viznyc09k7r3i4jbm758i57lx3"; + type = "gem"; + }; + version = "3.30.0"; + }; + ruby-enum = { + dependencies = ["i18n"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1pys90hxylhyg969iw9lz3qai5lblf8xwbdg1g5aj52731a9k83p"; + type = "gem"; + }; + version = "0.9.0"; + }; + safe_yaml = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0j7qv63p0vqcd838i2iy2f76c3dgwzkiz1d1xkg7n0pbnxj2vb56"; + type = "gem"; + }; + version = "1.0.5"; + }; + sassc = { + dependencies = ["ffi"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "0gpqv48xhl8mb8qqhcifcp0pixn206a7imc07g48armklfqa4q2c"; + type = "gem"; + }; + version = "2.4.0"; + }; + terminal-table = { + dependencies = ["unicode-display_width"]; + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1512cngw35hsmhvw4c05rscihc59mnj09m249sm9p3pik831ydqk"; + type = "gem"; + }; + version = "1.8.0"; + }; + unicode-display_width = { + groups = ["default" "jekyll_plugins"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1204c1jx2g89pc25qk5150mk7j5k90692i7ihgfzqnad6qni74h2"; + type = "gem"; + }; + version = "1.8.0"; + }; + webrick = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "1d4cvgmxhfczxiq5fr534lmizkhigd15bsx5719r5ds7k7ivisc7"; + type = "gem"; + }; + version = "1.7.0"; + }; +} diff --git a/src/index.md b/src/index.md new file mode 100644 index 0000000..bf0c84b --- /dev/null +++ b/src/index.md @@ -0,0 +1,3 @@ +--- +layout: home +--- \ No newline at end of file diff --git a/src/klise.gemspec b/src/klise.gemspec new file mode 100644 index 0000000..70d7e5d --- /dev/null +++ b/src/klise.gemspec @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +Gem::Specification.new do |spec| + spec.name = "klise" + spec.version = "1.0.1" + spec.authors = ["Mahendrata Harpi"] + spec.email = ["justharpi@gmail.com"] + + spec.summary = "🏖 Klisé is a minimalist Jekyll theme for running a personal site or blog, light & dark mode support." + spec.homepage = "https://github.com/piharpi/jekyll-klise" + spec.license = "MIT" + + spec.metadata["plugin_type"] = "theme" + spec.files = `git ls-files -z`.split("\x0").select do |f| + f.match(%r{^(_(includes|layouts|sass)/|(assets|LICENSE|README)((\.(txt|md|markdown|yml)|$)))}i) + end + + spec.add_runtime_dependency "jekyll", "~> 4.1" + spec.add_runtime_dependency 'jekyll-feed', '~> 0.13' + spec.add_runtime_dependency 'jekyll-sitemap', '~> 1.4' + spec.add_runtime_dependency 'jekyll-compose', '~> 0.12.0' + spec.add_runtime_dependency 'jekyll-postfiles', '~> 3.1' + + spec.add_development_dependency "bundler", "~> 2.1" +end diff --git a/src/now.json b/src/now.json new file mode 100644 index 0000000..b6dcfd5 --- /dev/null +++ b/src/now.json @@ -0,0 +1,7 @@ +{ + "version": 2, + "routes": [ + { "handle": "filesystem" }, + { "src": "/.*", "status": 404, "dest": "404.html" } + ] +} diff --git a/src/tags.html b/src/tags.html new file mode 100644 index 0000000..2140573 --- /dev/null +++ b/src/tags.html @@ -0,0 +1,27 @@ +--- +title: Tags +permalink: /tags/ +layout: page +excerpt: Sorted article by tags. +--- + +
    + all + {%- for tag in site.tags -%} + {% capture name %}{{ tag | first }}{% endcapture %} + {{ name }} + {%- endfor -%} +
    + +{%- for tag in site.tags -%} + {%- capture name -%}{{ tag | first }}{%- endcapture -%} +

    {{ name | upcase }}

    + {%- for post in site.tags[name] -%} + + {%- endfor -%} +{%- endfor -%} \ No newline at end of file